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
unit -> OUnit.test).
|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
This declares a test that will fail when the the expression is
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
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
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) ----------
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 ----------
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
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
module M = struct ... end but not
module M = F.G,
= 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|
TEST_MODULE=Module1 TEST_MODULE=Module2 (* ... for all the modules containing tests *) let _ = OUnit.run_test_tt_main (ounit_tests ())
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