Elixir Doctests
Doctests are tests that ExUnit runs from the examples written in a module's documentation. A wrong example fails the run, just like a wrong test.
One example becomes one test
The module is compiled into a folder first, then loaded from there. The doctest line turns its single example into one passing test.
File.write!("greeter.ex", """
defmodule Greeter do
@doc \"""
Greets a name.
iex> Greeter.hello("Ana")
"Hello, Ana"
\"""
def hello(name), do: "Hello, \#{name}"
end
""")
File.mkdir_p!("ebin")
Kernel.ParallelCompiler.compile_to_path(["greeter.ex"], "ebin", return_diagnostics: true)
Code.prepend_path("ebin")
Code.ensure_loaded!(Greeter)
ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule GreeterTest do
use ExUnit.Case
doctest Greeter
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")Each documented function adds a test
The module documents two functions, one example each. Each function's examples run as one test, so the run counts two.
File.write!("greeter.ex", """
defmodule Adder do
@doc \"""
Adds two numbers.
iex> Adder.add(2, 3)
5
\"""
def add(a, b), do: a + b
@doc \"""
Subtracts two numbers.
iex> Adder.sub(5, 2)
3
\"""
def sub(a, b), do: a - b
end
""")
File.mkdir_p!("ebin")
Kernel.ParallelCompiler.compile_to_path(["greeter.ex"], "ebin", return_diagnostics: true)
Code.prepend_path("ebin")
Code.ensure_loaded!(Adder)
ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule AdderTest do
use ExUnit.Case
doctest Adder
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")Doctests sit beside ordinary tests
A test module can hold a doctest line and ordinary tests together. The run counts the example and the ordinary test.
File.write!("greeter.ex", """
defmodule Greeter do
@doc \"""
Greets a name.
iex> Greeter.hello("Ana")
"Hello, Ana"
\"""
def hello(name), do: "Hello, \#{name}"
end
""")
File.mkdir_p!("ebin")
Kernel.ParallelCompiler.compile_to_path(["greeter.ex"], "ebin", return_diagnostics: true)
Code.prepend_path("ebin")
Code.ensure_loaded!(Greeter)
ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule GreeterTest do
use ExUnit.Case
doctest Greeter
test "greets another name" do
assert Greeter.hello("Bo") == "Hello, Bo"
end
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")Example
File.write!("greeter.ex", """
defmodule Speaker do
@doc \"""
Greets a name.
iex> Speaker.hello("Ana")
"Hello, Ana"
\"""
def hello(name), do: "Hello, \#{name}"
@doc \"""
Says goodbye to a name.
iex> Speaker.goodbye("Ana")
"Goodbye, Ana"
\"""
def goodbye(name), do: "Goodbye, \#{name}"
end
""")
File.mkdir_p!("ebin")
Kernel.ParallelCompiler.compile_to_path(["greeter.ex"], "ebin", return_diagnostics: true)
Code.prepend_path("ebin")
Code.ensure_loaded!(Speaker)
ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule SpeakerTest do
use ExUnit.Case
doctest Speaker
test "says goodbye to another name" do
assert Speaker.goodbye("Bo") == "Goodbye, Bo"
end
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")Key points
- Doctest: the doctest line in a test module turns a module's documentation examples into tests.
- Example: a documentation example shows an iex call and the value that call returns.
- Beam file: the compiled form of a module, which is where a doctest finds its examples.
- Script step: a module defined in a script has no beam file, so the lesson compiles it into a folder first.
- Count: the examples for one function run as one test in the run.
In this example, the Speaker module is compiled into a folder and loaded from there, with one documented example for each of its two functions. The test module runs both examples as doctests plus one ordinary test, and the script prints three passed tests.
Note
doctests keep documentation honest, because a wrong example fails the test run.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
Where does a doctest find the examples it runs?