Elixir, ExUnitとESpecの違いを比較する

Elixirでmix newしたときには標準でExUnitのテストが実行できる状態でプロジェクトが作成されますが、
RubyとRSpscに慣れ親しんだ人にはRSpec風にテストを書きたいと思うこともあると思います。
今回はElixirのBDDフレームワーク、[ESpec](https://github.com/antonmi/espec)を使って
みます。

# ESpecの導入
まずは、mix.esxのdepsにespecを追加して、mix deps.getを行います。

```elixir:mix.esx
  def deps do
    [
      {:ex_doc, "~> 0.19", only: :dev, runtime: false},
      {:espec, "~> 1.6.3", only: [:dev, :test]}
    ]
  end
```

```
$ docker-compose run --rm web mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
  earmark 1.3.1
  ex_doc 0.19.2
  makeup 0.8.0
  makeup_elixir 0.13.0
  nimble_parsec 0.5.0
New:
  espec 1.6.3
  meck 0.8.12
* Getting espec (Hex package)
* Getting meck (Hex package)
```

mix deps.getsが終了したら、mix espec.initでespecに必要なファイルを作ります。

```
$ docker-compose run --rm web mix espec.init
Could not find "rebar3", which is needed to build dependency :meck
I can install a local copy which is just used by Mix
Shall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn] Y
* creating /root/.mix/rebar
* creating /root/.mix/rebar3
===> Compiling meck
==> espec
Compiling 99 files (.ex)
warning: System.stacktrace/0 outside of rescue/catch clauses is deprecated. If you want to support only Elixir v1.7+, you must access __STACKTRACE__ inside a rescue/catch. If you want to support earlier Elixir versions, move System.stacktrace/0 inside a rescue/catch
  lib/espec/example_runner.ex:254

Generated espec app
==> hello_world
* creating spec
* creating spec/spec_helper.exs
* creating spec/shared
* creating spec/shared/example_spec.exs
```

まだテストを記述していませんが、mix especでテストを実行して、動作することを確認します。

```
$ docker-compose run --rm web mix espec
Compiling 1 file (.ex)
Generated hello_world app


	0 examples, 0 failures

	Finished in 0.45 seconds (0.38s on load, 0.07s on specs)

	Randomized with seed 288292
```

# Specの追加と実行
Specファイルはspecディレクトリにxxx_spec.exsという形式で作成する必要があるので、
hello_world_spec.exsというファイルに以下の記述を行います。

```elixir:spec/hello_world_spec.exs
defmodule HelloWorldSpec do
  use ESpec
  it do: expect HelloWorld.hello() 
                |> to(eq :world)
  it do: expect HelloWorld.hello("dd511805") 
                |> to(eq "Hello, dd511805")
end
```
specファイルを作成したあとは再度mix specを実行して動作確認を行います。

```
$ docker-compose run --rm web mix espec
..

	2 examples, 0 failures

	Finished in 0.75 seconds (0.53s on load, 0.22s on specs)

	Randomized with seed 222464
```

これでESpecの記述は完了です。

# Specが失敗したときの挙動について
記述したSpecをわざと失敗させてExUnitのテストのときと得られる情報に差異があるか
確認してみます。

```
$ docker-compose run --rm web mix espec
F.

	1) HelloWorldSpec
	spec/hello_world_spec.exs:6: (inside example)
	spec/hello_world_spec.exs:5: (example)
	Expected `"Hello, dd511805"` to equal (==) `"Hello, dd"`, but it doesn't.
	  expected: "Hello, dd"
	  actual:   "Hello, dd511805"

	2 examples, 1 failures

	Finished in 0.94 seconds (0.66s on load, 0.28s on specs)

	Randomized with seed 237106
```
上記がespecの実行結果です。
そして下記が同じテストの失敗が発生するようにしたExUnitの結果になります。

```
$ docker-compose run --rm web mix test
..

  1) test greets Hello, name (HelloWorldTest)
     test/hello_world_test.exs:9
     Assertion with == failed
     code:  assert HelloWorld.hello("dd511805") == "Hello, dd"
     left:  "Hello, dd511805"
     right: "Hello, dd"
     stacktrace:
       test/hello_world_test.exs:10: (test)

.

Finished in 0.2 seconds
2 doctests, 2 tests, 1 failure

Randomized with seed 219476
```

どちらも同じような情報を得ることが出来ます。ExUnitだと stacktraceの情報も表示されているよう
ですが、今回の失敗のパターンでは、ここから有用な情報を得ることは出来ませんでした。

# Spec実行時のdoctestについて
ExUnitはmix newでプロジェクトを作成した段階のテストのサンプルでdoctestの設定が書かれていますが、
ESpecを使う場合でもExUnitと同様の記述を追加することでespec実行時にdoctestを行うことが出来ます。

```elixir:spec/hello_world_spec.exs
defmodule HelloWorldSpec do
  use ESpec
  doctest HelloWorld
  ...
end
```

上記の記述を追加して、mix especを実行するとdoctestも同時に実行します。

```
$ docker-compose run --rm web mix espec
Compiling 1 file (.ex)
....

	4 examples, 0 failures

	Finished in 1.05 seconds (0.83s on load, 0.21s on specs)

	Randomized with seed 88580
```

ExUnitの場合と違って、doctestとspecの数を別に集計してくれることはないようですが、
exampleの数が増えているので、doctestもspecと同時に実行されていることがわかります。