erlangeunit

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?


Solution

  • 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: