🎓 A1Learner
🔍
0 of 46 lessons completed

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?