Paul
Paul

Reputation: 4530

Print test fixture description in erlang eunit failure

Is there a way to print the test description of an erlang test generator that uses fixtures? Using a generator makes it tricky to tell what test is actually failing and printing the description would help out.

Example:

-module(math_test).

-include_lib("eunit/include/eunit.hrl").
-define(test(Desc, F), {Desc, {setup, fun setup/0, fun cleanup/1, F}}).

setup() ->
  ok.

cleanup(_) ->
  ok.

math_test_ () ->
  [
    ?test("adds two numbers", fun add_two_numbers/0),
    ?test("subtract two numbers", fun subtract_two_numbers/0),
    ?test("undefined method called", fun undefined_error/0)
  ].

add_two_numbers () ->
  ?assertEqual(2, 1 + 3).

subtract_two_numbers () ->
  ?assertEqual(1, 2 - 2).

undefined_error () ->
  undefined_module:uh_oh().

And then running it

[root@a7c901c022bb src]# rebar3 eunit --module=math_test
===> Verifying dependencies...
===> Compiling math
===> Performing EUnit tests...
FFF
Failures:

  1) math_test:math_test_/0
     Failure/Error: ?assertEqual(2, 1 + 3)
       expected: 2
            got: 4
     %% /src/_build/test/lib/math/src/math_test.erl:20:in `math_test:-add_two_numbers/0-fun-0-/1`
     Output: 
     Output: 
  2) math_test:math_test_/0
     Failure/Error: ?assertEqual(1, 2 - 2)
       expected: 1
            got: 0
     %% /src/_build/test/lib/math/src/math_test.erl:23:in `math_test:-subtract_two_numbers/0-fun-0-/1`
     Output: 
     Output: 
  3) math_test:math_test_/0
     Failure/Error: {error,undef,[{undefined_module,uh_oh,[],[]}]}
     Output: 

The first 2 errors are ok, but not great -- you can at least see in the assertion where things actually went wrong.

However the 3rd error (calling undefined module/method) is where things go horribly wrong -- there's no real way to tell where it came from!

Is there a way to improve things, like printing the test description with the failure log?

Upvotes: 2

Views: 646

Answers (2)

RichardC
RichardC

Reputation: 10557

The answers by @legoscia are good, but I also suspect that it's the error reporting implemented by rebar3 that's suboptimal for that kind of error. Running the tests directly from eunit with its default output, you get this:

2> eunit:test(math_test). 
math_test: math_test_...*failed*
in function math_test:'-add_two_numbers/0-fun-0-'/0 (math_test.erl, line 22)
**error:{assertEqual,[{module,math_test},
                      {line,22},
                      {expression,"1 + 3"},
                      {expected,2},
                      {value,4}]}
  output:<<"">>

math_test: math_test_...*failed*
in function math_test:'-subtract_two_numbers/0-fun-0-'/0 (math_test.erl, line 25)
**error:{assertEqual,[{module,math_test},
                      {line,25},
                      {expression,"2 - 2"},
                      {expected,1},
                      {value,0}]}
  output:<<"">>

math_test: math_test_...*failed*
in function undefined_module:uh_oh/0
  called as uh_oh()
**error:undef 
  output:<<"">>

=======================================================
  Failed: 3.  Skipped: 0.  Passed: 0.

With the 'verbose' option, it also prints the descriptions before each setup. Furthermore, moving the description to the test fun, and using the ?_test(...) macro to create test funs with more positional information than a plain fun, as @legoscia suggested, you get this output:

  math_test:18: math_test_ (undefined method called)...*failed*
in function undefined_module:uh_oh/0
  called as uh_oh()
**error:undef 
  output:<<"">>

You could report this to the rebar3 maintainers.

Upvotes: 3

legoscia
legoscia

Reputation: 41528

One thing you can do is putting the test description on the test itself, not the entire setup tuple. That is, change this line:

-define(test(Desc, F), {Desc, {setup, fun setup/0, fun cleanup/1, F}}).

to:

-define(test(Desc, F), {setup, fun setup/0, fun cleanup/1, {Desc, F}}).

With that change, the test descriptions are printed:

Failures:

  1) math_test:math_test_/0: adds two numbers
     Failure/Error: ?assertEqual(2, 1 + 3)
       expected: 2
            got: 4
     %% /tmp/math_test/mylib/_build/test/lib/mylib/test/math_test.erl:20:in `math_test:-add_two_numbers/0-fun-0-/0`
     Output: 
     Output: 
  2) math_test:math_test_/0: subtract two numbers
     Failure/Error: ?assertEqual(1, 2 - 2)
       expected: 1
            got: 0
     %% /tmp/math_test/mylib/_build/test/lib/mylib/test/math_test.erl:23:in `math_test:-subtract_two_numbers/0-fun-0-/0`
     Output: 
     Output: 
  3) math_test:math_test_/0: undefined method called
     Failure/Error: {error,undef,[{undefined_module,uh_oh,[],[]}]}
     Output: 

Another thing to try is specifying the test functions with the ?_test macro instead of as plain fun terms:

math_test_ () ->
  [
    ?test("adds two numbers", ?_test(add_two_numbers())),
    ?test("subtract two numbers", ?_test(subtract_two_numbers())),
    ?test("undefined method called", ?_test(undefined_error()))
  ].

The ?_test macro remembers the line number it appeared on, and includes it in the output on test failure:

  1) math_test:math_test_/0:14: adds two numbers
  [...]
  2) math_test:math_test_/0:15: subtract two numbers
  [...]
  3) math_test:math_test_/0:16: undefined method called
  [...]

Now you can tell which line those tests were invoked from.


Yet another way to do it is to have the individual functions return eunit "test objects" instead of just running the tests. That would involve using ?_assertEqual instead of ?assertEqual, or wrapping the entire thing in ?_test:

math_test_ () ->
  [
    ?test("adds two numbers", add_two_numbers()),
    ?test("subtract two numbers", subtract_two_numbers()),
    ?test("undefined method called", undefined_error())
  ].

add_two_numbers () ->
  ?_assertEqual(2, 1 + 3).

subtract_two_numbers () ->
  ?_assertEqual(1, 2 - 2).

undefined_error () ->
  ?_test(undefined_module:uh_oh())

Then the output contains both the line numbers and the names of the individual test functions:

Failures:

  1) math_test:add_two_numbers/0:20: adds two numbers
     Failure/Error: ?assertEqual(2, 1 + 3)
       expected: 2
            got: 4
     %% /tmp/math_test/mylib/_build/test/lib/mylib/test/math_test.erl:20:in `math_test:-add_two_numbers/0-fun-0-/0`
     Output: 
     Output: 
  2) math_test:subtract_two_numbers/0:23: subtract two numbers
     Failure/Error: ?assertEqual(1, 2 - 2)
       expected: 1
            got: 0
     %% /tmp/math_test/mylib/_build/test/lib/mylib/test/math_test.erl:23:in `math_test:-subtract_two_numbers/0-fun-0-/0`
     Output: 
     Output: 
  3) math_test:undefined_error/0:26: undefined method called
     Failure/Error: {error,undef,[{undefined_module,uh_oh,[],[]}]}
     Output: 

Upvotes: 4

Related Questions