Skip to content

williamthome/doctest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

doctest

A library to test Erlang -moduledoc and -doc attributes.

Requirements

OTP >= 27.

Installation

% rebar.config
{profiles, [
    {test, [
        {deps, [{doctest, "0.8.0"}]}
    ]}
]}.

Usage

Tests run via the doctest:module/1,2 function or on modules that include the doctest header, but only exported functions are tested.

Testing via doctest:module/1,2 function

Take this module:

-module(foo).
-moduledoc """
Module doc tags can also be tested.

```erlang
1> foo:foo() =:= bar.
true
```
""".

-export([foo/0]).

-doc """
```erlang
1> foo:foo().
foo
```
""".
foo() ->
    bar.

Running it via rebar3 as test shell:

1> doctest:module(foo).
 PASS  ./test/support/foo.erl:6 -moduledoc
 FAIL  ./test/support/foo.erl:15 -docassertEqual

    Expected: foo
    Received: bar151> foo:foo().
 16foo
    │
    └── at ./test/support/foo.erl:15

Tests: 1 failed, 1 passed, 2 total
 Time: 0.007 seconds

Options

Options can be provided when using the doctest:module/2 function. The available options are:

  • moduledoc :: boolean(): enable or disable -moduledoc test
  • funs :: boolean() | [{atom(), arity()}]: enable or disable -doc tests or define the functions to be tested
  • eunit :: resolve | term(): set EUnit options

Testing via doctest header

Take this module:

-module(math).

-export([add/2]).

-ifdef(TEST).
-include_lib("doctest/include/doctest.hrl").
% -doctest <see the options section>.
-endif.

-doc """
Adds two numbers together.

_Example_:
```erlang
1> math:add(0, 1).
1
2> math:add(
.. 1,
.. 1
.. ).
2
```
""".
add(A, B) ->
    A+B.

Note that the code is defined like in the Erlang shell, starting the expression line from 1..N and incrementing it by one for every test, and continues in multiple lines with .. and aligning it with the code of the previous line, for example:

% Valid
1> foo.
foo
2> foo
.. =:=
.. bar.
bar

% Invalid
1> foo.
foo
200> foo % <- Must be 2 (previous line + 1)
 .. =:=  % <- Must be aligned
  .. bar.
bar

Now, by running rebar3 eunit:

 PASS  ./src/math.erl:29 -doc
 PASS  ./src/math.erl:31 -doc


Tests: 2 passed, 2 total
 Time: 0.008 seconds

By changing the first test to:

1> math:add(1, 1).
1

And running rebar3 eunit again:

 FAIL  ./src/math.erl:15 -docassertEqual

    Expected: 1
    Received: 2151> math:add(1, 1).
 161
    │
    └── at ./src/math.erl:15

 PASS  ./src/math.erl:17 -doc


Tests: 1 failed, 1 passed, 2 total
 Time: 0.007 seconds

Options

Options are defined via the -doctest attribute and can be defined multiple times. The available options are:

  • boolean() | {enabled, boolean()}: enable or disable the test running, e.g.:
    -doctest true.
  • {moduledoc, boolean()}: enable or disable -moduledoc test, e.g.:
    -doctest {moduledoc, true}.
  • [{atom(), arity()}] | {funs, [{atom(), arity()}] | boolean()}: enable or disable -doc tests or define the functions to be tested, e.g.:
    -doctest [add/2].
  • eunit :: resolve | term(): set EUnit options, e.g.:
    -doctest {eunit, resolve}.
  • map(): define all or partial options, e.g.:
    -doctest #{
        enabled => true,
        moduledoc => true,
        funs => [add/2],
        eunit => resolve
    }.

Global options

Options can be globally defined via a config file, e.g.:

% config/sys.config
[{doctest, [
    {enabled, true},
    {moduledoc, false},
    {funs, true},
    {eunit, [no_tty, {report, {eunit_progress, [colored, profile]}}]}
]}].

EUnit options

Valid EUnit options are the resolve atom and a proplist.

By defining resolve as the EUnit options, doctest will try to resolve the options via rebar. Custom options can be defined as documented at rebar3.org:

The default EUnit options can be configured, as documented here.

Interesting undocumented options are:

  • no_tty completely disables the default EUnit reporter output
  • {report, {Module, Args}} runs a custom EUnit reporter (the functionality that prints results to the shell). The reporter module needs the following callbacks implemented:
    -export([start/0]).
    -export([start/1]).
    -export([init/1]).
    -export([handle_begin/3]).
    -export([handle_end/3]).
    -export([handle_cancel/3]).
    -export([terminate/2]).

no_tty and report can be combined to replace the EUnit reporter with a custom one:

{eunit_opts, [no_tty, {report, {my_reporter, Opts}}]}.

Doctest EUnit Reporter

doctest has a built-in EUnit reporter called doctest_eunit_report for better visualization of the tests. It can be used by defining the below option in the rebar.config of your project:

{eunit_opts, [no_tty, {report, {doctest_eunit_report, []}}]}.

Currently, no options are expected/provided by the reporter.

An example of the doctest_eunit_report output: doctest_eunit_report

TODO

  • More tests
  • Specs
  • Improve docs

Sponsors

If you like this tool, please consider sponsoring me. I'm thankful for your never-ending support ❤️

I also accept coffees ☕

"Buy Me A Coffee"

Contributing

Issues

Feel free to submit an issue on Github.

License

Copyright (c) 2024 William Fank Thomé

doctest is 100% open source and community-driven. All components are available under the Apache 2 License on GitHub.

See LICENSE.md for more information.