1行まとめ
- ExUnitにおけるテストケースの重複時にコンパイルエラーにしたい場合、
test_helper.exsにCode.compiler_options(warnings_as_errors: true)オプションを追加しておこう- 多分、これはチーム開発では大いに役立ちます
ExUnitが test マクロを関数定義するまで
以下のようなテストケースがあったとします。
defmodule ErrorSampleExunitTest do
use ExUnit.Case
test "the truth" do
assert 1 + 1 == 2
end
end
この時、 test マクロで定義されたところは __ErrorSampleExunitTest__.':test the truth' としてコンパイル時に定義されます。
Elixir1.2.1では以下の行での話になります。
...
quote bind_quoted: binding do
test = :"test #{message}"
ExUnit.Case.__on_definition__(__ENV__, test)
def unquote(test)(unquote(var)), do: unquote(contents)
end
...
同名の test が存在した場合
さて、この時に複数の同名のテストが存在した場合はどうでしょう?
例えば以下。
defmodule ErrorSampleExunitTest do
use ExUnit.Case
test "the truth" do
assert 1 + 1 == 2
end
test "the truth" do
assert 1 + 2 == 3
end
end
この場合、コンパイル時にCLIで以下のような warn が表示されます。
test/error_sample_exunit_test.exs:8: warning: this clause cannot match because a previous clause at line 4 always matches
テストケースを記述するコードは、 .exs の拡張子を持つので、このようなwarnはテスト実行毎に表示されます。なので、実行を注意深く見ていると、このエラーを知ることができます。
ただ、他のテスト自体は実行されます。
コンパイルエラーにする
ただ、warnではなくコンパイルエラーにしたほうが親切なのでは?と思い、手元でコンパイルエラーになるようにExUnitを直接修正してみました。
ExUnitのtestマクロでは、以下の2点でコンパイル時に関数を定義します。
- https://github.com/elixir-lang/elixir/blob/v1.2.1/lib/ex_unit/lib/ex_unit/case.ex#L258
- https://github.com/elixir-lang/elixir/blob/v1.2.1/lib/ex_unit/lib/ex_unit/case.ex#L280
なので、その直前で以下のように既存モジュールに定義済みの同名関数があるかどうかを検出させ、trueであればraiseするようにします。
if Module.defines?(__MODULE__, {test, 1}), do: raise("'#{test}' in #{__MODULE__} is already defined")
コンパイラオプションで回避する
ただ、これはコンパイルオプションだけで回避することができるみたいです。PR投げてみようと思った矢先、issueを見つけました。
これによると、 test_helper.exsに以下を追加することで、コンパイルを失敗させることができるそうです。
Code.compiler_options(warnings_as_errors: true)
確かにコンパイルエラーに。
$ mix test Compiled lib/error_sample_exunit.ex test/error_sample_exunit_test.exs:8: warning: this clause cannot match because a previous clause at line 4 always matches Compilation failed due to warnings while using the --warnings-as-errors option
小さなライブラリは別に良いのですが、チーム開発をしている時なんかはこのオプションは必要そうですね。
でも、このwarnは少し丁寧ではない気も…とはいえ、warnで止めていること自体は正しいと思いますし、ここは学びを得た、で止めておこうと思います。