Clojure Developers Barcelona has been running for several years now. Since we’re not many yet, we usually do mob programming sessions as part of what we call “sagas“. For each saga, we choose an exercise or kata and solve it during the first one or two sessions. After that, we start imagining variations on the exercise using different Clojure/ClojureScript libraries or technologies we feel like exploring and develop those variations in following sessions. Once we feel we can’t imagine more interesting variations or we get tired of a given problem, we choose a different problem to start a new saga. You should try doing sagas, they are a lot of fun!
Recently we’ve been working on the Bingo Kata.
The initial implementation
These were the tests we wrote to check the randomly generated bingo cards:
and the code we initially wrote to generate them was something like (we didn’t save the original one):
As you can see the tests are not concerned with which specific numeric values are included on each column of the bingo card. They are just checking that they follow the specification of a bingo card. This makes them very suitable for property-based testing.
In the following session of the Bingo saga, I suggested creating the bingo cards using clojure.spec.
spec is a Clojure library to describe the structure of data and functions. Specs can be used to validate data, conform (destructure) data, explain invalid data, generate examples that conform to the specs, and automatically use generative testing to test functions.
I’d used clojure.spec at work before. At my current client Green Power Monitor, we’ve been using it for a while to validate the shape (and in some cases types) of data flowing through some important public functions of some key name spaces. We started using pre and post-conditions for that validation (see Fogus’ Clojure’s :pre and :post to know more), and from there, it felt as a natural step to start using clojure.spec to write some of them.
Another common use of clojure.spec specs is to generate random data conforming to the spec to be used for property-based testing.
In the Bingo kata case, I thought that we might use this ability of randomly generating data conforming to the spec in production code. This meant that instead of writing code to randomly generating bingo cards and then testing that the results were as expected, we might describe the bingo cards using clojure.spec and then took advantage of that specification to randomly generate bingo cards using clojure.test.check‘s generate function.
So with this idea in our heads, we started creating a spec for bingo columns on the REPL bit by bit (for the sake of brevity what you can see here is the final form of the spec):
then we discovered clojure.spec’s coll-of function which allowed us to simplify the spec a bit:
Generating bingo cards
Once we thought we had it, we tried to use the column spec to generate columns with clojure.test.check‘s generate function, but we got the following error:
ExceptionInfo Couldn’t satisfy such-that predicate after 100 tries.
Of course we were trying to find a needle in a haystack…
Then we used the spec code from the REPL to write the bingo cards spec:
in which we wrote the create-column-spec factory function that creates column specs to remove duplication between the specs of different columns.
With this in place the bingo cards could be created in a line of code:
Introducing property-based testing
Property-based tests make statements about the output of your code based on the input, and these statements are verified for many different possible inputs.
Jessica Kerr (Property-based testing: what is it?)
Having the specs it was very easy to change our bingo card test to use property-based testing instead of example-based testing just by using the generator created by clojure.spec:
See in the code that we’re reusing the check-column function we wrote for the example-based tests.
This change was so easy because of:
- clojure.spec can produce a generator for clojure/test.check from a given spec
- The initial example tests, as I mentioned before, were already checking the properties of a valid bingo card. This means that they weren’t concerned with which specific numeric values were included on each column of the bingo card, but instead, they were just checking that the cards followed the rules for a bingo card to be valid.
Going fast with REPL driven development (RDD)
The next user story of the kata required us to check a bingo card to see if its player has won. We thought this might be easy to implement because we only needed to check that the numbers in the card where contained by the set of called numbers, so instead of doing TDD, we
played a bit on the REPL
did REPL-driven development (RDD):
Once we had the implementation working, we copied it from the REPL into its corresponding name space
and wrote the quicker but ephemeral REPL tests as “permanent” unit tests:
In this case RDD allowed us to go faster than TDD, because RDD’s feedback cycle is much faster. Once the implementation is working on the REPL, you can choose which REPL tests you want to keep as unit tests.
Some times I use only RDD like in this case, other times I use a mix of TDD and RDD following this cycle:
- Write a failing test (using examples that a bit more complicated than the typical ones you use when doing only TDD).
- Explore and triangulate on the REPL until I made the test pass with some ugly but complete solution.
- Refactor the code.
Other times I just use TDD.
I think what I use depends a lot on how easy I feel the implementation might be.
The last user story required us to create a bingo caller that randomly calls out Bingo numbers. To develop this story, we used TDD and an atom to keep the not-yet-called numbers. These were our tests:
and this was the resulting code:
This experiment was a lot of fun because we got to play with both clojure.spec and clojure/test.check, and we learned a lot. While explaining what we did, I talked a bit about property-based testing and how I use REPL-driven development.
Thanks again to all my colleagues in Clojure Developers Barcelona!