Getting a Good Grasp of F# (仮)

関数型言語F#をもっと楽しみたい

F#のコードをユニットテストで検証する その4(xUnit)

前回xUnit.net を利用したユニットテストの続きです。前回とは違うやり方で、ひとつのテスト関数に複数のテストデータを渡す方法を試します。

サンプルコード その1
  1. module Sample3
  2. open Xunit
  3.  
  4. let SampleData01 : seq<obj array> =  
  5.         seq [
  6.             [| "";      0 |]
  7.             [| "ab";    2 |]
  8.             [| "ABC";   3 |]
  9.             [| "ABCD";  4 |]
  10.             [| "12345"; 5 |]
  11.         ]
  12.  
  13. [<Theory>]
  14. [<MemberData("SampleData01")>]
  15. let test01(str: string, expected: int) : unit =
  16.     Assert.Equal(str.Length, expected)

4~11行目で5個のテストデータを定義しています。

4行目の変数 SampleData01 で定義した5つのテストデータ(6~10行目)がテスト実行時に、15行目のテスト関数 test01 に渡されます。

15行目のテスト関数 test01 には Theoryアトリビュートが付いています。さらにMemberDataアトリビュートを使ってテストデータ群が格納された変数 SampleData01 の識別子名を文字列として指定して関連付けています。

4行目の変数の型注釈は以下のように書いても同じ意味です。コンパイルも通ります。

  1. let SampleData01 : System.Collections.Generic.IEnumerable<System.Object[]> =  

 

Visual Studio のテストエクスプローラーを実行してみます。

f:id:pongitsune:20160215211834p:plain

 次に、個々のテストデータの型をコンパイル時にチェックできるスタイルに書き直してみます。

サンプルコード その2
  1. module Sample4
  2. open Xunit
  3.  
  4. let SampleData02 = new TheoryData<string, int>()
  5. SampleData02.Add ("",      0)
  6. SampleData02.Add ("ab",    2)
  7. SampleData02.Add ("ABC",   3)
  8. SampleData02.Add ("ABCD",  4)
  9. SampleData02.Add ("12345", 5)
  10.  
  11. [<Theory>]
  12. [<MemberData("SampleData02")>]
  13. let test02(str: string, expected: int) : unit =
  14.     Assert.Equal(str.Length, expected)

4~9行目で5個のテストデータを定義しています。変数 SampleData02 にテストデータ群が格納されます 。5~9行目にかけて TheoryData クラスのAddメソッドを使ってテストデータを1つずつ追加しています。

4行目の TheoryData<string, int> において、test02 に渡すデータの型が string 型とint 型のペアであることを明示的に指定し、コンパイル時の型チェックが行われるようにしてあります。

テストを実行すると「サンプルコード その1」と同様の結果が得られます。

f:id:pongitsune:20160215214059p:plain

 

F#のコードをユニットテストで検証する その3(xUnit)

前回と同じく Visual Studio 2013 のテストエクスプローラーから xUnit.net を使ったユニットテストを行います。今回は1つのテスト関数に複数のテストデータを渡す方法を試します。

xUnit.net を使ったコードの例
  1. module Sample2
  2. open Xunit
  3.  
  4. let fibonacci (nth: int) : int=
  5.     let rec auxFib (acc1: int) (acc0: int) (count: int) : int =
  6.         match count with
  7.         | 0 -> acc0
  8.         | 1 -> acc1
  9.         | _ -> auxFib (acc1 + acc0) acc1 (count - 1)
  10.     auxFib 1 0 nth
  11.  
  12. [<Theory>]
  13. [<InlineData( 0,  0)>]
  14. [<InlineData( 1,  1)>]
  15. [<InlineData( 2,  1)>]
  16. [<InlineData( 3,  2)>]
  17. [<InlineData( 4,  3)>]
  18. [<InlineData( 5,  5)>]
  19. [<InlineData( 6,  8)>]
  20. [<InlineData( 7,  13)>]
  21. [<InlineData( 8,  21)>]
  22. [<InlineData( 9,  34)>]
  23. [<InlineData(10,  55)>]
  24. [<InlineData(11,  89)>]
  25. [<InlineData(12,  144)>]
  26. [<InlineData(13,  233)>]
  27. [<InlineData(14,  377)>]
  28. [<InlineData(15,  610)>]
  29. let ``Fibonacci number``(nth: int, expected: int) : unit =
  30.         let actual = fibonacci nth
  31.         Assert.Equal(expected, actual)

 4行目の fibonacci というフィボナッチ数列の第 n 項(n は0以上の整数)を求める関数をユニットテストの対象とします。

12行目 Theory アトリビュートをテスト関数 ``Fibonacci number`` (29行目から始まる)に付加します。

Theory 以降に続く13~28行目の InlineData アトリビュートの引数で指定したテストデータが ``Fibonacci number`` の引数として渡ります。InlineData からデータを渡す回数分のテストが実行されます。ここでは16個のテストデータを置いたので16回のテストが実行されることになります。

 テストデータ用のフィボナッチ数の値は、サイト「THE ONーLINE ENCYCLOPEDIA OF INTEGER SEQUENCES® 」からお借りしました。

ユニットテストの実行結果

f:id:pongitsune:20160214214938p:plain

テスト関数の定義は一つですが16回のテストが実行されていることがわかります。

F#のコードをユニットテストで検証する その2(xUnit)

xUnit.net を利用してVisual Studio 2013のテストエクスプローラーでユニットテストを実行してみます。

その準備として以下の xUnit.net および xUnit.net テストランナーの NuGet パッケージをプロジェクトにインストールします。

 NuGet パッケージ管理画面からインストール

f:id:pongitsune:20160213214520p:plain

xUnit.net を利用したコードの例
  1. module Sample1
  2. open Xunit
  3.  
  4. let countWordsInStr (str: string) : int =
  5.     let clist: char list = [ for c in str -> c ] // stringをcharの配列に変換
  6.     let rec inWord (curList: char list) (count: int) : int =
  7.         match curList with
  8.         | [(**)] -> count
  9.         | hd::tl when hd = ' ' -> inSpace tl count
  10.         |  _::tl               -> inWord  tl count
  11.     and inSpace (curList: char list) (count: int) : int=
  12.         match curList with
  13.         | [(**)] -> count
  14.         | hd::tl when hd = ' ' -> inSpace tl count
  15.         |  _::tl               -> inWord  tl (count + 1) // 空白→文字の遷移でインクリメント
  16.     inSpace clist 0
  17.      
  18. [<Fact>]
  19. let ``Word Count - Part0``() =
  20.     Assert.Equal(0,  countWordsInStr " ")
  21.  
  22. [<Fact>]
  23. let ``Word Count - Part1``() =
  24.     Assert.Equal(1,  countWordsInStr " aaa")    
  25.  
  26. [<Fact>]
  27. let ``Word Count - Part2``() =
  28.     Assert.Equal(2,  countWordsInStr "aaa bbbb")    
  29.  
  30. [<Fact>]
  31. let ``Word Count - Part3``() =
  32.     Assert.Equal(3,  countWordsInStr "aaa bbbb ccccccc ")
  33.    
  34. [<Fact>]
  35. let ``Word Count - Part4``() =
  36.     Assert.Equal(4,  countWordsInStr "a b c d")

4行目の countWordsInStr がテスト対象となる関数です。引数で与えられた文字列に含まれている単語数を数えます。ここではホワイトスペースのみを区切り文字として認識するように書いています。

6~15行目はこの関数内で使用するローカル関数です。let rec...and...の構文使って相互再帰(mutual recursion)する関数を2つ定義しています。16行目でこのローカル関数を使ってcountWordsInStr の戻り値を作り、そのまま返しています。

18行目以降の5つの関数がテスト用関数の定義になります。Factアトリビュートを付加した関数がテストランナーによって実行されます。xUnitAssert.Equal メソッドで引数に渡された二つの値が等しいかを検査しています。

注)8行目・13行目にある(**)はF#用コメントです。はてなブログでは角括弧が消えてしまうことがあるのでその対策として入れてあります。

Visual Studio2013のテストエクスプローラーの実行結果

f:id:pongitsune:20160213214246p:plain

 

F#のコードをユニットテストで検証する その1

デフォルトのVisual Studio 2013の環境で、F#のコードのユニットテストを実行してみます。以下は普通のコンソールアプリケーションのプロジェクトに含まれているコードです。
Microsoft.VisualStudio.TestTools.UnitTesting名前空間を使うので、予め Microsoft.VisualStudio.QualityTools.UnitTestFramework への参照をプロジェクトに追加しておきます。

コードの例
  1. module Test01
  2. (* 参照の追加:Microsoft.VisualStudio.QualityTools.UnitTestFramework *)
  3. open Microsoft.VisualStudio.TestTools.UnitTesting
  4.  
  5. let rangeData = [1..10]
  6.  
  7. [<TestClass>]
  8. type ``A unit testing exercise``() =
  9.     let mutable myList = []
  10.  
  11.     [<TestInitialize>]  
  12.     member this.setup(): unit =
  13.         // テストの準備
  14.         myList <- rangeData
  15.  
  16.     [<TestMethod>]
  17.     member this.body() : unit =
  18.         let sum = List.fold (+) 0 myList  // リスト要素の総和を求める
  19.         Assert.AreEqual(55 , sum)
  20.  
  21.     [<TestCleanup>]  
  22.     member this.teardown(): unit =
  23.         // テストの後始末
  24.         ()

F#ではキーワード type を使ってclass宣言を行います。8行目の``A unit testing exercise``ユニットテストに用いるクラス名です。ユニットテストで使うことを明示するために TestClassアトリビュートを付加しておきます。クラス名をバッククォート記号「`」二つずつを前後に使って囲っているのは識別子名にホワイトスペースが含まれているからです。F#ではバッククォート記号で識別子名を囲むことによって識別子に使えない文字を混ぜることができます。

12行目のTestInitializeアトリビュートの付いたメソッドsetupはテスト実行前に必要な処理を書きます。F#ではアトリビュート識別子は [< ... >] で囲みます。
メソッドシグネチャは(unit -> unit)。

17行目のTestMethodアトリビュートの付いたメソッドbodyはテストで使われる本体部分です。
メソッドシグネチャは(unit -> unit)。ここではリストの要素(1から10までの整数)の総和を求めて55に等しいかを Assert.AreEqual メソッドを使って検査しています。

22行目のTestCleanupアトリビュートの付いたメソッドteardownはテスト完了後に必要な後処理を書きます。この例では特に何もしないので()を返しています。
メソッドシグネチャは(unit -> unit)。

テストを実行するために、メニューバーから「テスト」→「ウィンドウ」→「テストエクスプローラー」を選択してテスト画面を開きます。

f:id:pongitsune:20160210204115j:plain

テストクスプローラー上で「すべて実行」をクリックするとソースコードはビルドされユニットテストが実行されて結果が表示されます。以下はこのテストが成功した場面。

f:id:pongitsune:20160210204322j:plain

ここで先ほどのコード19行目の Assert.AreEqual を Assert.AreNotEqual に書き変えてわざとテストを失敗させてみます。再度テストを実行すると以下のような結果が得られます。

f:id:pongitsune:20160210204655j:plain

F#でなるべく簡単にグラフ表示をさせたい その4

今回はチャート・ライブラリ F# Chartingを利用してグラフを作成します。

VisualStudio 2013のプロジェクトにNugetを利用してライブラリをインストールするためにプロジェクトの「参照設定」から、右クリックメニュー項目「Nugetパッケージの管理」を選択。

f:id:pongitsune:20160203185959p:plain

 Nugetパッケージ管理の画面で、オンラインで「fsharp.charting」と文字列検索します。「FSharp.Charting」の項目が見つかったら「インストール」のボタンを押してインストール実行。

f:id:pongitsune:20160203190633p:plain

ライブラリを使って折れ線グラフを描くコードの例

プロジェクトの参照設定に、FSharp.Charting・System.Windows.Forms・System.Drawingが追加されていることを確認。

open System
open FSharp.Charting  
 
[<EntryPointSTAThread>]
let main _ = 
    let fscChart: FSharp.Charting.ChartTypes.GenericChart = 
         [ for x in 0.0.. 0.02 .. (Math.PI * 2.0) -> (x, Math.Sin x) ] 
         |> Chart.Line
 
    let chartCtrl: FSharp.Charting.ChartTypes.ChartControl = 
            new FSharp.Charting.ChartTypes.ChartControl(fscChart) 
    chartCtrl.Dock <- System.Windows.Forms.DockStyle.Fill
 
    let form = 
            new System.Windows.Forms.Form(Width = 700, Height = 400, 
                                            Text = "FSharp.Chartingのテスト")
    form.Controls.Add(chartCtrl)
       
    System.Windows.Forms.Application.Run(form)
    0 

 実行結果

Windowsアプリケーションとしてビルドして実行すると、以下のようなウィンドウが表示されます。

f:id:pongitsune:20160203193900p:plain

[2016-02-04 追記]

X軸方向のグリッド位置にハンパな値を割り振られてしまうので修正。

    let fscChart: FSharp.Charting.ChartTypes.GenericChart = 
         [ for x in 0.0.. 0.02 .. (Math.PI * 2.0) -> (x, Math.Sin x) ] 
         |> Chart.Line

 上記の部分に1行追加。

    let fscChart: FSharp.Charting.ChartTypes.GenericChart = 
         [ for x in 0.0.. 0.02 .. (Math.PI * 2.0) -> (x, Math.Sin x) ] 
         |> Chart.Line
         |> Chart.WithXAxis(Min = 0.0)

 ChartクラスのWithXAxisメソッドを呼び出してX軸方向の最小値を明示的に指定。この追加行において、クラスのメソッド引数のみで利用可能な「名前付き引数(Named Arguments)」及び「省略可能なパラメータ(Optional Arguments)」の文法が使われている。詳細はMSDN:パラメーターと引数 (F#)を参照のこと。

 実行結果

f:id:pongitsune:20160204211149p:plain