Writing Tests
Index
Conventions
Since the figuring out page demonstrates that well laid out test files are the key to understanding how modules work, it is important to write tests so this can always be achieved.
The following sections will lay out how we can achieve this.
Make sure names from the test matches setup_all
ExUnit.start()
:ok
defmodule AnomaTest.LiveBook.Nam do
use ExUnit.Case
setup_all do
special = 3
[special: special]
end
test "this is acceptable", %{special: special} do
assert special == 3
end
test "this test is not acceptable", %{special: spec} do
assert spec == 3
end
end
{:module, AnomaTest.LiveBook.Nam, <<70, 79, 82, 49, 0, 0, 14, ...>>,
{:"test this test is not acceptable", 1}}
If this convention is not followed, then the user can not simply be copy and paste the lines to figure out how to use the module.
Write setup_all to not crash on reevaluation
defmodule AnomaTest.LiveBook.NoCrash do
use ExUnit.Case
setup_all do
name = :intent_example
unless Process.whereis(name) do
Anoma.Node.Intent.init(name)
end
[intent_pool: name]
end
end
warning: Anoma.Node.Intent.init/1 is undefined (module Anoma.Node.Intent is not available or is yet to be defined)
documentation/contributing/testing.livemd#cell:qbrtwwd53rvqgtpz:8: AnomaTest.LiveBook.NoCrash.__ex_unit_setup_all_0/1
{:module, AnomaTest.LiveBook.NoCrash, <<70, 79, 82, 49, 0, 0, 11, ...>>,
{:__ex_unit_setup_all_0, 1}}
- Here we check if the process is running. This way if it is already in IEX we simply don't disturb it but rename it to point to the correct one we wish to operate over.
- If we did not do this check the other commands may fail and IEX may not be trapped to continue.
mix test
will not catch this
Try to make tests idempotent
Let us demonstrate this point, by making a simple queue service.
defmodule Queue do
use GenServer
def init(_init) do
{:ok, :queue.new()}
end
def start_link(arg) do
GenServer.start_link(__MODULE__, arg, name: arg)
end
def reset(queue) do
GenServer.cast(queue, :reset)
end
def enqueue(queue, name) do
GenServer.cast(queue, {:enqueue, name})
end
def pop(queue) do
GenServer.call(queue, :pop)
end
def handle_cast(:reset, _pool) do
{:noreply, :queue.new()}
end
def handle_cast({:enqueue, val}, pool) do
{:noreply, :queue.cons(val, pool)}
end
def handle_call(:pop, _from, queue) do
{:reply, :queue.get_r(queue), :queue.drop_r(queue)}
end
end
{:module, Queue, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:handle_call, 3}}
defmodule AnomaTest.LiveBook.Idempotent do
use ExUnit.Case
setup_all do
name = :queue_name
unless Process.whereis(name) do
Queue.start_link(name)
end
[queue: name]
end
test "reset", %{queue: name} do
# Make sure we get reliable results!
Queue.reset(name)
Queue.enqueue(name, 5)
Queue.enqueue(name, 4)
assert 5 == Queue.pop(name)
end
end
{:module, AnomaTest.LiveBook.Idempotent, <<70, 79, 82, 49, 0, 0, 15, ...>>, {:"test reset", 1}}
Here before getting values from the queue, we make sure it's fresh by resetting it.
In the Queue
case it's contrived, however a lot of genservers in the codebase work like this!
Something important to note is that mix test
will not catch this!
So please try to keep tests isolated from each other.
Try to Name Values
For debugging purposes, it is best to name values, and so you can rerun values on command, or help the debugging process.