AssertMatchを用いてパターンマッチによるassertをパイプで繋いで実行する
2022/09/17にリモートでtokyo.ex#20が開催されました。
メインセッションのなかで@ymtszwさんによる「Further leveraging pattern matches in Elixir tests!」という発表があり、Elixirでのテストのテクニックが紹介されました。
資料も公開いただいています、ありがとうございます。
発表の中でパターンマッチによるassertionをパイプ演算子でイイ感じに繋げられるようにした「AssertMatch」なるOSSを開発されたとのことで、今回の記事ではAssertMatchをお試ししてみたいと思います。
前提知識
ExUnitのassertマクロでは ==
による比較だけでなく、パターンマッチによるアサーションが可能です。
test "assert by pattern match" do
attrs = %{a: 1, b: 2}
assert %{a: 1} = attrs
end
左辺と右辺を逆にするとただの変数へのbindingになってしまうため注意が必要です。
test "assert by pattern match" do
attrs = %{a: 1, b: 2}
# これだとテストにならない
assert attrs = %{a: 1}
end
最小構成でAssertMatchを試す
なんらかライブラリの挙動を試すときにはLivebookが便利です。
以下、動作検証をしたlivebookの結果のコピペです。動かした結果がそのままmarkdownになるのは最高ですね!
Mix.install([
{:assert_match, github: "siiibo/assert_match"}
])
* Getting assert_match (https://github.com/siiibo/assert_match.git)
remote: Enumerating objects: 85, done.
remote: Counting objects: 100% (85/85), done.
remote: Compressing objects: 100% (44/44), done.
remote: Total 85 (delta 31), reused 68 (delta 21), pack-reused 0
origin/HEAD set to main
==> assert_match
Compiling 1 file (.ex)
Generated assert_match app
:ok
バージョン
System.version()
"1.14.0"
テストコード
試しに構造体とリストのパターンマッチをassert_matchを利用して書いてみます。
利用方法はassert_match本体のテストコードを見ると分かりやすいです。
-
import AssertMatch
を実行 - あとは
assert_match/2
を使うだけ
という使い方のようです。
ExUnit.start()
defmodule User do
defstruct [:name, :age]
def build(attrs) do
%User{name: attrs.name, age: attrs.age}
end
end
defmodule SampleTest do
use ExUnit.Case
import AssertMatch
test "構造体のマッチ" do
attrs = %{name: "foo", age: 20}
# pipeで書くとこんな感じ
# IO.inspectなどと同様、pipeの入力をそのまま
# 出力するような設計にされており、pipeで繋げられる!
attrs
|> User.build()
|> assert_match(%{name: "foo"})
|> assert_match(%{age: 20})
end
test "リストのマッチ" do
1..10//2
|> Enum.to_list()
|> assert_match([1, 3, 5 | _])
end
end
ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures
Randomized with seed 718926
%{excluded: 0, failures: 0, skipped: 0, total: 2}
無事にテストが通っています🎉
まとめ
簡単ですがassert_matchについて紹介しました。こういったDSLはチームによって入れる・入れない分かれるところではありますが、これぐらい小さなツールであればそこまで学習コストなく使えるのではと思います。
今回の例だとミニマムすぎてありがたみが薄い例になってしまいましたが、@ymtszwさんの発表にもあったようにPlug.Conn周りのリクエストのテストなど、パイプで書けると中間の変数定義がなくなってスッキリするケースは多々あると思います。
おわりに
tokyo.exでは今後も定期的にイベントが開催される予定とのことですので、ぜひconnpassの登録なり、slackのworkspace->#tokyo-exチャンネルにjoinいただくなり、情報をウォッチしていただけたらと思います!
Discussion