Current section
Files
Jump to
Current section
Files
lib/prove.ex
defmodule Prove do
@moduledoc """
Prove provides the macros `prove` and `batch` to write simple tests shorter.
A `prove` is just helpful for elementary tests. Prove generates one test with
one assert for every `prove`.
## Example
```elixir
defmodule NumTest do
use ExUnit.Case
import Prove
defmodule Num do
def check(0), do: :zero
def check(x) when is_integer(x) do
case rem(x, 2) do
0 -> :even
1 -> :odd
end
end
def check(_), do: :error
end
describe "check/1" do
prove Num.check(0) == :zero
batch "returns :odd or :even" do
prove Num.check(1) == :odd
prove Num.check(2) == :even
prove "for big num", Num.check(2_000) == :even
end
batch "returns :error" do
prove Num.check("1") == :error
prove Num.check(nil) == :error
end
end
end
```
The example above generates the following tests:
```shell
$> mix test --trace test/num_test.exs --seed 0
NumTest [test/num_test.exs]
* test check/1 Num.check(0) == :zero (0.00ms) [L#20]
* test check/1 returns :odd or :even Num.check(1) == :odd (0.00ms) [L#22]
* test check/1 returns :odd or :even Num.check(2) == :even (0.00ms) [L#22]
* test check/1 returns :odd or :even for big num Num.check(2000) == :even (0.00ms) [L#22]
* test check/1 returns :error Num.check("1") == :error (0.00ms) [L#28]
* test check/1 returns :error Num.check(nil) == :error (0.00ms) [L#28]
Finished in 0.05 seconds (0.00s async, 0.05s sync)
6 tests, 0 failures
Randomized with seed 0
```
The benefit of `prove` is that tests with multiple asserts can be avoided.
The example above with regular `test`s:
```elixir
...
describe "check/1" do
test "returns :zero" do
assert Num.check(0) == :zero
end
test "returns :odd od :even" do
assert Num.check(1) == :odd
assert Num.check(2) == :even
assert "for big num", Num.check(2_000) == :even
end
test "returns :error" do
assert Num.check("1") == :error
assert Num.check(nil) == :error
end
end
...
```
"""
@operators [:==, :!=, :===, :!==, :<=, :>=, :<, :>, :=~]
@doc """
A macro to write simple a simple test shorter.
Code like:
```elxir
prove identity(5) == 5
prove "check:", identity(7) == 7
```
is equivalent to:
```elixir
test "identity(5) == 5" do
assert identity(5) == 5
end
test "check: indentity(7) == 7" do
assert identity(7) == 7
end
```
`prove` supports the operators `==`, `!=`, `===`, `!==`, `<`, `<=`, `>`, `>=`,
and `=~`.
"""
defmacro prove(description \\ "", expr)
defmacro prove(description, {operator, _, [_, _]} = expr)
when is_binary(description) and operator in @operators do
quote_prove(description, expr)
end
defmacro prove(_description, expr) do
raise ArgumentError, message: "Unsupported do: #{Macro.to_string(expr)}"
end
@doc """
Creates a batch of proves.
The `description` is added to every `prove` in the `batch`.
"""
defmacro batch(description, do: {:__block__, _, block}) do
quote_batch(description, block)
end
defmacro batch(description, do: block) when is_tuple(block) do
quote_batch(description, [block])
end
defp quote_batch(description, block) do
Enum.map(block, fn
{:prove, _, [prove]} ->
quote_prove(description, prove)
{:prove, _, [prove_description, prove]} ->
quote_prove("#{description} #{prove_description}", prove)
_ ->
raise ArgumentError, message: "A batch can only contain proves"
end)
end
defp quote_prove(description, {operator, _, [_, _]} = expr)
when is_binary(description) and operator in @operators do
quote do
test unquote(name(description, Macro.to_string(expr))) do
assert unquote(expr)
end
end
end
defp name("", b), do: b
defp name(a, b), do: "#{a} #{b}"
end