Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
syntax extension for oUnit
OCaml
branch: master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
example
src
LICENSE.txt
Makefile
README.asciidoc

README.asciidoc

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).

Installing

The build pre-requisite are:

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

Manual

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

Syntax

TEST

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]

TEST_FAILS

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)

TEST_UNIT

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 example.ml.

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

TEST_UNIT =
 assert_equal 1 (List.length empty_list);
 assert_equal 3 (List.length list_a)
----------

TEST_MODULE

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
  end

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

end
----------

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).

Warning
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.

Tip
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
TEST_MODULE=Module1
TEST_MODULE=Module2
(* ... for all the modules containing tests *)

let _ =
 OUnit.run_test_tt_main (ounit_tests ())

Examples

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 =
  try
    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/ns.ml[]
Something went wrong with that request. Please try again.