テストライブラリ ShouldI

テストが好きでたまらない皆さん、こんにちわ。17日目ですね。昨日はk1completeさんによる[アナフォリックマクロを作ってみる](http://d.hatena.ne.jp/k-1/20141216/p1)でした。

今回はElixirのテストライブラリ[ShouldI](https://github.com/batate/shouldi)を紹介します。ShouldIを使うと、ネストしたテストが書けます。

## 導入

`mix.exs`のdepsに以下を追加します。`only: :test`をお忘れなく。

```ex
{:shouldi, "~> 0.2", only: :test}
```

そんで`mix deps.get`しましょう。

## 使う

なんのモジュールか分かんないですが、これをテストすることにします。

```ex
defmodule Member do
  defstruct [:name, :age]

  def bob,  do: %__MODULE__{name: "Bob",  age: 22}
  def mary, do: %__MODULE__{name: "Mary", age: 28}
end
```

`Member.bob`で返ってくるメンバーの名前が`"Bob"`であることと、MaryはBobより年上であることを確認します。

```ex
defmodule ExampleTest do
  use ExUnit.Case
  use ShouldI

  with "bob" do
    setup context do
      [bob: Member.bob]
    end

    should "be Bob", context do
      assert context.bob.name == "Bob"
    end

    with "mary" do
      setup context do
        [mary: Member.mary]
      end

      should "be older than bob", context do
        assert context.mary.age > context.bob.age
      end
    end
  end
end
```

まずは`use ExUnit.Case`と`use ShouldI`します。[将来`use ShouldI`だけでOKになるっぽいです](https://github.com/batate/shouldi/issues/7)。

`with`でネスト、`should`でテストケースを書きます。`setup`に与えたdoブロックの戻り値は、テストケース内で`context`経由で使えます(`context.bob`や`context.mary`)。上の階層の`setup`が順に呼ばれ結果が全てマージされるので、最後の`should`では`bob`も`mary`も参照できます。お察しの通り、内部ではExUnitの`setup`が使われています。

実際にテストを走らせるには、今まで通り

```bash
$ mix test
```

です。`test_helper.exs`はイジらなくて大丈夫です。

## `setup`の実行タイミング

先ほど、上の階層の`setup`が順に呼ばれると書きましたので、どのように実行されるのか見てみます。例えばこんなの。

```ex
with "with 1" do
  setup context do
    IO.puts "setup 1"
    []
  end

  should "should 1" do
    IO.puts "should 1"
  end

  with "with 2" do
    setup context do
      IO.puts "setup 2"
      []
    end

    should "should 2-1" do
      IO.puts "should 2-1"
    end

    should "should 2-2" do
      IO.puts "should 2-2"
    end
  end
end
```

テストを走らせると、こんな感じの出力がされます。

```
setup 1
setup 2
should 2-1
setup 1
should 1
setup 1
setup 2
should 2-2
```

各テストケースの実行順はランダムですが、

1. 最上層の`setup`
2. 次の層の`setup`
3. ...
4. `should`

という順序は当然保証されます。`setup`は何度も呼ばれるので、副作用のあることをする場合は注意が必要です。

どうしても最初に一度だけ実行したい処理を書きたい場合は、ShouldIに拘らずにExUnitの素の機能を使えばできます。

```ex
setup_all do
  {:ok, [one: 1]}
end

with "setup_all" do
  should "have context.one", context do
    assert context.one == 1
  end
end
```

ただしこの場合、`with`の中にさらに`setup_all`を入れるのはNGです。想定通りに動きません。ぜーんぶまとめて最初に実行されてしまいます。この挙動はExUnit単体で使ったときも同様です。

## Matchers

ShouldIには`ShouldI.Matchers.Context`と`ShouldI.Matchers.Plug`というモジュールが用意されています。

### Matchers.Context

`ShouldI.Matchers.Context`モジュールには`setup`で作られてきた`context`をテストするマクロが提供されています。

|まっちゃ                          |中身                                  |
|:--------------------------------|:------------------------------------|
|`should_assign_key(key: "value")`|`assert context(:key) == "value"`    |
|`should_match_key(key: {:ok, _})`|`assert {:ok, _} = context(:key)`    |
|`should_have_key(:key)`          |`assert Dict.has_key?(context, :key)`|
|`should_not_have_key(:key)`      |`refute Dict.has_key?(context, :key)`|

ワタクシのやり方が悪いんだと思うんですけど、**どんなテストも通ってしまう**ので、誰か正しい使い方と使い所を教えて下さい。

### Matchers.Plug

`ShouldI.Matchers.Plug`モジュールはplugのテストに特化したマッチャーを提供してくれます。
`context.connection`にテスト対象の`%Plug.Conn{}`があることが前提になっています。

|マッチャ|中身|
|:-----------------------------------|:----------------------------------------|
|`should_respond_with(:success)`     |`assert context.connection.status in 200..299`|
|`should_respond_with(:redirect)`    |`assert context.connection.status in 300.399`|
|`should_respond_with(:bad_request)` |`assert context.connection.status == 400 `|
|`should_respond_with(:unauthorized)`|`assert context.connection.status == 401 `|
|`should_respond_with(:missing)`     |`assert context.connection.status == 404`|
|`should_respond_with(:error)`       |`assert context.connection.status in 500..599`|
|`should_match_body_to("a-z+")`      |`assert context.connection.resp_body =~ ~r"a-z+"`|

いやーなんかこれも、ワタクシのやり方が悪いんだと思うんですけど、**エラーメッセージが全部`multiple matcher errors`**になってしまい、テストがどう落ちてるのか分からないので、誰か正しい使い方教えてください。

## `defmatcher`

マッチャーを自分で定義することもできるらしいのですが、`Context`も`Plug`もイケてないので、自分で作ってもあんまりイケてる感じにはならないと思います。それより`TestHelper`モジュールとか作ったほうがいいと思います。そのほうが読みやすいです。


## 注意: 日本語

`with`や`should`の引数に日本語は使えません。日本語に限らず、atomに使えない文字は全部エラーです。

```ex
with "ボブ" do
  should "彼の名はBobである", context do
    ...
  end
end
#=> (ArgumentError) argument error
#   :erlang.binary_to_atom("test with 'ボブ': should 彼の名はBobである", :utf8)
```

## まとめ

* ElixirのテストライブラリShouldIを紹介しました。
* `with`と`should`によるネスト構文と`setup`だけを利用するのが(今のところ)ベストだと思います。
* マッチャーは今後の開発に期待です。

## 余談

[hex.pm](https://hex.pm)で"test"を検索するといくつか出てきますが、良さ気なのは[ShouldI](https://hex.pm/packages/shouldi)と[amrita](https://hex.pm/oackages/amrita)です。
こんな記事書いておいてアレですが、amritaの方がよく出来ています。ただ、どちらもダウンロード数がパッとしないですし、githubを見てても、みんなExUnitで満足しているようです。[phoenix](https://github.com/phoenixframework/phoenix)のテストも全部ExUnitです。RspecよりTest::Unitみたいな動きがあるのか、言語の性質的にExUnitで十分なのか、どうなんでしょ。

明日、18日目は[@keithseahus](http://qiita.com/keithseahus)です。