- Published on
Unit testing OCaml code with ppx_expect: some notes
- Authors
- Name
- Chris Armstrong
- @ckarmstrong
ppx_expect allows you to write inline tests which sit with your code and can be run easily with dune.
It works is a rather novel way: you print out a string in your test and then assert against the printed result.
This has the advantage of allowing you to build your tests retrospectively (inverting TDD): you can write the code that generates the test and then update it to the correct value. This can be much faster that trying to guess the output and refactoring it to work.
Things to know using ppx_expect
- It only works on libraries, not executables. If you're building your code with dune, move the code to be tested into a
(library (name my_library)...)
declaration, and then reference that library in your executable as(executable ... (libraries my_library))
- ppx_expect has a lot of JaneStreet dependencies, like
Base
andCore
(something to be mindful of if you don't want them as transitive dependencies). fmt
is useful to assist with printing format strings andppx_deriving
with theshow
plugin is indispensible for printing your types.
Setup
ppx_expect has to be added as a project dependency first. If you're using dune-project
to manage your opam files, add it in the depends
clause like so:
(package)
; ...
(depends fmt ppx_deriving ppx_expect)
)
You also need to install it into your opam switch:
opam install ppx_expect
In your dune library (ppx_expect does not work with executables), add (inline_tests)
and the (preprocess ppx_expect)
reference:
(library (name my_library)
(libraries fmt)
(inline_tests)
(preprocess (pps ppx_expect ppx_deriving.show))
)
Usage
Writing tests
Here is an example with a simple odd/even generator:
type odd_even = Odd | Even
[@@deriving show]
let is_odd_even x = if x mod 2 = 0 then Even else Odd
let%expect_test "is_odd_even 0" =
let res = (is_odd_even 0) in
Fmt.pr "%a" pp_odd_even res;
[%expect "My_library.Even"]
let%expect_test "is_odd_even 5" =
let res = (is_odd_even 5) in
Fmt.pr "%a" pp_odd_even res;
[%expect "My_library.Odd"]
let%expect_test "is_odd_even -10" =
let res = (is_odd_even (-10)) in
Fmt.pr "%a" pp_odd_even res;
[%expect "My_library.Even"]
Executing test suite
We can execute our unit tests with:
dune runtest
If your tests are successful, you will see no output.
Test failures
Let's add an error test to see what that looks like:
let%expect_test "is_odd_even 13" =
let res = (is_odd_even (13)) in
Fmt.pr "%a" pp_odd_even res;
[%expect "My_library.Even"]
We will now see error output:
File "my_library.ml", line 1, characters 0-0:
diff --git a/_build/default/my_library.ml b/_build/.sandbox/89010188fcdb897a907873b6c7c65c59/default/my_library.ml.corrected
index f9683ce..4093b19 100644
--- a/_build/default/my_library.ml
+++ b/_build/.sandbox/89010188fcdb897a907873b6c7c65c59/default/my_library.ml.corrected
@@ -21,4 +21,4 @@ let%expect_test "is_odd_even 5" =
let%expect_test "is_odd_even 13" =
let res = (is_odd_even (13)) in
Fmt.pr "%a" pp_odd_even res;
- [%expect "My_library.Even"]
+ [%expect "My_library.Odd"]