LoginSignup
1
1

More than 5 years have passed since last update.

テストライブラリ ShouldI

Posted at

テストが好きでたまらない皆さん、こんにちわ。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.Caseuse ShouldIします。将来use ShouldIだけでOKになるっぽいです

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

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

  1. 最上層のsetup
  2. 次の層のsetup
  3. ...
  4. 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.ContextShouldI.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

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

注意: 日本語

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

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

まとめ

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

余談

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

明日、18日目は@keithseahusです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1