A deprecated unit testing framework for OCaml. Use the cs3110 tools CLI instead.
Running ocamltest file.ml
should search for a file named file_test.ml
and execute any unit tests within file_test.ml
with file.ml
in scope.
Unit tests are unit -> unit
functions with names prefixed by the string test_
.
This tester was the basis for the Fall 2013 test harness for CS3110, but it never worked well for files with dependencies.
Looking back, the timeouts, "all tests pass" message, and recursive globbing were the most fun features.
- This project requires gnumake
- This project requires Python 2.7
- This project requires OCaml. It has been developed with 4.01.0 but probably works with 3.12.
- This project does not currently support the testing of files with dependancies.
- This project does not work on Windows. Sorry.
Writing and running unit tests should be easy. That's really all this project is about.
Under this framework, you only need to write complimentary _test
modules to your main code and define test functions within them.
Every test in a test module is run in an isolated toplevel environment.
All the finding, compiling, timeouts, and logging is taken care of
(by the Python testing environment).
The ocamltest
executable searches for modules ending with _test.ml
.
Within those files, it searches for unit -> unit
functions that begin with the prefix test_
and then runs each testcase in a separate environment.
That's all you need to start testing: a script and a _test.ml
script.
- Clone the repository
git clone https://github.com/bennn/bubbles.git
- Run
make install
. You will likely need root permissions - (Optional) Set the
OCAMLTEST_HOME
environment variable to the root folder of whatever project you want to run tests in.
Run make uninstall
with root permissions.
- Make a simple module. Let's pretend its called
mymodule.ml
. - Make a test file
mymodule_test.ml
. The_test
suffix is important! Test file should containunit -> unit
functions with the prefixtest_
. These are the test cases. Other functions and modules may be defined inside the test file, but those will not be executed as part of the suite. - Run
ocamltest mymodule
and hope for the best.
The simplest possible test suite can be made as follows:
touch fun.ml
echo "let test\_one () = assert true" | fun_test.ml
A slightly more useful version of fun_test.ml
would import fun.ml
and the standard testing library, ocamltest.cma
:
open Fun
open Ocamltest
let test\_one () = assert_true true
But these aren't too instructive. Let's make a tiny lists library and run some tests on it. First, we write the source code:
let length = function [] -> 0 | _::t -> 1 + length t
Cool cool. That's a good start. Next, we write a test for our new module:
open Ocamltest
module M = My_lists
module State = struct
let xs = [1;2;3]
let ys = [1;2;3;4]
end
let test_length1 () =
assert_less (M.length [1;2]) 3
let test_length2 () =
assert_greater 3 (M.length [1;2])
let test_length3 () =
assert_equal (M.length State.ys) (1 + M.length State.xs)
let helper xss = List.hd xss
let test_length4 () =
let mylist = [[1; 2]] in
assert_greater (M.length (helper mylist)) (M.length mylist)
Now we can run the tests:
ocamltest my_lists
and party. "ALL TESTS PASS"
There's a bit going on in my_lists_test.ml
, so let's break that down:
- The first line,
module M = My_lists
, imports the source code into the local namespace. It permits us to call the functions we want to test. - The module starting at the second line,
module State = struct
creates shared state for the test cases. Listsxs
andys
are defined once, here, and can be used in any number of tests later in the file. This is by convention. See the section on Shared State for an explanation. - Functions
test_length1
throughtest_length4
are the test cases. Those get run. Their output decides wheter the suite passed or failed. helper
is an auxillary function, defined for convenience right smack in the middle of the file. It is ignored by the harness, buttest_length4
uses it. Note that this function may not be called by test 1 through 3, because it (helper
) was not in scope when they (tests 1 - 3) were defined.
If you'd like to define variables for the test cases to access, just declare them before you declare the test case. Test modules are compiled just like any other, from top to bottom. Preferred convention is to keep all your helpers and variables inside a module defined at the very top of the test file. This way, the variables are easy to locate within a test file and all test test cases can access them.
There no support for referencing external modules. The State
module or its equivalents, if you choose to define them, must exist within either the source code or the test file.
You can supply ocamltest with a pattern instead of a module name. Say you have a directory with the following four files: module1.ml
, module2.ml
, module1_test.ml
, module2_test.ml
.
Running ocamltest 'module*'
inside this directory will execute the harnesses module1_test.ml
and module2_test.ml
. (The quotes might matter, depends on which shell you use.)
Actually, you can do even better. Leading and trailing asterisks are implicit, so the following patterns achieve the same results:
ocamltest module
ocamltest mod
ocamltest ule
ocamltest 'od*l'
You get the idea. Be careful getting too lazy if you have lots of test files around and don't want to run 'em all. Or don't be careful and just run all the tests all the time. This pattern matching is maybe a bit too eager. Let me know if you hate it.
By default, ocamltest mymodule
searches for files matching the pattern "mymodule" in the current working directory and its containing folders. You can change this behavior.
ocamltest -d <dirname> mymodule
starts the search in the directory dirname
, instead of the current directory. The --directory
option does the same thing. Also, you can set the environment variable OCAMLTEST_HOME
, which causes the harness to search from that directory instead of the current one. It's like running ocamltest -d $OCAMLTEST_HOME mymodule
, just with less typing.