Elixirトラックでのテスト

ExercismでElixirの演習をテストする方法を学びましょう


ターミナルで演習のベースディレクトリに移動してから、次のコマンドでテストを実行します。

$ mix test

これで、testサブフォルダーにあるテストファイル(ファイル名の末尾が_test.exsのもの)が実行されます。

保留中のテスト

プラクティス演習のテストスイートでは、最初のテスト以外はすべてスキップされるようにタグ付けされています。

テストが1つ通ったら、該当する@tag :pendingを#記号でコメントアウトすると、次のテストのスキップを解除できます。

たとえば、次のようになります。

# @tag :pending
test "shouting" do
  assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end

すべてのテストを一度に実行したい場合は、mix testコマンドに--includeフラグを付けると、スキップされたテストをすべて含めて実行できます。

$ mix test --include pending

または、テストスイート内のExUnit.configureの行をコメントアウトすると、すべてのテストを有効にできます。

# ExUnit.configure exclude: :pending, trace: true

その他のElixirテスト機能

ExUnitとmix testには、テストをグループ化・タグ付け・実行する方法や、テストの実行を制御するさまざまな方法が数多く用意されています。その多くを以下にまとめます。

特定のテストを実行する方法

ドキュメント:

特定のファイルのテストを実行する

ファイルを指定すれば、1つのファイル内のすべてのテストをmix testで実行できます。

$ mix test test/<FILE>.exs

注意:この方法を使うと、taggingによって実際に実行されるテストが変わることがあります。

個別のテストを実行する

ファイル内のテストの行番号を指定すると、個々のテストを実行できます。

$ mix test test/<FILE>.exs:LINENUM

複数の行番号を:で区切って指定すると、複数のテストを実行できます。

たとえば、行番号付きで次の内容を持つファイルがあるとします。

test "Test 1" do           # 1
  # test implementation    # 2-6
end                        # 7
                           # 8
test "Test 2" do           # 9
  # test implementation    # 10-21
end                        # 22
                           # 23
test "Test 3" do           # 24
  # test implementation    # 25-35
end                        # 36

1番目と3番目のテストは、次のようにして実行できます。

$ mix test test/FILE.exs:1:24

注意:行番号でテストを指定した場合、taggingは無視されます。

テストのグループを実行する

テストはdescribeを使ってグループ化できます。

describe "short test group description" do
  test "test description" do
    # test implementation
  end

  test "another test description" do
    # test implementation
  end
end

グループ内のすべてのテストも、個々のテストを参照して実行するのと同じように、ファイル内のそのグループの行番号を指定して実行できます。

ドキュメント:

その他の便利なmix testオプション

  • --includeと--exclude:@tagに基づいて、特定のテストを実行したり実行しなかったりします
  • --failed:前回の実行で失敗したテストだけを実行します
  • --max-failures:テストの失敗がこの数に達すると、スイートはテストの評価を停止します
  • --seed:テストの順序をランダム化するために使われる乱数生成器のシードを設定します。--seed 0を指定するとランダム化が無効になり、1つのファイル内のテストは定義された順序で常に実行されます
  • --stale:前回--stale付きでテストを実行してから変更されたモジュールを参照しているテストだけを実行します
  • --only task_id:1:番号は別のものでもかまいません。学習演習では、特定のタスクに関連するテストだけを実行します

ドキュメント:

型仕様とDialyzer(DIscrepancy AnalYZer for ERlang programs)

Elixirの演習には、libサブディレクトリにスケルトンの実装ファイルが含まれています。このファイルには、実装が期待されるモジュールと関数の概要が示されています。ほとんどの演習では、関数宣言の上に型仕様があります。これらは@specタグで始まり、通常は@spec function_name(type1, type2) :: return_typeという形式に従います。これらはElixirとErlangでドキュメントとして使われるほか、Dialyzerというツールと組み合わせて、型の矛盾や起こりうるバグを見つけるのにも使われます。詳しくは型仕様のドキュメントをご覧ください。Dialyzerのドキュメントについては、Erlang -- dialyzerをご覧ください。

任意ですが、Dialyzerを使って自分の実装の型をチェックすることもできます。そのためにはいくつか手順が必要です。まず、自分の問題のmix.exsファイルにDialyxir依存関係を追加する必要があります。

defp deps do
  # Add this:
  [{:dialyxir, "~> 0.4", only: [:dev]}]
end

次に、コマンドラインからmixタスクを使って取得とコンパイルを行います。

$ mix deps.get
...
$ mix deps.compile
...

Dialyzerを初めて実行する場合、pltファイルはまだないはずです。永続ルックアップテーブル(PLT)は、DialyzerがElixirとErlangの組み込み型の情報をキャッシュするために使います。適切なデフォルトでpltを作成するには、次のコマンドを実行します。

$ mix dialyzer --plt

最後に、次のコマンドで実行できます。

$ mix dialyzer

パスは、自分のシステムにあるElixirライブラリのパスに置き換えてください。たとえば、HomebrewでElixirをインストールした場合、おそらく/usr/local/Cellar/elixir/1.3.2の下にあります。

繰り返しになりますが、Dialyzerを実行してすべての警告を解消するのは、演習を完了するうえで任意の手順です。Dialyzerの警告は、解読が難しいことがあります。たとえば、Bob演習のこのとてもおかしな実装に対する警告を見てみましょう。

defmodule Bob do
  @spec hey(input :: String.t()) :: String.t()
  def hey(input) do
    1
  end

  def hey(input) do
  end
end

これにより、次の警告が出力されます。

bob.exs:2: Invalid type specification for function 'Elixir.Bob':hey/1. The success typing is (_) -> 1
bob.exs:7: The variable _input@1 can never match since previous clauses completely covered the type any()

最初の警告は、関数が正しい型を返していないことを意味します。最後の警告は、最初の関数定義が常にマッチするため、2番目の関数定義には決して到達できないことを示しています。