Elixir でのコードを書くにあたり、テスティングフレームワーク ExUnit および ESpec を調べてみました。
ExUnit は Ruby で言うところの Test::Unit で、ESpec は RSpec のようです。調べてみた結果、自分は ExUnit を使うことにしたのですが、せっかくなので調べたことをまとめておきます。
比較表
ExUnit | ESpec | |
---|---|---|
インストール方法 | Elixir, Phoenix Framework 標準 | mix で追加する(espec, espec_phoenix) |
テストの書き方 | 主に assert で真偽チェック | expect や should で文章のように書ける |
テストのグループ化 | describe が使える(Elixir 1.3 以降) | context, describe, example_group が使える |
実行方法 | mix test |
mix espec |
テスト結果 | 失敗した行番号が含まれる | 失敗した行番号が含まれない |
テスト名の制約 | 日本語を使えない | 日本語を使える |
以下は、この比較表の詳細です。
ExUnit
インストール方法
Elixir の標準に含まれており、mix.exs に何も足さなくても使えます。
また、Phoenix Framework では mix phoenix.new
を実行する際に、データベース接続のためのヘルパーや CaseTemplate が自動生成されます。これにより、ExUnit のテストケースを使ったモデルのテストが可能になっています。
テストの書き方
主に assert または refute での真偽チェックとして書く必要があります。
defmodule ElixirSampleTest do use ExUnit.Case doctest ElixirSample test "the truth" do assert 1 + 1 == 2 end end
標準出力をテストするための ExUnit.CaptureIO やログ出力をテストするための ExUnit.CaptureLog といった便利機能もあります。これらを使う場合も、最終的には assert を呼ぶ必要があります。以下は CaptureLog の使用例です。
test "example" do assert capture_log(fn -> Logger.error "log msg" end) =~ "log msg" end
テストのグループ化
Elixir 1.3 から describe マクロが追加されて、テストのグループ化が簡単になりました。RSpec の describe と同じようにグループ化できます。
以下は ExUnit.Case のドキュメント に記載された例です。
defmodule StringTest do use ExUnit.Case, async: true describe "String.capitalize/1" do test "first grapheme is in uppercase" do assert String.capitalize("hello") == "Hello" end test "converts remaining graphemes to lowercase" do assert String.capitalize("HELLO") == "Hello" end end end
実行方法
mix test
で実行します。
$ mix test .. Finished in 0.05 seconds 2 tests, 0 failures Randomized with seed 37255
テスト結果
テストに失敗した場合、失敗したテストの開始行と、assertion に失敗した行の番号を表示します。
例えば、以下のように絶対失敗するテストを書いたとします。
defmodule ElixirSampleTest do use ExUnit.Case doctest ElixirSample test "the truth" do assert 1 + 1 == 2 assert 1 + 1 != 2 end end
実行結果はこうなります。
$ mix test . 1) test the truth (ElixirSampleTest) test/elixir_sample_test.exs:5 Assertion with != failed, both sides are exactly equal code: 1 + 1 != 2 left: 2 stacktrace: test/elixir_sample_test.exs:7: (test) Finished in 0.04 seconds 2 tests, 1 failure Randomized with seed 648187
テスト名の制約
test にも describe にも、日本語名(正確には、アトムに変換できない文字列)を使えないようです。例えば、以下のようにテストを書いたとします。
test "1足す1が2に一致する" do assert 1 + 1 == 2 end
これを実行すると、以下のようなエラーが返されます。
$ mix test ** (ArgumentError) argument error :erlang.binary_to_atom("test 1足す1が2に一致する", :utf8) (ex_unit) lib/ex_unit/case.ex:411: ExUnit.Case.register_test/4 test/elixir_sample_test.exs:5: (module) (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6 (elixir) lib/code.ex:370: Code.require_file/2 (elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5
BDD ではテストメソッド名に日本語を使おう、という意見があったりしますが、ExUnit では難しいようです。
ExUnit の参考ページ
- ExUnit – ExUnit
- ExUnit.Case – ExUnit
- ExUnit入門 - Qiita
- Programming Phoenix にもテストの章があり、参考にしました
Programming Phoenix: Productive, Reliable, Fast
- 作者: Chris Mccord,Bruce Tate,Jose Valim
- 出版社/メーカー: Pragmatic Bookshelf
- 発売日: 2016/04/30
- メディア: ペーパーバック
- この商品を含むブログを見る
ESpec
インストール方法
Elixir 標準ではないので、antonmi/espec のページにあるように mix.exs に追加する必要があります。
def deps do ... {:espec, "~> 1.3.0", only: :test}, ... end
あとはいつもの mix deps.get
でインストールしたあとで、以下のコマンドを実行して spec/spec_helper.exs を生成すれば準備 OK です。
$ MIX_ENV=test mix espec.init
Phoenix Framework で ESpec を使う場合は、mix.exs に espec_phoenix を追加します。これにより、モデルによるデータベース接続を伴うテストなどが可能になります。
テストの書き方
expect … to の組み合わせか、should を使って書きます。RSpec だと今は should は推奨されていないようですが、ESpec についてはそういう記載は見当たりませんでした。
defmodule ContextSpec do use ESpec example_group do context "Some context" do it do: expect "abc" |> to(match ~r/b/) end describe "Some another context with opts", focus: true do it do: 5 |> should(be_between 4, 6) end end end
テストのグループ化
context、describe、example_group が使えます。RSpec に似ていますね。
antonmi/espec に載っている例(下記)では example_group に説明文が付いていないですが、付けることも可能なようです。
defmodule ContextSpec do use ESpec example_group do context "Some context" do it do: expect "abc" |> to(match ~r/b/) end describe "Some another context with opts", focus: true do it do: 5 |> should(be_between 4, 6) end end end
実行方法
MIX_ENV=test mix espec
で実行します。ただ、antonmi/espec に従って mix.exs に設定を追加すれば、MIX_ENV=test
は省略できるようになります。
$ mix espec . 1 examples, 0 failures Finished in 0.07 seconds (0.06s on load, 0.01s on specs) Randomized with seed 654062
テスト結果
テストに失敗した場合、失敗したテストの開始行を表示します。 assertion に失敗した行の番号は表示してくれません。
ExUnit の場合と同じように、絶対失敗するテストを書いてみます。
defmodule ElixirSampleSpec do use ESpec it "the truth" do expect(1 + 1) |> to(be 2) expect(1 + 1) |> to_not(be 2) end end
すると、実行結果はこうなります。test の開始行の番号(4行目)しか出てきません。
$ mix espec F 1) ElixirSampleSpec the truth /Users/myoshiz/devel/elixir_sample/spec/elixir_sample_spec.exs:4 Expected `2` not to equals (==) `2`, but it does. 1 examples, 1 failures Finished in 0.11 seconds (0.1s on load, 0.01s on specs) Randomized with seed 728628
これくらいの内容なら、テスト結果からどの行かわかりますが、テストあたりの行数が長くなると辛くなってきます。長いテストコードは書くべきでない、という設計思想なんでしょうか。
コマンドライン引数などで、この動作を変更できるのではないかと思ったのですが、方法を見つけられませんでした……。これ、個人的には致命的に不便だと思うんですけど、他の人はどうなんですかね。
テスト名の制約
ESpec の it や describe は、テスト名に日本語を含めても、問題なく動作しました。
ESpec の参考ページ
- antonmi/espec: Elixir Behaviour Driven Development
- antonmi/espec_phoenix: ESpec for Phoenix web framework.
- Elixir + Phoenix + ESpecでBDDするまで - Qiita
まとめ
ESpec で書いたテストのほうが読みやすくなるのですが、総合的に考えて、個人的には今後は ExUnit を使うことにしました。
ExUnit は標準で採用されているために導入の手間が少なく、グループ化などの基本的な機能は備えているので、十分かなと。テスト失敗したときに行番号が表示されるなら、ESpec でも良かったんですけどね……。Elixir の練習のために admiral_stats_parser を admiral_stats_parser_ex に移植していて、作業が終わりかけたところでこれに気付いたときは愕然としました。
ちなみに、Elixir には、Ruby における Cucumber 相当の “WhiteBread” や、FactoryGirl 相当の “ExMachina” もあるようです。そのうち、これらも試してみたいと思います。