Improving OUnit Output

In our example with the buggy implementation of sum, we got the following output:

==============================================================================
Error: test suite for sum:2:onetwo.

File ".../_build/oUnit-test suite for sum-...#01.log", line 8, characters 1-1:
Error: test suite for sum:2:onetwo (in the log).

Called from unknown location

not equal
------------------------------------------------------------------------------

Let's see how to improve that output to be a little more informative.

Stack traces

The Called from an unknown location indicates OCaml was unable to provide a stack trace. That happened because, by default, stack traces are disabled. We can enable them by compiling the code with the debug tag:

$ ocamlbuild -pkgs oUnit -tag debug sum_test.byte
$ ./sum_test.byte

==============================================================================
Error: test suite for sum:2:onetwo.

File "/Users/clarkson/tmp/sum/_build/oUnit-test suite for sum-...#01.log", line 9, characters 1-1:
Error: test suite for sum:2:onetwo (in the log).

Raised at file "src/oUnitAssert.ml", line 45, characters 8-27
Called from file "src/oUnitRunner.ml", line 46, characters 13-26

not equal
------------------------------------------------------------------------------

Now we see the stack trace that resulted from assert_equal raising an exception. You'll probably agree that stack trace isn't very informative though: what matters is which test case fails, not which files in the implementation of OUnit were involved in raising the exception. And we could already identify the failing test case from the first line of output. (It's the test case named onetwo, which at position 2 in the test suite named test suite for sum.)

So we don't usually bother enabling stack traces for OUnit test suites. Nonetheless, it could occasionally be useful if your own code is raising exceptions that you want to track down.

Output values

The not equal in the OUnit output means that assert_equal discovered the two values passed to it in that test case were not equal. That's not so informative: we'd like to know why they're not equal. In particular, we'd like to know what the actual output produced by sum was for that test case. To find out, we need to pass an additional argument to assert_equal. That argument, whose label is printer, should be a function that can transform the outputs to strings. In this case, the outputs are integers, so string_of_int from the Pervasives modules will suffice. We modify the test suite as follows:

let tests = "test suite for sum" >::: [
  "empty"  >:: (fun _ -> assert_equal 0 (sum []) ~printer:string_of_int);
  "one"    >:: (fun _ -> assert_equal 1 (sum [1]) ~printer:string_of_int);
  "onetwo" >:: (fun _ -> assert_equal 3 (sum [1; 2]) ~printer:string_of_int);
]

And now we get more informative output:

==============================================================================
Error: test suite for sum:2:onetwo.

File "/Users/clarkson/tmp/sum/_build/oUnit-test suite for sum-sauternes#01.log", line 8, characters 1-1:
Error: test suite for sum:2:onetwo (in the log).

Called from unknown location

expected: 3 but got: 4
------------------------------------------------------------------------------

That output means that the test named onetwo asserted the equality of 3 and 4. The expected output was 3 because that was the first input to assert_equal, and that function's specification says that in assert_equal x y, the output you (as the tester) are expecting to get should be x, and the output the function being tested actually produces should be y.

Notice how our test suite is accumulating a lot of redundant code. In particular, we had to add the printer argument to several lines. Let's improve that code by factoring out a function that constructs test cases:

let make_sum_test name expected_output input =
  name >:: (fun _ -> assert_equal expected_output (sum input) ~printer:string_of_int)

let tests = "test suite for sum" >::: [
  make_sum_test "empty" 0 [];
  make_sum_test "one" 1 [1];
  make_sum_test "onetwo" 3 [1; 2];
]

For output types that are more complicated than integers, you will end up needing to write your own functions to pass to printer. This is similar to writing toString() methods in Java: for complicated types you invent yourself, the language doesn't know how to render them as strings. You have to provide the code that does it.

results matching ""

    No results matching ""