この記事は、「Elixir Advent Calendar 2019」4日目の記事です。
きのうは @torifukukaiou さんの「12月3日なので、一二三、123ダーなElixirのこと」でした。
概要
開発言語 Elixir でテスト駆動開発をします。
テーマ
フィボナッチ数のプログラムを、テストを書きながら実装していきます。
# Fibonacci number : 前の2つの数を加えると次の数になる数列
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...
実行環境とライブラリ
- Elixir 1.16.0 (compiled with Erlang/OTP 26)
- ExUnit
- mix_test_watch
プロジェクト作成
今回、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_sample
、mix 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 に 失敗するテスト を書いていきます。
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 を、次のように書きます。
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 となっています。
こちらをもとに、成功するテストを書いていきます。
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 は省略)
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
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
ファイルに、以下コードを追記します。
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)
先ほどのつづきで、成功するテストコードを以下のように書いていきます。
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
自動テストが走り始めて、成功するテストが通りました。
このまま、あらたなテストコード → 本体コード、と実装していきます。
test "3rd number is expected 2" do
assert FibonacciSample.nth(3) == 1
end
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
テスト自動化の開発環境がととのいました。
リファクタリング
本体コード内の構文、
def nth(hoge) do
moge
end
は、def nth(hoge), do: moge
みたいにワンライナーで記述することもできます。
これに加えて、n
に任意の自然数が入ってきても可変で対応できるよう、本体コードをリファクタリングします。
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
テストコード
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」についてお話します!