Elixirでテスト駆動開発をゆるふわにやってみる（テスト自動化つき）

この記事は、[「Elixir Advent Calendar 2019」](https://qiita.com/advent-calendar/2019/elixir)4日目の記事です。
きのうは @torifukukaiou さんの[「12月3日なので、一二三、123ダーなElixirのこと」](https://qiita.com/torifukukaiou/items/8c37f9710e45b50b6aba)でした。

# 概要

開発言語 **[Elixir](https://elixir-lang.org/)** で**テスト駆動開発**をします。

## テーマ

フィボナッチ数のプログラムを、テストを書きながら実装していきます。

``` sh
# Fibonacci number : 前の２つの数を加えると次の数になる数列
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...
```

### 実行環境とライブラリ

- Elixir 1.16.0 (compiled with Erlang/OTP 26)
- [ExUnit](https://hexdocs.pm/ex_unit/1.16.0/ExUnit.html)
- [mix_test_watch](https://hex.pm/packages/mix_test_watch)

# プロジェクト作成

今回、`mix new fibonacci_sample` を実行して、
`fibonacci_sample` という名前のプロジェクトをつくっていきます。

``` sh

$ mix new fibonacci_sample

* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/fibonacci_sample.ex
* creating test
* creating test/test_helper.exs
* creating test/fibonacci_sample_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd fibonacci_sample
    mix test

Run "mix help" for more commands.

```

これでプロジェクトが作成されました。
画面の指示にしたがって、`cd fibonacci_sample`、`mix test` を実行します。

``` ｓｈ
$ cd fibonacci_sample
```

``` sh
$ mix test

Compiling 1 file (.ex)
Generated fibonacci_sample app
..

Finished in 0.7 seconds
1 doctest, 1 test, 0 failures
```

# テスト実装とコード

それでは、テストコードと本体コードを書いていきます。

はじめに、test/testfibonacci_sample_test.exs に __失敗するテスト__ を書いていきます。

``` Elixir:testfibonacci_sample_test.exs
defmodule FibonacciSampleTest do
  use ExUnit.Case
  doctest FibonacciSample

  # test "greets the world" do
    # assert FibonacciSample.hello() == :world
  # end
  # ここ↑ の部分を、次のように書きかえます。
  test "1st number is expected 1" do
    assert FibonacciSample.nth(1) == 2
  end

end
```

つづいて、lib/fibonacci_sample.ex を、次のように書きます。


``` Elixir:fibonacci_sample.ex
defmodule FibonacciSample do
  @moduledoc """
  Documentation for FibonacciSample.
  1, 1, 2, 3, 5, 8, ... , (n-2)+(n-1)
  """

  @doc """
  Fibonacci calculation test.

  ## Examples

      iex> FibonacciSample.nth(1)
      1

  """

  def nth(1) do
    1
  end

end

```

これで、最初のプログラムができました。

## テスト実行

では、さっそく `mix test` を実行してテストを走らせてみます。

``` sh
$ mix test

Compiling 1 file (.ex)


  1) test 1st number is expected 1 (FibonacciSampleTest)
     test/fibonacci_sample_test.exs:5
     Assertion with == failed
     code:  assert FibonacciSample.nth(1) == 2
     left:  1
     right: 2
     stacktrace:
       test/fibonacci_sample_test.exs:6: (test)

.

Finished in 0.2 seconds
1 doctest, 1 test, 1 failure
```

ぶじにテストが失敗しました！ 1 failure となっています。

こちらをもとに、成功するテストを書いていきます。

``` Elixir:testfibonacci_sample_test.exs
defmodule FibonacciSampleTest do
  use ExUnit.Case
  doctest FibonacciSample

  test "1st number is expected 1" do
    assert FibonacciSample.nth(1) == 1  # <- fix
  end

end
```

`mix test` します。

``` sh
$ mix test
..

Finished in 0.1 seconds
1 doctest, 1 test, 0 failures
```

今度はテストが通りました！ 0 failures になっています。

つづけて、次の __失敗するテスト__ と本体コードを追加していきます。(doctest は省略)

``` Elixir:fibonacci_sample.ex
defmodule FibonacciSample do
  @moduledoc """
  Documentation for FibonacciSample.
  1, 1, 2, 3, 5, 8, ... , (n-2)+(n-1)
  """

  @doc """
  Fibonacci calculation test.

  ## Examples

      iex> FibonacciSample.nth(1)
      1

  """

  def nth(1) do
    1
  end

  # add
  def nth(2) do
    1
  end

end
```

``` Elixir:testfibonacci_sample_test.exs
defmodule FibonacciSampleTest do
  use ExUnit.Case
  doctest FibonacciSample

  test "1st number is expected 1" do
    assert FibonacciSample.nth(1) == 1
  end

  # add
  test "2nd number is expected 1" do
    assert FibonacciSample.nth(2) == 2
  end

end
```


`mix test`します。

``` sh
$ mix test
.

  1) test 2nd number is expected 1 (FibonacciSampleTest)
     test/fibonacci_sample_test.exs:10
     Assertion with == failed
     code:  assert FibonacciSample.nth(2) == 2
     left:  1
     right: 2
     stacktrace:
       test/fibonacci_sample_test.exs:11: (test)

..

Finished in 0.2 seconds
1 doctest, 2 tests, 1 failure
```

順調に、テストが失敗しました！

これを受けて、つぎに成功するコードを書いていく、、のですが、つどつど `mix test` を手打ち実行するのは、そろそろめんどうになってきましたね。
**テスト自動化** の機運が高まってきました。

## テスト自動化

**[mix-test.watch](https://github.com/lpil/mix-test.watch)** モジュールを導入します。

プロジェクトのルートフォルダ直下 `mix.exs` ファイルに、以下コードを追記します。

``` Elixir:mix.exs
  def deps do
    # add
    {:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false}
  end
```

`mix deps.get`を叩いて、**mix-test.watch** モジュールを使えるようにします。

``` sh
$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
  file_system 0.2.10
  mix_test_watch 1.1.1
* Getting mix_test_watch (Hex package)
* Getting file_system (Hex package)
```

先ほどのつづきで、成功するテストコードを以下のように書いていきます。

``` Elixir:testfibonacci_sample_test.exs
defmodule FibonacciSampleTest do
  use ExUnit.Case
  doctest FibonacciSample

  test "1st number is expected 1" do
    assert FibonacciSample.nth(1) == 1
  end

  test "2nd number is expected 1" do
    assert FibonacciSample.nth(2) == 1  # <- fix
  end

end
```

それでは、`mix test.watch`で自動テストを走らせてみます。

``` sh
$ mix test.watch

==> file_system
Compiling 7 files (.ex)
Generated file_system app
==> mix_test_watch
Compiling 8 files (.ex)
Generated mix_test_watch app

Running tests...
Compiling 1 file (.ex)
Generated fibonacci_sample app



...

Finished in 0.1 seconds
1 doctest, 2 tests, 0 failures
```

自動テストが走り始めて、成功するテストが通りました。

このまま、あらたなテストコード → 本体コード、と実装していきます。


``` Elixir:testfibonacci_sample_test.exs
  test "3rd number is expected 2" do
    assert FibonacciSample.nth(3) == 1
  end
``` 

``` Elixir:fibonacci_sample.ex
  def nth(3) do
    2
  end
``` 

本体コードとテストコードの各ファイルを保存すると、テストが勝手に走ってくれるようになります。

``` sh
Running tests...
Compiling 1 file (.ex)
.

  1) test 3rd number is expected 2 (FibonacciSampleTest)
     test/fibonacci_sample_test.exs:10
     Assertion with == failed
     code:  assert FibonacciSample.nth(2) == 1
     left:  2
     right: 1
     stacktrace:
       test/fibonacci_sample_test.exs:12: (test)

.

Finished in 0.1 seconds
1 doctest, 2 tests, 1 failure
```

テスト自動化の開発環境がととのいました。

## リファクタリング

本体コード内の構文、

``` Elixir:fibonacci_sample.ex
  def nth(hoge) do
    moge
  end
```

は、`def nth(hoge), do: moge` みたいにワンライナーで記述することもできます。

これに加えて、`n` に任意の自然数が入ってきても可変で対応できるよう、本体コードをリファクタリングします。

``` Elixir:fibonacci_sample.ex
defmodule FibonacciSample do
  @moduledoc """
  Documentation for FibonacciSample.
  1, 1, 2, 3, 5, 8, ... , (n-2)+(n-1)
  """

  @doc """
  Fibonacci calculation test.

  ## Examples

      iex> FibonacciSample.nth(1)
      1

  """
  def nth(n) when n <= 0, do: 0

  def nth(1), do: 1

  def nth(n) do
    nth(n - 1) + nth(n - 2)
  end
end
```

### テストコード

``` Elixir:testfibonacci_sample_test.exs
defmodule FibonacciSampleTest do
  use ExUnit.Case
  doctest FibonacciSample

  test "1st number is expected 1" do
    assert FibonacciSample.nth(1) == 1
  end

  test "2nd number is expected 1" do
    assert FibonacciSample.nth(2) == 1
  end

  test "3rd number is expected 2" do
    assert FibonacciSample.nth(3) == 2
  end

  test "4th number is expected 3" do
    assert FibonacciSample.nth(4) == 3
  end

  test "10th number is expected 55" do
    assert FibonacciSample.nth(10) == 55
  end

  test "confirm function by refute" do
    refute FibonacciSample.nth(10) == 1
  end
end

``` 

上記コードの最後の個所 `refume` は、結果が `False` なら成功、結果が `True` なら失敗を返します。
（つまり `assert` の逆）

``` sh
Running tests...
.......

Finished in 0.2 seconds
1 doctest, 6 tests, 0 failures
```

テストも通っています！

## Next Action

- 入力値のバリデーション
  - `n` に自然数ではない値が入ってきたらどうするか
- doctest 実装
- `@spec` 追記
- 計算量の考慮（Big-O）

に取り掛かっていくと良さそうです。

# おわりに

本記事では、ゆるふわにテスト駆動開発をやってみるがお題なので、ここまでとします。

明日の[「Elixir Advent Calendar 2019」5日目の記事](https://qiita.com/sym_num/items/e41e85447a9de61bf6b8) は、@sym_num さんです。お楽しみに！

### kokura.ex

北九州小倉のElixirコミュニティ __kokura.ex__ は、福岡市で2017年から人気を博しているElixirコミュニティ「fukuoka.ex」が小倉にブランチした、新生Elixirコミュニティです。

「fukuoka.ex」同様、「高速処理性能」と「高い開発効率性」を両立できるプログラミング言語 __Elixir__ と、そのWebアプリケーションフレームワーク __Phoenix__ を北九州で広め、ワイワイと盛り上げていくコミュニティです。

これから北九州・小倉で先端技術をやりたい方や、最新のプログラミングを学びたい方、未来に向けてITに強くなりたい！方など、技術への興味レベルが高い方や、プログラミングに関心が高い方のご参加を歓迎します！
ご遠方の方でも、Zoom + slackでリモート参加も可能ですのでぜひどうぞ。

イベントページ(connpass) ・・・ **[fukuoka.ex](https://fukuokaex.connpass.com/)**

### fukuoka.ex アドベントカレンダー

[「fukuoka.ex Elixir／Phoenix Advent Calendar 2019」](https://qiita.com/advent-calendar/2019/fukuokaex)もありますので、ぜひどうぞ。

12/18の枠に、自分もkokura.exのお話をテーマに執筆参加してますので、こちらもよろしかったらご覧ください。
・[プログラミング言語Elixirの北九州コミュニティ「kokura.ex」についてお話します！](https://qiita.com/im_miolab/items/ded11bfee9a6cc0cf5fe#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB/)