26
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2019-12-03

この記事は、「Elixir Advent Calendar 2019」4日目の記事です。
きのうは @torifukukaiou さんの「12月3日なので、一二三、123ダーなElixirのこと」でした。

概要

開発言語 Elixirテスト駆動開発をします。

テーマ

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

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

実行環境とライブラリ

プロジェクト作成

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


$ 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_samplemix test を実行します。

$ cd fibonacci_sample
$ 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 に 失敗するテスト を書いていきます。

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 を、次のように書きます。

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 を実行してテストを走らせてみます。

$ 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 となっています。

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

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 します。

$ mix test
..

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

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

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

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
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します。

$ 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 モジュールを導入します。

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

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

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

$ 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)

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

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で自動テストを走らせてみます。

$ 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

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

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

testfibonacci_sample_test.exs
  test "3rd number is expected 2" do
    assert FibonacciSample.nth(3) == 1
  end
fibonacci_sample.ex
  def nth(3) do
    2
  end

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

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

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

リファクタリング

本体コード内の構文、

fibonacci_sample.ex
  def nth(hoge) do
    moge
  end

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

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

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

テストコード

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 の逆)

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日目の記事 は、@sym_num さんです。お楽しみに!

kokura.ex

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

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

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

イベントページ(connpass) ・・・ fukuoka.ex

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

「fukuoka.ex Elixir/Phoenix Advent Calendar 2019」もありますので、ぜひどうぞ。

12/18の枠に、自分もkokura.exのお話をテーマに執筆参加してますので、こちらもよろしかったらご覧ください。
プログラミング言語Elixirの北九州コミュニティ「kokura.ex」についてお話します!

26
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?