F#のテストフレームワーク「expecto」を使ってみる


みんな大好きテストの話です。

## F#とテスト

F#ではC#のテストフレームワークがそのまま使えます。
NUnitやxUnit、MsTest等ですね。

これらC#用テストフレームワーク以外に、F#で書かれたF#向けのテストフレームワークもあります。
例えば[FsUnit](http://fsprojects.github.io/FsUnit/)は上記C#用テストフレームワークのラッパーですが、よりF#っぽい文法でテストが書けるようにしてくれます。

今回はそれとは別の、[expecto](https://github.com/haf/expecto)を使ってF#の単体テストを書いてみましょう。

## expecto

expectoはF#で書かれた、F#用のテストフレームワークです（C#からも使えます。多分）。
"with APIs made for humans" とある通り、（機械ではなく）人間にとっての使いやすさを目指しているようです。

また、 "strong testing methodologies to everyone" とある通り、テストの並列・直列や同期・非同期のコントロール、fixtures、前処理と後処理のような補助を利用できる他、parametised testやproperty based testといったテスト手法をサポートしています。

その他、特徴として挙げられている点は「テストを単なる値として扱える」という部分です。
単なる値なので、テストそれ自体を代入したりリストにまとめたり、フィルタしたり組み合わせたりする事が容易です。

その他、負荷テスト用の機能やベンチマーカー等も付いていますが、今回は単体テストにだけ絞ります。

というわけで、使ってみましょう。

## 準備

NuGetで入ります。

## テストを書く

サンプルとして載っている小さなテストを見てみましょう。

```fsharp
open Expecto

let tests =
  test "A simple test" {
    let subject = "Hello World"
    Expect.equal subject "Hello World" "The strings should equal"
  }

[<EntryPoint>]
let main args =
  runTestsWithArgs defaultConfig args tests
```

### test

まず、 `tests` という変数をテストそのものに束縛しているのが見えます。これが、上記した「テストは単なる値」という事です。

テストですが、 `test` 関数を利用して生成しています。 `test` 関数は、文字列（テスト名）を取ってコンピュテーション式用のビルダークラスを返す関数です。コンピュテーション式の中で、Expectモジュールに定義されたテスト用関数を呼び出す事で、テストを組み立てる事ができます。
テスト用関数はテストする値と、テストに関するメッセージを引数に取ります。このメッセージはテストが失敗した場合に表示されます。

別にコンピュテーション式を使わずとも、単なる関数を使う事もできます。

```fsharp
open Expecto

let simpleTest =
  testCase "A simple test" <| fun () ->
    let expected = 4
    Expect.equal expected (2+2) "2+2 = 4"
```

その場合は `testCase` 関数を使います。

### 実行

テストの実行部分を見てみましょう。

```fsharp
[<EntryPoint>]
let main args =
  runTestsWithArgs defaultConfig args tests
```

メイン関数が定義されています。
expectoのテストは基本的に、NUnitやxUnitのように外部ツールにバイナリを見せるのではなく、テストコード自体を実行ファイルにコンパイルしてそれを実行する事で行います。
"No magic is involved here" という言の通り、テストも単なる関数の評価で行われます。runTestsWithArgsはテストコンフィグ、引数（並列・直列や表示形式の設定等）、そしてテストを取って、そのテストを実行し、結果をコンフィグや引数で指定された通りに出力する関数です。

このテストを `msbuild Sample.fsproj && bin/Debug/Sample.exe` のようにコンパイルして実行すると、コンソールにテスト結果が出力されます。 `--summary` オプションなどを付けると、個々のテストケースを表示してくれたりします。

## 応用的な使い方

テストの実行方法はわかりましたが、そのままでは少し使いづらいです。
テストを階層化してまとめたい場合、出力にその階層を表示して欲しいでしょう。
また、プロジェクト内にある全てのテストを変数に入れて管理するのは大変なので、他のテストフレームワークのように属性ベースでテストを指定したい事もあるでしょう。

どちらも可能です。

### テストのグルーピング

これもサンプルコードです。

```fsharp
let tests =
  testList "A test group" [
    test "one test" {
      Expect.equal (2+2) 4 "2+2"
    }

    test "another test that fails" {
      Expect.equal (3+3) 5 "3+3"
    }

    testAsync "this is an async test" {
      let! x = async { return 4 }
      Expect.equal x (2+2) "2+2"
    }
  ]
```

`testList` 関数を使う事で、テストを階層化・グループ化する事ができます。 `testList` 内に `testList` をネストする事も可能です。

### Tests属性

変数定義に属性を付ける事で、 `runTestsInAssembly` を実行した時、自動的にそのコードをテストしてくれます。

```fsharp
(* テストされる *)
[<Tests>]
let tests =
  test "A simple test" {
    let subject = "Hello World"
    Expect.equal subject "Hello World" "The strings should equal"
  }

(* こっちはテストされない *)
let notTests =
  test "Another test" {
    Expect.equal 1 2 "failed!"
  }

[<EntryPoint>]
let main args =
  runTestsInAssembly defaultConfig args
```

その他、fixturesやpending tests, focusing testsのような便利な機能がありますので、READMEを読んでみてください。

## property-based testing

関数型言語のテストと言えばproperty-based testingが有名です。
F#のproperty-based testing用フレームワークには[FsCheck](https://fscheck.github.io/FsCheck/)がありますが、expectoはこれに対応しており、統合して使う事ができます。

READMEからサンプルを引きます。

```fsharp
// ExpectoFsCheckモジュールは自動でopenされる
// コンフィグ用のレコード定義もExpecto名前空間に入っている
open Expecto

let config = { FsCheckConfig.defaultConfig with maxTest = 10000 }

let properties =
  testList "FsCheck samples" [
    testProperty "Addition is commutative" <| fun a b ->
      a + b = b + a

    testProperty "Reverse of reverse of a list is the original list" <|
      fun (xs:list<int>) -> List.rev (List.rev xs) = xs

    // コンフィグを上書きする事も可能
    testPropertyWithConfig config "Product is distributive over addition" <|
      fun a b c ->
        a * (b + c) = a * b + a * c
  ]

Tests.runTests defaultConfig properties
```

生成される値の範囲や、試行回数等の調整は、FsCheckのドキュメントも併せて参考にしてください。

## ツールとの連携

テストフレームワークはそれ単体の性能だけでなく、IDEやCIサービスなどとの連携も重要です。expectoはどうでしょうか。

### Visual Studio

[expecto-adapter](https://github.com/adamchester/expecto-adapter)は、Visual Studio testのexpecto用アダプタで、これを入れると、Visual Studioのテスト機能でexpectoを利用できるようになります。

が、残念な事にまだ完璧ではないようで、時々テスト結果を解析できなかったりするようです。

### AppVeyor

Windows環境でビルドやテストができる事で有名なCIサービスです。複数のテストフレームワークに対応していますが、残念ながらexpectoには対応していません。

「テストフレームワークに対応」というのは、結果をいい感じにパーズしてコンソールのTESTSタブに表示してくれる、という程度の意味です。
AppVeyorのテストコンテナ内では独自のスクリプトを走らせる事が可能なので、

```appveyor.yml
after_build:
  - ps: .\bin\Debug\Sample.exe --summary

test: off
```

のように設定すれば、expectoのテストをCIで実行する事は可能です。expectoはテストが失敗したら終了ステータスを1で終えるので、AppVeyorもこれを失敗と認識してくれて、きちんとテストする事は可能です。

その他のCIサービス、例えばtravis CI等でも、コマンドの終了ステータスが1の時はCI失敗扱いにしてくれるので、このあたりのCIサービスでも同じようにexpectoを使う事ができます。

## その他

monoでも、.NET Coreでも使えるようです。

## まとめ

それなりに使いやすい機能が揃っている、と感じるexpectoの紹介でした。
まだまだメジャーとは言えないフレームワークで、足りない機能もありますが、開発は活発で機能追加も続いているので、これからもっと使いやすくなっていくと思います。