こんにちは!
プログラミング未経験文系出身、Elixirの国に迷い込んだ?!見習いアルケミストのaliceと申します。
今回はElixirでTDDの演習として、Phoenixでクエリパラメータを渡して足し算する方法をまとめます。
目的
TDDの演習としてPhoenixでクエリパラメータを引数a, bとし、 a + b の実行結果を画面に表示する実装をする。
実行環境
Windows 11 + WSL2 + Ubuntu 22.04
Elixir v1.14.3
Erlang v26.0.2
Phoenix v1.7.10
前提
本シリーズは下記で生成されたPhoenixのプロジェクトを使用しています。
mix phx.new dec19
cd dec19
mix ecto.create
事前準備
mix test.watchの導入
TDDではテストが通る/通らないを継続的に監視する必要があるので、まずmix test.watchを導入します。
詳しくは下記で記事化しています。
以降はmix test.watchの動作確認まで済んでいる前提で進めます。
ロジック部分の実装
テストを書く
前回の記事と同様にまず、テストを書きます。
@doc """
Get the result of adding and display.
## Examples
iex> Dec19.display(1, 2)
"add result: 3"
"""
end
(適当な実装を書いて)テストが落ちることを確認する
次に、a + bを実行するロジック部分の実装を適当に書きます。
@doc """
Get the result of adding and display.
## Examples
iex> Dec19.display(1, 2)
"add result: 3"
"""
+ def display(a, b) do
+ nil
+ end
Redの状態であることを確認します。
Running tests...
Compiling 1 file (.ex)
.....
1) doctest Dec19.display/2 (3) (Dec19Test)
test/dec19_test.exs:3
Doctest failed
doctest:
iex> Dec19.display(1, 2)
"add result: 3"
code: Dec19.display(1, 2) === "add result: 3"
left: nil
right: "add result: 3"
stacktrace:
lib/dec19.ex:20: Dec19 (module)
Finished in 0.08 seconds (0.03s async, 0.05s sync)
5 tests, 3 doctests, 1 failure
落ちたテストを通す最低限の実装をする
最後に、落ちたテストを通す最低限の実装をします。
@doc """
Get the result of adding and display.
## Examples
iex> Dec19.display(1, 2)
"add result: 3"
"""
def display(a, b) do
- nil
+ "add result: 3"
end
end
Greenの状態であることを確認します。1
Running tests...
Compiling 1 file (.ex)
......
Finished in 0.09 seconds (0.03s async, 0.06s sync)
5 tests, 3 doctests, 0 failures
ロジック部分のリファクタリング
テストを書く
こちらも前回の記事と同様にdoctestを増やしてみます。
@doc """
Get the result of adding and display.
## Examples
iex> Dec19.display(1, 2)
"add result: 3"
+ iex> Dec19.display(2, 3)
+ "add result: 5"
"""
def display(a, b) do
"add result: 3"
end
end
テストが落ちることを確認する
再びRedの状態であることを確認します。
Running tests...
Compiling 1 file (.ex)
.......
1) doctest Dec19.display/2 (4) (Dec19Test)
test/dec19_test.exs:3
Doctest failed
doctest:
iex> Dec19.display(2, 3)
"add result: 5"
code: Dec19.display(2, 3) === "add result: 5"
left: "add result: 3"
right: "add result: 5"
stacktrace:
lib/dec19.ex:23: Dec19 (module)
..
Finished in 0.08 seconds (0.03s async, 0.05s sync)
5 tests, 4 doctests, 1 failure
落ちたテストを通す最低限の実装をする
落ちたテストを通す最低限の実装をします。
このとき、他のテストが落ちないように初めてa + b
という変数を用いた実装になります。
defmodule Dec19 do
@doc """
Get the result of adding and display.
## Examples
iex> Dec19.display(1, 2)
"add result: 3"
iex> Dec19.display(2, 3)
"add result: 5"
"""
def display(a, b) do
- "add result: 3"
+ "add result: #{a + b}"
end
end
再び、Greenの状態であることを確認します。
Running tests...
Compiling 1 file (.ex)
.........
Finished in 0.08 seconds (0.03s async, 0.05s sync)
5 tests, 4 doctests, 0 failures
1つの実装で2つのテストを通すリファクタリングができました。
画面表示部分の実装
リテラルで引数a, bを渡す
home.html.heex内でロジック部分で実装した関数displayを呼び出します。
ひとまずリテラルで引数a, b
をそれぞれ1, 3
として渡します。2
+ <%= Dec19.display(1, 3)%>
<.flash_group flash={@flash} />
#後略
引数a, bを変数化する
下記の仕様を叶える実装を作っていきます。
- URLが
http://localhost:4000/?a=1&b=2
だとして、引数a, b
がそれぞれ1, 2
になるようにしたい。 - 画面には
add result: 3
が出てほしい。
paramsが使えるようにする
クエリパラメータ(params)を使えるように下記の通り書き換えます。
defmodule Dec19Web.PageController do
use Dec19Web, :controller
- def home(conn, _params) do
+ def home(conn, params) do
# The home page is often custom made,
# so skip the default app layout.
- render(conn, :home, layout: false)
+ render(conn, :home, layout: false, params: params)
end
end
heexファイルでparamsを使う
下記のように書き換えてparamsをロジック側に渡します。
- <%= Dec19.display(1, 3)%>
+ <%= Dec19.display(@params["a"], @params["b"])%>
<.flash_group flash={@flash} />
#後略
もともとあったテストが落ちたので修正
もともとあったpage_controller_test.exs
のテストがクエリパラメータが無い場合にnil + nilを足そうとして落ちました。
Running tests...
Compiling 1 file (.ex)
....
1) test GET / (Dec19Web.PageControllerTest)
test/dec19_web/controllers/page_controller_test.exs:4
** (ArithmeticError) bad argument in arithmetic expression: nil + nil
code: conn = get(conn, ~p"/")
stacktrace:
:erlang.+(nil, nil)
#後略
heexファイル側でクエリパラメータの有/無で条件分岐を書き、クエリパラメータが有るときだけparamsをロジック側に渡すようにします。
- <%= Dec19.display(@params["a"], @params["b"])%>
+ <%= if @params["a"] != nil && @params["b"] != nil, do: Dec19.display(@params["a"], @params["b"]), else: "" %>
<.flash_group flash={@flash} />
#後略
条件分岐を書いた結果、page_controller_test.exs
のテストは通るようになりましたがパラメータが文字列として渡されているようです。
** (exit) an exception was raised:
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+("1", "2")
文字列ではなく数値として渡すように修正します。
- <%= if @params["a"] != nil && @params["b"] != nil, do: Dec19.display(@params["a"], @params["b"]), else: "" %>
+ <%= if @params["a"] != nil && @params["b"] != nil, do: Dec19.display(String.to_integer(@params["a"]), String.to_integer(@params["b"])), else: "" %>
<.flash_group flash={@flash} />
#後略
エラーも出なくなり、かつテストも全件通るようになりました。
Running tests...
Compiling 1 file (.ex)
.........
Finished in 0.06 seconds (0.02s async, 0.04s sync)
5 tests, 4 doctests, 0 failures
画面側で動作確認
1件目http://localhost:4000/?a=1&b=2
の場合
[info] GET /
[debug] Processing with Dec19Web.PageController.home/2
Parameters: %{"a" => "1", "b" => "2"}
Pipelines: [:browser]
[info] Sent 200 in 2ms
2件目http://localhost:4000/?a=1&b=5
の場合(念の為)
[info] GET /
[debug] Processing with Dec19Web.PageController.home/2
Parameters: %{"a" => "1", "b" => "5"}
Pipelines: [:browser]
[info] Sent 200 in 2ms
コンソール上でもパラメータが渡されているのが確認でき、クエリパラメータに応じて画面の表示が変化することが確認できました(^▽^)/
リポジトリ
不明点
・引数a, bを変数化する背景(Phoenixのライフサイクルそのもの?)が分かっていない
・パラメータの渡し方(@params["a"]
といった書き方)が分かっていない
・paramsってクエリパラメータ以外にも使うことなかったっけ?
~Elixirの国のご案内~
↓Elixirって何ぞや?と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます
↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ!私もエンジニア未経験から学習中です。
↓We Are The Alchemists, my friends!3
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。4