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.
(** Let's look at how good OCaml is with integers and strings:
{@ocaml[
# 1 + 2;;
- : int = 2
# "a" ^ "bc";;
- : string = "ab"
]}
*)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.
(lang dune 3.6)
(name murios)
(version 0.1.0)
(generate_opam_files true)
(source
(github foo/bar))
(authors "Calvin M. White <CalvinMWhite@jourrapide.com>")
(maintainers "snugly annoyed run")
(license UNLICENSED)
(using mdx 0.2)
(package
(name murios)
(synopsis "The murios data manipulation system")
(description "The murios data manipulation system")
(depends (ocaml (>= 5.0.0))
mdx
dune-build-info
cmdliner
odoc
ocamlformat
ocaml-lsp-server
(eio (>= 0.8))
eio_main utop
sqlite3
base
core
core_unix
(dune (>= 3.7)))
(tags
(cmdline "universal takeover")))
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
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.