macroのテストを書く
macroのテストでは、通常は
- コンパイルされたモジュールの振る舞い
- コンパイルされた結果(AST)自体
の2つの側面をテスト対象と見てテストコードを書いていくことになります。
振る舞いに関しては、以下のような感じでExUnitを使います。こちらは、普通にmacroによって定義された関数を使って、期待するinput/outputを確認する、というものです。
ExUnit.start
Code.require_file "target.exs", __DIR__
defmodule TargetTest do
use ExUnit.Case
test "title" do
# do something
assert target, expected
end
end
コンパイル自体に対しては、以下のようにAST自体が正しくできているか?を確認することになります。他は↑とさほど変わりません。inputがASTの文字列、outputがassertionになります。
例えば、以下のような内容になります。
test "t/3 raises KeyError when bindings not provided" do
assert Sample.compile([]) |> Macro.to_string == String.strip ~S"""
(
def(t(locale, path, bindings \\ []))
[]
def(t(_locale, _path, _bindings)) do
{:error, :no_translation}
end
)
"""
end
少し話が逸れますが、Agentなどのテストにおいて、Processにsendされた値を確認したい時があります。つまり、mailboxの中身を確認したい。そんな時は以下のように __mailbox__ を使うことができます。
iex> send self, :sample_message iex> ExUnit.Assertions.__mailbox__(self()) ". Process mailbox:n:sample_message"
いつMacroを使うか?
- より簡潔にコードを記述できる場合
- コード生成が必要な場合
- 例えば、Phoenixのルーティングではコード生成によってGET/POST/PUT/PATCH/DELETEなどの
matchを定義しています。 - このような、少量のコードを書くことで、大量の汎用的なコードを生成する、というような場合、それら全てを手で書くよりも正しく、効率的に実行コードを生成できます
- 例えば、Phoenixのルーティングではコード生成によってGET/POST/PUT/PATCH/DELETEなどの
ただし、複雑になると理解が難しくなるので、metaprogramingで大事なことは、simpleであることです。
mix-inするとき、 import を使えるなら use は使わない
use を使うと、 __using__ を読み込みます。そのため、mix-inとしてimportのように使うことができます。ただ、macroをmix-inのためだけに使うのは止めましょう。単に複雑さを増すので。
- macroを使ったmix-in: NO
defmodule Sample do
defmacro __using__(_option) do
def something(do) do
# process
end
end
end
defmodle Neko do
use Sample
def inu do
something do
end
end
- importを使ったmix-in: OK
defmodule Sample do
def something do
# process
end
end
defmodle Neko do
import Sample
def inu do
something do
end
end
他、いろいろありましたが大事なところはここら辺かな。
ここまでの内容は、『Metaprogramming Elixir』を読んだ内容が主でした。 __using__ の使い所とか、テストフレームワークとか作るのに必要なメタプログラミングの知見を得られたかな、という感じで、良い学びでした。ついでに、ちょくちょくElixir関連のOSSに貢献できたので、それも良かったかな 🙂
関連
1 Comment