Doctests for Ocaml
You can test in the small, validate the code in your documentation, encourage testing, and get better documentation by leveraging MDX, a doctest-like tool for OCaml.
Documentation-based tests for Ocaml, similar to what the doctest
tool does for Python, and a similarly named tool for Haskell, is supported by OCaml's tooling. It's not especially obvious or well-documented, but it is well-supported. You do that using a tool called mdx
. It was built to keep code up to date in the Real World OCaml book and is usable in your repository.
Get it to run on your mli files
There are a few different approaches to documentation in Ocaml. My assumption here is that you take the approach of creating mli
files for all of your Modules and that you are documenting the modules in the mli
file.
Start by dropping some code into a mli file.
Add the mdx
dependency and stanzas to your dune-project
This assumes you have a dune
based project with a directory that looks something like the following.
.
├── README.md
├── bin
│ ├── dune
│ └── main.ml
├── dune-project
├── lib
│ ├── config.ml
│ ├── dune
├── bar.opam
├── bar.opam.locked
├── test
│ ├── dune
│ └── bar.ml
Add (using mdx <version>)
to your dune-project
file. At the time of this writing, the version was 0.2
. That may have changed by the time you read this. You also need to add mdx
as a dependency in your package
stanza.
Finally, you want to make sure that mdx
is installed with opam
.
# dune build
# opam install . --deps-only -y
# opam lock .
In the best case, you would have this as a target in Make
or Make
like automation.
Finally, update the dune
file in the library, probably in lib/dune
where you want to do the doctest
style testing. It should look like the following.
(mdx
(files :standard - *.mli)
(libraries bar))
(library
(name bar)
(inline_tests)
(preprocess
(pps ppx_sexp_conv ppx_expect ppx_sexp_value))
(libraries
core
eio.mock
eio.unix
eio_main
core_unix
ppx_expect
ppx_sexp_value
base
sqlite3
eio
ppx_sexp_conv
sexplib))
Notice the mdx
stanza. Both the files
part and the libraries
part need to be in place or you will not successfully run the code embedded in your documentation . Ensure that mdx
runs on the mli
files and include the library you are trying to test in that stanza. In this example, that library is bar
. Notice that name is the same name in both the mdx
stanza and the library
stanza. If you don't you will get an Unbound Module
warning that looks something like the following.
File "lib/config.mli", line 1, characters 0-0:
diff --git a/_build/default/lib/msqlite.mli b/_build/default/lib/.mdx/config.mli.corrected
index ddef9c6..6f66a20 100644
--- a/_build/default/lib/config.mli
+++ b/_build/default/lib/.mdx/config.mli.corrected
@@ -7,8 +7,12 @@
{@ocaml[
# open Bar.Config;;
+ Line 1, characters 6-20:
+ Error: Unbound module Bar
# "a" ^ "bc";;
- : string = "abc"
]}
It is worth spending some time reading over the Mdx Stanza documentation for a full reference of what you can put there.
Running MDX Tests
Once you have done all of that, mdx
will be wired into your dune
based build. When you run the dune test target, dune runtest
, it will also run the documentation based tests you have created.
Using this with the Promotion feature of dune makes this an incredibly powerful way to build tests.