— Quick Unit Tests for OCaml
version 2.0*

Vincent Hugot & the Batteries team


1  Introduction

In a nutshell, qtest is a small program which reads .ml and .mli source files and extracts inline oUnit unit tests from them. It is used internally by the OCaml Batteries project, and is shipped with it as of version 2.0, but it does not depend on it and can be compiled and used independently.

Browse its code in the Batteries Github repository.

This document is available as either one big page or several smaller pages. Contents are the same.

2  Using qtest : a Quick, Simple Example

Say that you have a file foo.ml, which contains the implementation of your new, shiny function foo.

let rec foo x0 f = function
  [] -> 0 | x::xs -> f x (foo x0 f xs)

Maybe you don’t feel confident about that code; or maybe you do, but you know that the function might be re-implemented less trivially in the future and want to prevent potential regressions. Or maybe you simply think unit tests are good practice anyway. In either case, you feel that building a separate test suite for this would be overkill. Using qtest, you can immediately put simple unit tests in comments near foo, for instance:

(*$T foo
  foo  0 ( + ) [1;2;3] = 6
  foo  0 ( * ) [1;2;3] = 0
  foo  1 ( * ) [4;5]   = 20
  foo 12 ( + ) []      = 12

the syntax is simple: (*$ introduces a qtest "pragma", such as T in this case. T is by far the most common and represents a "simple" unit test. T expects a "header", which is most of the time simply the name of the function under test, here foo. Following that, each line is a "statement", which must evaluate to true for the test to pass. Furthermore, foo must appear in each statement.

Now, in order to execute those tests, you need to extract them; this is done with the qtest executable. The command

$ qtest -o footest.ml extract foo.ml
Target file: `footest.ml'. Extraction : `foo.ml' Done.

will create a file footest.ml; it’s not terribly human-readable, but you can see that it contains your tests as well as some oUnit boilerplate. Now you need to compile the tests, for instance with ocamlbuild, and assuming oUnit was installed for ocamlfind.

$ ocamlbuild -cflags -warn-error,+26 -use-ocamlfind -package oUnit \
Finished, 10 targets (1 cached) in 00:00:00.

Note that the -cflags -warn-error,+26 is not indispensable but strongly recommended. Its function will be explained in more detail in the more technical sections of this documentation, but roughly it makes sure that if you write a test for foo, via (*$T foo for instance, then foo is actually tested by each statement – the tests won’t compile if not.

Important note: in order for this to work, ocamlbuild must know where to find foo.ml; if footest.ml is not in the same directory, you must make provisions to that effect. If foo.ml needs some specific flags in order to compile, they must also be passed.

Now there only remains to run the tests:

$ ./footest.native
Failure: qtest:0:foo:3:foo.ml:10

OUnit: foo.ml:10::>  foo 12 ( + ) [] = 12
Failure: qtest:0:foo:2:foo.ml:9

OUnit: foo.ml:9::>  foo 1 ( * ) [4;5] = 20
Ran: 4 tests in: 0.00 seconds.
FAILED: Cases: 4 Tried: 4 Errors: 0 Failures: 2 Skip:0 Todo:0

Oops, something’s wrong... either the tests are incorrect or foo is. Finding and fixing the problem is left as an exercise for the reader. When this is done, you get the expected

$ ./footest.native
Ran: 4 tests in: 0.00 seconds.

Tip: those steps are easy to automate, for instance with a small shell script:

set -e # stop on first error
qtest -o footest.ml extract foo.ml
ocamlbuild -cflags -warn-error,+26 -use-ocamlfind -package oUnit footest.native

3  More qtest Pragmas

4  Technical Considerations and Other Details

What has been said above should suffice to cover at least 90% of use-cases for qtest. This section concerns itself with the remaining 10%.

5  Editor Support

coming soon

This document was translated from LATEX by HEVEA.