Skip to content
syntax extension for oUnit
Find file
Latest commit d88f97f Nov 29, 2011 @till-varoquaux expanded the readme


Pa_ounit - Syntax extension for oUnit

Pa_ounit is a syntax extension that helps writing in-line oUnit. It takes care of automatically registering the tests and generates helpfull failure messages with the file and line number.

It allows user to register tests with a new TEST top-level expressions and automatically collects all the tests in a module (in a function ounit_tests of type unit -> OUnit.test).


The build pre-requisite are:

Download the tarball from github. From there it’s a pretty straightforward make and make install.


If you are trying to get a quick overview of pa_ounit you should probably gloss jump straight to an example.



Declare a test with an expression that should return true.

"TEST" <string_id>? "=" <boolean expression>

The simplest way to declare a test is to use the TEST statement:

This declares a test that will fail when the the expression is false. The string_id is a string that can be specified to name the test. If it is not present the syntax extension will use the file name and position to generate an identifier.

TEST = List.sort compare [4;2;3;1] = [1;2;3;4]


Declare a test for an expression that is expected to raise an error.

"TEST_FAILS" <string_id>? ("RAISES" <exn_pat>)? (":" <type>)? "=" <expression>

The exn_pat is used to pattern match on the raised exception; if not specified the test succeed for any exception. The optional type can be used to specify the type of the expression, if it isn’t specified the compiler might warn Warning 10: this expression should have type unit.

TEST_FAILS RAISES Invalid_argument _ : int = List.nth [1;2] (-1)


Declare a test with a unit expression that will raise an exception in case there’s an error

"TEST_UNIT" <string_id>? "=" <expression>

This is mostly usefull to run oUnit assertions but can also come in handy when autogenerating test cases.

This example is based on oUnit's

open OUnit
let empty_list = []
let list_a = [1;2;3]

 assert_equal 1 (List.length empty_list);
 assert_equal 3 (List.length list_a)


Register a module with test to be initialized only when running the tests/create a test group.

"TEST_MODULE" <string_id>? "=" <module_expression>

This registers the tests in the module_expression. The module_expression will only be evaluated by the test runner which makes a suitable place to initialise test values. If a string_id is provided the tests are grouped in a group with this id. It can also be used to register tests defined in other modules and/or in functors.

TEST_MODULE = struct
  let empty_list = []
  let list_a = [1;2;3]

  TEST_MODULE "List.length" = struct
    TEST = (List.length empty_list) = 1
    TEST = (List.length list_a) = 3

  TEST "List.append" =
    List.append empty_list list_a = [1;2;3]


Collecting tests

The tests in a module are collected in a function ounit_tests : unit -> OUnit.test. As long as there are registered this function is available and contains all the tests registered before that point in the code of the module. This function is also exported outside of the module even if this module type was constraint by a signature that would hide that value (it is added in the signature of the module by the extension).

the top levelt value ounit_tests : unit -> OUnit.test is automatically added at the toplevel of .mli files by the syntax extension. This will cause compilation to fail if you try to compile a file that registers no tests at the top level and doesn’t define this value.

Tests in open and included modules are ignored. Tests in functor definition are added to the signature of the resulting module. Tests in submodules defined directly (i.e.: module M = struct ... end but not module M = F.G, module M = F(G) are automatically registered in an OUnit subgroup labelled with the name of the module.

In the interest of readability it is recommended to write tests as close as possible to the functions they are testing. For libraries the test runner should be in a separate executable. An easy way to write such a test runner would be
(* ... for all the modules containing tests *)

let _ =
 OUnit.run_test_tt_main (ounit_tests ())


Example 1. Netstring encoder/decoder

DJ bernstein’s netstring are a very simple way to encode strings.

The encoded the string s is written out as <bytelen of s>:<s>; Where the bytelen is written as a plain text,non padded, number in ascii. The string "I love pa_ounit" would be written out: 15:I love pa_ounit;.

We will write a very basic parser and encoder for netstrings with embedded tests.

let netstring_encode (s:string) : string =
  Printf.sprintf "%i:%s;" (String.length s) s;;

Now we define a couple of very basic unit tests.

TEST = netstring_encode "" = "0:;"
TEST = netstring_encode "abcd" = "4:abcd;"

The function used to decode netstring encoded is a bit more involved and will require more testing.

let netstring_decode s =
    let len,pos = Scanf.sscanf s "%i:%n" (fun len pos -> len,pos) in
    let end_pos = pos + len in
    if len < 0 || end_pos >= String.length s || s.[end_pos] <> ';' then
      raise (Invalid_argument "netstring_decode");
    String.sub s pos len
  with Scanf.Scan_failure _ ->
    raise (Invalid_argument "netstring_decode");

Those checks are basic sanity checks like before.

TEST = netstring_decode "3:ads;" = "ads"
TEST = netstring_decode "0:;" = ""

While 'netstring_encode' could just accept any input we now need to check that
'netstring_decode' fails properly when it receives malformed netstrings.

TEST_FAILS "Longer than specified" RAISES (Invalid_argument _) : string =
      netstring_decode "3: ;"
TEST_FAILS "No ; termination" RAISES (Invalid_argument _) : string =
      netstring_decode "3:123."
TEST_FAILS "Neg length" RAISES (Invalid_argument _) : string =
      netstring_decode "-2:."
TEST_FAILS "Bad header" RAISES (Invalid_argument _) : string =
      netstring_decode "adf:."


Now that we have all our test we need to call a function to run them. In a
bigger project you would realistically want to do this in a separate file.
Since this is a self contained example we are just going to rely on the fact
that the syntax extension has defined a 'ounit_test : unit -> OUnit.test'
function with all of our tests.

let _ = OUnit.run_test_tt_main (ounit_tests ())


The full code for this example is available in link:example/[]
Something went wrong with that request. Please try again.