SG

作業項目を完全に削除する

 TFS を使っていても、作業項目を削除する機能が見当たらない。GUI からでは作業項目を作業することができないため、コマンドラインのツールを使用する。作業項目の状態を変更してやれば済む話だが、余分な項目があると落ち着かない人には有効。

echo off
set /p ID=削除する作業項目のIDを入力してください : 

cd "C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE"
witadmin destroywi /collection:http://tfs-server/tfs/HogeTeam /id:%ID%

1バイト単位まで指定したサイズのファイルを作成する

 ファイルサイズによって処理を変更する機能をテストしたい場合、「大体あってる」サイズのファイルを使用したテストよりも、境界サイズをテストした方が不具合検出に役立つのは、テストの経験が浅い担当者でも感覚的に理解していると思う。下記の内容をバッチファイルに保存し、実行することで、完全にサイズが一致したファイルを作成することが可能となる。

echo off
cd /d %~dp0

set /p FILENAME=ファイル名を入力してください : 
set /p SIZE=サイズを入力してください(バイト単位) : 

fsutil file createnew %FILENAME% %SIZE%

 なお、Windows Vista 移行では作成したバッチファイルを「管理者として実行」する必要がある。また、ファイル自体は指定されたバイト数だけ 0 で埋まっているだけのものなので、圧縮処理をテストする場合には注意が必要となる(圧縮アルゴリズムにもよるが、中身には 0 しかないため非常に圧縮率が高くなる)。

Developers Summit 2012 に行ってきた

 なんと製品がリリースできたので、Developers Summit 2012に行ってきました。勉強会じゃないけどこのカテゴリにしておく。去年も同じ事言ってますね。まるで成長していない……いや、今回は品川で降りて目黒まで新幹線の切符1枚で辿り着いているので成長はしています。まぁ男坂もとい行人坂を間違えて交番のほうまで行ってしまったのは去年と同じなんですが。
 講演についてはマインドマップを作りながら聞いていました。始めの方はスライドに書かれているキーワードを拾っていたのですが、それよりは話の中で自分が重要だなと思った点を残しておいたほうが後で見直したときに役に立つのではないか、と思って方針を試してみたりも。スライドは大体の方が公開されてますし。マインドマップは効率よくメモが取れる反面、何を書くか選択する為にはスキルが必要だと改めて感じました。












 為になる講演が多かったのですが、特に長沢さんの講演は、TFS を活用していることもあり、今後に生かせそうだと感じました。去年のデブサミでも長沢さんの講演を拝聴していましたが、そのとき弊社では悪名高い VSS を使用していたこともあり、内容が半分も理解できていませんでした。その点を思うと、人より能力の無い自分でも、1年間勉強を続けてれば少しは成長するもんだなぁと。

 和田さんの講演「あの人の自分戦略を聞きたい!」も拝聴しました。私が参加申し込みをしたときは、Facebook でのアンケートも始まっていませんでしたが、内容を読んだときに「あぁ、これは bleis さんが講演されるに違いない」という謎の自信を持っていました。その予想は後日現実のものとなったわけです。伊達に 1年間ストーカー……もとい背中を追ってはいないですからね。
 しかし途中で iPod touch の電源が切れる事態となり、マインドマップは時空の彼方へと消え去ってしまったのでした……。私に足りないのはアウトプットだな、と思いました。それにしても、bleis さんはプログラミングの勉強を始めてから凡そ 10年だそうです。この差はどうやっても埋まらないものだと自覚していますが、差が縮まらないことに絶望して手を止めてしまうことはしないように努力していきたいと考えています。怠けないウサギに追いつけないからと不貞腐れていては、怠けないカメに抜かれてしまいますし。

 「縮まらないから」といって
 それがオレが進まない理由にはならん
 「抜けない事があきらか」だからって
 オレが「努力しなくていい」って事にはならない

 まずは 10年。

 

F# で TFS 上のテスト結果を取得してみた

open System
open Microsoft.TeamFoundation.Client
open Microsoft.TeamFoundation.TestManagement.Client
open Microsoft.TeamFoundation.WorkItemTracking.Client

(* プロジェクトの一覧を取得 *)
let getTestProjectsByProjectCollection (collection : TfsTeamProjectCollection) = 
  let testService = collection.GetService(typeof<ITestManagementService>) :?> ITestManagementService
  let store = collection.GetService(typeof<WorkItemStore>) :?> WorkItemStore
      
  store.Projects
  |> Seq.cast
  |> Seq.map (fun (project : Project) -> testService.GetTeamProject(project))
    
(* Uri からプロジェクトの一覧を取得 *)
let getTestProjectsByUrl uriString = 
  let uri = Uri(uriString)
  let collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(uri)

  getTestProjectsByProjectCollection collection     
    
(* テスト計画の一覧を取得 *)
let getTestPlans (testProject : ITestManagementTeamProject) = 
  testProject.TestPlans.Query("Select * From TestPlan")
    
(* テストスイート全体のテストスイートの一覧を取得 *)
let getTestSuitesInSuite (suite : ITestSuiteEntry) = 
    
  let rec getSuite (suite : ITestSuiteEntry) = 
        
    match suite.TestSuite with
    | :? IStaticTestSuite
      ->          
        let other = 
          let staticSuite = suite.TestSuite :?> IStaticTestSuite
          staticSuite.Entries
          |> Seq.cast
          |> Seq.collect (fun e -> getSuite e)
          
        if suite.TestCase = null then
          let self = Seq.singleton suite
          Seq.append self other 
        else
          other

    | _ -> 
      Seq.empty

  getSuite suite
    
(* テスト計画全体のテストスイートの一覧を取得 *)
let getTestSuitesInPlan (testPlan : ITestPlan) = 
  testPlan.RootSuite.Entries
  |> Seq.cast
  |> Seq.collect (fun s -> getTestSuitesInSuite s)

(* テストプロジェクト全体のテストスイートの一覧を取得 *)
let getTestSuitesInProject (testProject : ITestManagementTeamProject) = 
  testProject
  |> getTestPlans
  |> Seq.cast 
  |> Seq.collect (fun p -> getTestSuitesInPlan p)

(* 対象のテストスイートで実行された対象のテストの結果を取得 *)
let getTestPointsBySuiteAndTestCase (suite : ITestSuiteEntry) (testcase : ITestCase) = 
  let plan = suite.TestSuite.Plan

  plan.QueryTestPoints(
      sprintf "SELECT * FROM TestPoint Where SuiteId = %A AND TestCaseId = %A" suite.Id testcase.Id)
        
(* 対象のテストスイートで実行されたテストの結果を取得 *)         
let getTestPointsInSuite (suite : ITestSuiteEntry) = 
 
  match suite.TestSuite with
  | :? IStaticTestSuite ->

    let staticSuite = suite.TestSuite :?> IStaticTestSuite
    staticSuite.TestCases
    |> Seq.collect (fun t -> getTestPointsBySuiteAndTestCase suite t.TestCase)

  | _ -> 
    Seq.empty


type TestResult = {
    Id : int;
    Title : string;
    Outcome : TestOutcome
} 
   
[<EntryPoint>]
let main args = 

  let uri = args.[0]
  let projectName = args.[1]
  
  let results = 
    uri
    |> getTestProjectsByUrl
    |> Seq.filter  (fun proj  -> proj.WitProject.Name = projectName)
    |> Seq.collect (fun proj  -> getTestSuitesInProject proj) 
    |> Seq.collect (fun suite -> getTestPointsInSuite suite)
    |> Seq.filter  (fun point -> point.MostRecentResult <> null)
    |> Seq.map     (fun point -> {Id = point.Id;
                                  Title = point.TestCaseWorkItem.Title;
                                  Outcome = point.MostRecentResult.Outcome})
  
  results 
  |> Seq.toList
  |> List.iter (fun result -> printfn "[%d] %s : %A" result.Id result.Title result.Outcome) |> ignore

  0

F# で LINQ 入門 Count LongCount

 シーケンスの要素数を返します。IEnumerable(Of T) を受け取ったとき、.NET Framework 2.0 の頃では、いちいち List(Of T) に入れて要素数を取得する……なんてアプローチを逐一とる必要がありましたが、このメソッドのおかげで簡単に要素数を調べることが出来ます。LongCount は要素数が整数値を超える場合に使用できます。膨大な数のログを処理するときなどには有効なのではないでしょうか。

 まずはシーケンスを作成します。

>
open System
open System.Linq

let target = seq{ 1..10 }


 要素数を取得します。

> target.Count();;
val it : int = 10


 期待通り。では、整数値の最大を超える要素数の取得はどうだろう。

>
let target' = seq{ 1..System.Int32.MaxValue + 1 };;

val target' : seq<int>

> target'.Count();;
val it : int = 0


 なぜか 0 になってしまいました。System.Int32.MaxValue + 1 は何が評価されるんだろう。

> System.Int32.MaxValue + 1;;
val it : int = -2147483648


 マイナス……だと……? 実は F# では、最大値を超えるような計算をすると、値が1周して符号が反転してしまう振る舞いがデフォルトとなっています。減算でも同様。

> System.Int32.MinValue - 1;;
val it : int = 2147483647


 C#VB のように、その型が扱うことのできる値の範囲を超える場合はエラー、としたい場合は Checked モジュールを open します。

> open Checked;;
> System.Int32.MaxValue + 1;;
> System.OverflowException: 算術演算の結果オーバーフローが発生しました。
   場所 <StartupCode$FSI_0017>.$FSI_0017.main@()
エラーのため停止しました

 とはいえ、どちらにしても int では System.Int32.MaxValue + 1 の値を表現できないため、int64 を使用します。

> let target' = seq{ 1L..(int64 System.Int32.MaxValue + 1L) };;

val target' : seq<int64>

> target'.Count();;
System.OverflowException: 算術演算の結果オーバーフローが発生しました。
   場所 System.Linq.Enumerable.Count[TSource](IEnumerable`1 source)
   場所 <StartupCode$FSI_0024>.$FSI_0024.main@()
エラーのため停止しました

 Count メソッドはシーケンスの要素数を整数値で返すメソッドなので、シーケンスの要素数が整数値を超える場合の呼び出しではエラーになってしまいます。ここでようやく LongCount の出番がやってきます。

> target'.LongCount();;
val it : int64 = 2147483648L

 LongCount でも扱えないようなシーケンスを扱っているのだとしたら、システムを見直した方がいいんじゃないかと思います。BigCount を自分で実装する?

F# で LINQ 入門 Max Min Sum Average

 集計関数は簡単なので一気に紹介してしまいます。Max は最大値、Min は最小値、Sum は合計値、Average は平均値を集計して返します。List モジュールにも同様のメソッドがありますが、F# のために実装された List モジュールは LINQ よりも型に対して厳しいチェックが行われます。下記の例では、List.average 関数を使用する場合、int list 型ではコンパイルエラーとなってしまいます。

open System.Linq

let target = [ 1..10 ]

let max = target.Max()
let min = target.Min()
let sum = target.Sum()
let ave = target.Average()

let max' = List.max target
let min' = List.min target
let sum' = List.sum target

// 型 'int' は 'DivideByInt' という演算子をサポートしていません
// let ave' = List.average target
let ave' = target |> List.map float |> List.average


 実行結果

val target : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
val max : int = 10
val min : int = 1
val sum : int = 55
val ave : float = 5.5
val max' : int = 10
val min' : int = 1
val sum' : int = 55
val ave' : float = 5.5

F# で LINQ 入門 Take TakeWhile

 Take はシーケンスの頭から指定した要素数のデータを取得します。

open System.Linq

let age = seq { 10..50 }

// なんとかきゅーぶ
age.Take(3)
|> Seq.iter (printfn "%d歳は小学生です。まったく、小学生は最高だぜ!")

// 刺されるのではないか
age.TakeWhile(fun a -> a < 24)
|> Seq.iter (printfn "%d日は通常の値段でケーキを販売しています。")

// Take と合わせて間を取得する
let jk = age.SkipWhile(fun a -> a < 16).TakeWhile(fun a -> a <= 18)
jk |> Seq.iter (printfn "マックで%d歳の女子高生がF#以下略")

 実行結果

10歳は小学生です。まったく、小学生は最高だぜ!
11歳は小学生です。まったく、小学生は最高だぜ!
12歳は小学生です。まったく、小学生は最高だぜ!
10日は通常の値段でケーキを販売しています。
11日は通常の値段でケーキを販売しています。
12日は通常の値段でケーキを販売しています。
13日は通常の値段でケーキを販売しています。
14日は通常の値段でケーキを販売しています。
15日は通常の値段でケーキを販売しています。
16日は通常の値段でケーキを販売しています。
17日は通常の値段でケーキを販売しています。
18日は通常の値段でケーキを販売しています。
19日は通常の値段でケーキを販売しています。
20日は通常の値段でケーキを販売しています。
21日は通常の値段でケーキを販売しています。
22日は通常の値段でケーキを販売しています。
23日は通常の値段でケーキを販売しています。
マックで16歳の女子高生がF#以下略
マックで17歳の女子高生がF#以下略
マックで18歳の女子高生がF#以下略

val age : seq<int>
val jk : seq<int>

 シーケンスの要素数を超える数を指定するとどうなるのっと

// 要素数をこえる場合
jk.Take(10)

 実行結果

val it : seq<int> = seq [16; 17; 18]