- Simple to get started
- Familiar if you’ve written unit-tests in other languages
- Long runway, good tool integration
- Have complete Erlang/OTP installed (some distros have erlang-eunit as a package)
- Create a directory to work in
- Create a module to hold tests
-module(mytests). -compile([export_all, debug_info]). -include_lib("eunit/include/eunit.hrl").
- All test functions are suffixed with
_test
or_test_
. - Assertions are macros included from
eunit.hrl
- Create a simple test that asserts true:
truth_test() -> ?assert(true).
- Start an Erlang shell
$ erl Eshell V5.10.3 (abort with ^G)
- Compile the test module
1> c(mytests). {ok,mytests}
- Run eunit on the module
2> eunit:test(mytests). %% or mytests:test(). Test passed. ok
- Make the test fail!
truth_test() -> ?assert(false).
- Run eunit
3> c(mytests), eunit:test(mytests). mytests: truth_test (module 'mytests')...*failed* in function mytests:'-truth_test/0-fun-0-'/0 (mytests.erl, line 6) **error:{assertion_failed,[{module,mytests}, {line,6}, {expression,"false"}, {expected,true}, {value,false}]}
- Simple truth, falsehood
?assert( TruthValue ) ?assertNot( FalseValue )
- Equality, inequality
?assertEqual( Expected, Expression ) ?assertNotEqual( Unexpected, Expression )
- Pattern matching
?assertMatch( Pattern, Expression ) ?assertNotMatch( Pattern, Expression )
- Exceptions
?assertException( Class, Term, Expression ) ?assertNotException( Class, Term, Expression ) %% Class = exit | error | throw ?assertError( Term, Expression ) ?assertExit( Term, Expression ) ?assertThrow( Term, Expression )
- External commands
?cmd( ShellCommand ) ?assertCmdStatus( ExitStatus, ShellCommand ) ?assertCmdOutput( Text, ShellCommand )Let’s test a naive implementation of a familiar data structure, a stack. In Erlang we will represent these as lists, wrapped in a module that implements New, Push, Pop and Size operations.
-module(stack). -export([new/0, push/2, pop/1, size/1]).
:
new() -> [].
:
push(Thing, Stack) -> [Thing|Stack].
:
pop(Stack) -> {hd(Stack), tl(Stack)}.
:
size(Stack) -> length(Stack).
- Create
stack_tests
module - Include
eunit.hrl
- Compile both
stack
andstack_tests
in the shell
- New stacks are empty (size 0)
- Push followed by Pop returns original item and stack
- Push increases size of stack
- Pop decreases size of stack
- Pop on an empty stack raises an error
pop_empty_raises_error_test() -> ?assertError(badarg, stack:pop(stack:new())).
16> eunit:test(stack_tests, [verbose]).
======================== EUnit ========================
module 'stack_tests'
stack_tests: new_stacks_are_empty_test...ok
stack_tests: lifo_test...ok
stack_tests: push_increases_size_test...ok
stack_tests: pop_decreases_size_test...ok
stack_tests: pop_empty_raises_error_test...ok
[done in 0.014 s]
=======================================================
All 5 tests passed.
See also github.com/seancribbs/eunit_formatters
Things you might want to do:
- Integrate the test suite with build tools
- Perform setup/teardown around tests
- Enforce timeouts on tests that might stall
- Group multiple related tests together more tightly
- Debug state and progress inside a test
- Report on code coverage of your tests
- Run
rebar eunit
- Defines
TEST
macro - Includes modules in
test/
directory when testing
Sometimes you need to do setup or teardown around tests. You can group together tests that require the same environment:
similar_tests_() -> % use trailing _! {setup, fun() -> start_server() end, % setup fun(Pid) -> stop_server(Pid) end, % cleanup [ %% {TestDescription, TestFunction} {"test 1", fun test1/0}, {"test 2", fun test2/0} ]}.
You can also use foreach
instead of setup
to run setup/cleanup
around every test.
eunit
runs every test in a new process and kills it after 5
seconds by default. You can increase/decrease the timeout like so:
slow_test_() -> % use trailing _! {timeout, 60, % 1 minute, in seconds [fun slow_thing/0]}. % can be a list of tests
Sometimes you want to treat a bunch of tests as a group, in which case you can just return a list of them. Note an assertion with the underscore prefix creates a test function that wraps the assertion.
fizzbuzz_test_() -> % use trailing _! [ ?_assertEqual(fizz, fizzbuzz(3)), ?_assertEqual(buzz, fizzbuzz(5)), ?_assertEqual(fizzbuzz, fizzbuzz(15)), ?_assertEqual(7, fizzbuzz(7)) ].
It can be hard to tell what’s going on in complex tests. Debug macros help.
?debugMsg("Trying fizzbuzz")
mytests.erl:62:<0.126.0> Trying fizzbuzz
?debugHere
mytests.erl:65:<0.126.0> <-
?debugVal(Count)
mytests.erl:71:<0.126.0> Count = 10
?debugFmt("Got ~p", [A])
mytests.erl:83:<0.126.0> Got fizz
?debugTime("fizz", netfizz())
mytests.erl:100:<0.126.0> fizzing: 0.321 s
rebar
supports computing and outputting coverage automatically. Add this line torebar.config
:{cover_enabled, true}.
After running
rebar eunit
, open .eunit/index.html- Otherwise:
cover:start(). cover:compile_beam_directory("."). eunit:test(mytests). [cover:analyze_to_file(Mod, [html]) || Mod <- cover:modules()]. cover:stop().
eunit
swallows console output by default. Write to log files or use ?debug macros.- Ensure your tests are pure or cleanup properly after themselves. Ordering issues can plague test suites run on different machines.
- Complicated setup/teardown combos can be hard to reason about. Move to common\_test if your suite becomes hard to setup.
- Know when to use the
_
on test functions:- If your function executes directly and uses regular assertions, omit the underscore.
- If your function uses fixtures or returns a list of tests, include the underscore.
rebar
: https://github.com/rebar/rebar- eunit guide: http://www.erlang.org/doc/apps/eunit/users_guide.html
- Prettier suite output: https://github.com/seancribbs/eunit_formatters
- These slides: http://speakerdeck.com/seancribbs/getting-started-with-eunit