Using OUnit to test your OCaml program

Bobby Priambodo
4 min readJul 23, 2017

--

A random dove image to get you reading :) [source]

I have been spending my weekends working on Haversiner, a toy project with OCaml and Opium. I started this to see how easy it is to build an API with Opium (and I’m loving it so far!). The basic idea of Haversiner is a web API for calculating great-circle distance between two coordinates using the Haversine formula.

To accomplish this, I wrote a Haversine module that is responsible to actually calculate the distance. The module has the following API:

It has a coordinate_of_floats function to convert a pair of latitude-longitude floats into implementation-specific coordinate type, and a calculate_distance function that, given two coordinates, will calculate the great-circle distance (in kilometers) between the two. Quite neat.

As I am aspiring to become a good software engineer, I then want to do what good software engineers do: write tests.

Bare tests without framework

This is ultimately my first proper OCaml project, so I haven’t got around testing much and don’t know the standards. I initially generated this project using Jared Forsyth’s helpful Ohai library. Fortunately it includes an example test, but it has no dependencies and only utilizes OCaml’s built-in assert function.

Thinking that it will serve well for starters, I started writing the test using only the tools available. After several runs, this was what I settled with:

Print statements and assert, yuck!

Guess what: it worked! At least it gave me confidence on whether my implementation is correct. Here is the success output:

$ make test
jbuilder runtest
...8< snip jbuilder output...
test alias test/runtest
Tolerating +- 10 meters
BNA-LAX: expected = 2886.45 km, actual = 2886.44444284
JAK-BDG: expected = 114.78 km, actual = 114.781513783

And here’s when I purposefully set wrong expected value on the assertion:

$ make test
jbuilder runtest
...8< snip jbuilder output...
test alias test/runtest (exit 2)
(cd _build/default/test && ./test.exe)
Tolerating +- 10 meters
BNA-LAX: expected = 2886.45 km, actual = 2886.44444284
JAK-BDG: expected = 114.80 km, actual = 114.781513783
Fatal error: exception "Assert_failure test/test.ml:23:2"
Raised at file "test/test.ml", line 23, characters 2-37
Called from file "test/test.ml", line 25, characters 8-14
make: *** [test] Error 1

As you may have noticed, the output was not helpful. It just either kept silent on success or errored on failure. That is why I added some print_endline calls there to know which test fails. I believe we can do better.

After I finished the implementation, I wanted to improve my tests and use a proper test framework. After digging the (quite sparse) resource in the community, I settled with OUnit, as it seemed to be the most popular.

Another reason is it is loosely based on HUnit (Haskell’s testing framework), and what’s not to like about Haskell? :)

Migrating to OUnit

To use it, first I install OUnit via OPAM:

$ opam install ounit

After that, I add the OUnit dependency to my test executable on test/jbuild file:

...(executable (
(name test)
(libraries (lib ounit))
))
...

And the dependency setup should be done! Thanks to Jane Street’s jbuilder tool, the process is so simple. Now, onto writing the test.

Unfortunately, OUnit’s documentation is more of an API reference and has very little comprehensive examples. I spent some time looking for examples on writing tests with OUnit, and then I encounter this repo by Oliver Frolovs that he actually built to remind himself how to write tests with OUnit. It now helps me! :)

With Oliver’s code as a reference, I refactored my test code to be as follows:

The output? Here goes:

$ make test
jbuilder runtest
...8< snip jbuilder output...
test alias test/runtest
..
Ran: 2 tests in: 0.00 seconds.
OK

Nice! We get the number of test cases and also the test duration. And it’s 0.00 seconds! How cool is that. How about when the test fails?

$ make test
jbuilder runtest
...8< snip jbuilder output...
test alias test/runtest (exit 1)
(cd _build/default/test && ./test.exe)
.F
==============================================================================
Failure: 0:Haversine:1:calculate_distance JAK-BDG
not equal
------------------------------------------------------------------------------
Ran: 2 tests in: 0.00 seconds.
FAILED: Cases: 2 Tried: 2 Errors: 0 Failures: 1 Skip: 0 Todo: 0 Timeouts: 0.

Okay, so we have more information now. We know that, from the .F, we run two tests and one of them fails. We also know that the error comes from Haversine > calculate_distance JAK-BDG test case, so we can localize and locate the error to fix it.

I wouldn’t say that this output is the best, though. Some test frameworks on other languages (notably ExUnit for Elixir) shows the value comparison when something is not equal. Nevertheless, this is an improvement over the no-framework test I started with.

A note on OUnit version

At the time of this writing, OUnit version is 2.0.0, and it ships with two namespaces OUnit and OUnit2. I tried using OUnit2, but the test runs significantly slower (0.10 second vs 0.00 second) for such simple tests, and the error output is rather noisy with irrelevant information. That is why I stick with OUnit.

So that’s it! Hope you get something out of my exploration. Let me know if there’s something I missed.

Happy testing!

--

--

Bobby Priambodo

Software Engineer. Functional programming and distributed systems enthusiast. Java, JavaScript, Elixir, OCaml, Haskell.