The goal of this small series of blogs is to implement the Bank kata in OCaml, doing it test and type driven and try to have a clean design. We’ll try to implement the kata with outside-in tests. My personal goal is to learn some OCaml along the way. I won’t be going into every detail of OCaml, but if you have some experience with an ML language, you should be able to follow along.
I’ve got the inspiration of doing this from a nice blog post from Erik Sacre where he explores outside-in TDD.
This is the outline of the blog posts:
- Creating the project: ready for tests
- Implementing a first outside-in test and implemeting it as fast as possible
- Implementing the clock
Let’s get our system ready
Before we can start, make sure you have opam 2.0
(the ocaml package manager) installed on your system. If you haven’t got it, go over here to install it.
If you’ve already installed opam, make sure to run opam update
regularly to stay up to date with the packages.
Creating the project
In our favorite workspace directory we’ll go and create a new directory:
mkdir bank-kata
cd bank-kata
git init # I won't be showing you the commits
Now we are ready to initialize everything we need for the ocaml project:
opam switch create . 4.07.0
This will let us work in the current directory with OCaml 4.07.0. The command has created an _opam
folder which we want to add to .gitignore
. Also add _build
to .gitignore
.
At the end of the install there will be some command for you to run to update the local variables, for me (I’m using fish
instead of bash
) this is eval (opam env)
so make sure to run that, so all commands are available in your terminal.
Now we are ready to install the base libraries that we need:
opam install merlin ocp-indent dune
merlin
and ocp-indent
are 2 tools so your editor can show you compile errors, type information and have some basic indenting. dune
is the build tool we’ll be using.
If you’ve did everything correct, you should have dune 1.1.1
at least.
Now let’s create a new folder test
with the file dune
with these contents:
(library
(name test)
(libraries base stdio)
(inline_tests)
(preprocess (pps ppx_expect))
)
This creates a dune library test
, with a dependency on libraries base
and stdio
. The line inline_tests
marks this as a test library and the last line includes the ppx_expect
ppx to run expectation tests
When we now run dune runtest
we’ll see that library base can’t be found. You’ll also see that running dune created a dune-project
file in the root directory.
The error output gives us a hint, running that hint we get to the real solution: opam install base stdio
.
When we run again we see that ppx_expect
can’t be found either. No more luck with the hints though :-(. Luckely the solution is the same: opam install ppx_expect
.
Now when we run dune runtest
again we should have no more errors and no output either (because we haven’t written any tests yet).
Our first sanity test
Create a file test/test.ml
with these contents:
open! Base
open! Stdio
let%expect_test "Sanity" =
print_endline "Hello, World!";
[%expect{||}]
The let%expect_test
construct let’s us test output to stdio in the [%expect{||}]
block. Now when you run dune runtest
you’ll see the error, and you could change the [%expect{||}]
block manually. But you can also run dune runtest --auto-promote
, this will automatically change your expect lines in the source code to match the new output. Try to be careful with this, but it can be very useful, especially when all your code is in version control.
Before we can move on to the kata, let’s make sure we an other directory lib
with a dune
file:
(library
(name lib)
)
In test/dune
add lib
as a dependency to libraries
. As a sanity check you can create a file lib/Sanity.ml
with:
let sanity () =
print_endline "Hello, World!"
and call it from your test:
let%expect_test "Sanity" =
Lib.Sanity.test ();
[%expect{| Hello, World! |}]
If your tests still work, you can remove the Sanity
file.
I also removed the test/test.ml
file so we have a clean setup to start the actual kata.
In the next part we’ll actually start implementing the kata