Diamond Kata - Thoughts on Incremental Development
Some more thoughts on my experience doing the Diamond Kata with property-based tests…
When test-driving development with example-based tests, an essential skill to be learned is how to pick each example to be most helpful in driving development forwards in small steps. You want to avoid picking examples that force you to take too big a step (A.K.A. “now draw the rest of the owl”). Conversely, you don’t want to get sidetracked into a boring morass of degenerate cases and error handling when you’ve not yet addressed the core of the problem to be solved.
Property-based tests are similar: the skill is in picking the right next property to let you make useful progress in small steps. But the progress from nothing to solution is different.
Doing TDD with example-based tests, I’ll start with an arbitrary, specific example (arbitrary but carefully chosen to help me make useful progress), and write a specific implementation to support just that one example. I’ll add more examples to “triangulate” the property I want to implement, and generalise the implementation to pass the tests. I continue adding examples and triangulating, and generalising the implementation until I have extensive coverage and a general implementation that meets the required properties.
Doing TDD with property-based tests, I’ll start with a very general property, and write a specific but arbitrary implementation that meets the property (arbitrary but carefully chosen to help me make useful progress). I’ll add more specific properties, which force me to generalise the the implementation to make it meet all the properties. The properties also double-check one another (testing the tests, so to speak). I continue adding properties and generalising the implementation until I have a general implementation that meets the required properties.
I find that property-based tests let me work more easily in larger steps than when testing individual examples. I am able to go longer without breaking the problem down to avoid duplication and boilerplate in my tests, because the property-based tests have less scope for duplication and boilerplate.
For example, if solving the Diamond Kata with example-based tests, my reaction to the “now draw the rest of the owl” problem that Seb identified would be to move the implementation towards a compositional design so that I could define the overall behaviour declaratively and not need to write integration tests for the whole thing.
For example, when I reached the test for “C”, I might break the problem down into two parts. Firstly, a higher-order mirroredLines
function that is passed a function to generate each line, with the type (in Haskell syntax):
mirroredLines :: (Char -> Char -> String) -> Char -> String
I would test-drive mirroredLines
with a stub function to generate fake lines, such as:
let fakeLine ch maxCh = "line for " ++ [ch]
Then, I would write a diamondLine
function, that calculates the actual lines of the diamond. And declare the diamond function by currying:
let diamond = mirroredLines diamondLine
I wouldn’t feel the need to write tests for the diamond
function given adequate tests of mirroredLines
and diamondLine
.