テストが好きでたまらない皆さん、こんにちわ。17日目ですね。昨日はk1completeさんによるアナフォリックマクロを作ってみるでした。
今回はElixirのテストライブラリShouldIを紹介します。ShouldIを使うと、ネストしたテストが書けます。
導入
mix.exs
のdepsに以下を追加します。only: :test
をお忘れなく。
{:shouldi, "~> 0.2", only: :test}
そんでmix deps.get
しましょう。
使う
なんのモジュールか分かんないですが、これをテストすることにします。
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より年上であることを確認します。
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になるっぽいです。
with
でネスト、should
でテストケースを書きます。setup
に与えたdoブロックの戻り値は、テストケース内でcontext
経由で使えます(context.bob
やcontext.mary
)。上の階層のsetup
が順に呼ばれ結果が全てマージされるので、最後のshould
ではbob
もmary
も参照できます。お察しの通り、内部ではExUnitのsetup
が使われています。
実際にテストを走らせるには、今まで通り
$ mix test
です。test_helper.exs
はイジらなくて大丈夫です。
setup
の実行タイミング
先ほど、上の階層のsetup
が順に呼ばれると書きましたので、どのように実行されるのか見てみます。例えばこんなの。
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
各テストケースの実行順はランダムですが、
- 最上層の
setup
- 次の層の
setup
- ...
should
という順序は当然保証されます。setup
は何度も呼ばれるので、副作用のあることをする場合は注意が必要です。
どうしても最初に一度だけ実行したい処理を書きたい場合は、ShouldIに拘らずにExUnitの素の機能を使えばできます。
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に使えない文字は全部エラーです。
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で"test"を検索するといくつか出てきますが、良さ気なのはShouldIとamritaです。
こんな記事書いておいてアレですが、amritaの方がよく出来ています。ただ、どちらもダウンロード数がパッとしないですし、githubを見てても、みんなExUnitで満足しているようです。phoenixのテストも全部ExUnitです。RspecよりTest::Unitみたいな動きがあるのか、言語の性質的にExUnitで十分なのか、どうなんでしょ。
明日、18日目は@keithseahusです。