Hello,
I am trying to reduce boilerplate stereotypical and rather brainless tests (that I still want to have in my library in case I make dumb mistakes down the road) that assert on fetching fields from a structure. The tests have this format:
test "batch_size" do
assert C.get(@valid_opts, :batch_size) == @valid_batch_size
assert C.get_batch_size(@valid_opts) == @valid_batch_size
end
The opportunity for code generation is there.
How can I, given a field name as an atom, generate the above code with macros (or even without)?
I am pretty bad at metaprogramming in Elixir still and will appreciate a pointer and an advice.
I wouldn’t use macros for the task as you have laid it out. I would do something like this:
@fields [
{:batch_size, :get_batch_size, @valid_batch_size}
]
test "getting fields" do
for {field, func_name, expected_result} <- @fields do
assert C.get(@valid_opts, field) == expected
assert apply(C, func_name, [@valid_opts]) == expected
end
end
You could also generate one test per field, but personally I wouldn’t do that because it makes the code generation more complicated (I believe you’d need unquote
) and they’re all effectively the same test anyway. Also instead of baking in @valid_batch_size
into @valid_opts
, I’d probably add a opts = Map.put(@valid_opts, expected)
as the first line. That way if you want you could test different behavior depending on the value of :batch_size
.
2 Likes
That’s a good way of doing it.
But assume for a minute that I’d like to only specify the field name as an atom and use meta-programming. How would you do it?
Module.get_attribute/2
, so in this case it can be:
for {name, {opts, size}} <- [batch_size: {:valid_opts, :valid_batch_size}] do
test name do
assert C.get(unquote(Module.get_attribute(__MODULE__, opts)), unquote(name)) == unquote(Module.get_attribute(__MODULE__, size))
assert apply(C, unquote(:"get_#{name}"), []) == unquote(Module.get_attribute(__MODULE__, size))
end
end
2 Likes
Likewise to you: any way to do this by only specifying a single atom per tested field?
I tried several combinations but always ended up with undefined function x/0
since I tried to use a local variable in an unquote
call, or getting messages that Module.get_attribute
cannot be called in certain contexts.
This worked (EDIT: forgot to add full code first time around):
[:batch_size, :db_name, :exec_timeout, :genserver_timeout]
|> Enum.each(fn(name) ->
test name do
expected = unquote(Module.get_attribute(__MODULE__, String.to_atom("valid_#{name}")))
assert C.get(@valid_opts, unquote(name)) == expected
assert apply(C, unquote(:"get_#{name}"), [@valid_opts]) == expected
end
end)
Thanks for the help, @axelson and @hauleth, you pointed me at the right direction.
1 Like