There Is No Advanced TDD

This article was previously published on Linkedin

A screenshot of a FizzBuzz kata executed in JavaScript, very early stage

There’s not really such as thing as “advanced TDD”, other than practising TDD more diligently, writes Esko Luontola.

I experienced this directly when working on my hobby project this month. I’m trying to code the rules to a boardgame that is regarded as pretty simple by the boardgaming community (it has a complexity score of 2.69 over 5 on Boardgamegeek). It is however orders of magnitude more complicated to code than, say, Force 4 or Chess.

One rule of this game that it took me a long time to get right is the one around retreats. It goes like this: when one of my units attacks another unit, I roll a number of special dice, whose sides depict various symbols. Some symbols, when they turn face up, inflict damage on the opponent’s unit. The “flag” symbol is special: it means that the unit is expected to retreat some hexes (1, 2, 3 or 4 hexes, depending on the defending unit’s speed). There are a number of complications:

The above rules apply in the middle of the close combat procedure, which in turn has other complications, such as that if the defending unit does not die and does not retreat, it is entitled to battle back, and other things.

My first idea was to single out the retreat logic in a pure function; pure in the sense that this function should not move units around on the board, take decisions, or apply damage. I wanted to focus on inputs (the dice results, the situation on the board) and the outputs: how much damage the defending unit should take, and what are the move options that the defending player must choose from. Instead of coding a function that acted on the model, I wanted a function that returned a data structure that represents the actions to be taken. I think I learned this trick from a blog by Jessica Kerr that I now am no longer able to find, where she called it the “super simple approach”, or something like this.

I then proceeded to implement this, writing tests first. It did not go well! This is my git log:

The git log shows a lot of "WIP" commits and false starts
The git log at the time when I realized I was not going to make it

The code I had at 9a0ec89 looked like this: horribly complicated, and not working correctly. There are also signs of debugging via console logs, another indication that my effort was failing.

Screenshot of my hopelessly complicated early code

What went wrong? I took large steps. I tried to guess the correct algorithm too early. I forced myself to continue even though I was tired. Then I did the right thing: throw away the code and start from scratch!

This time I decided to try and follow TDD more carefully. Small steps and fake it. “Fake it” means that when I write a new failing test, I make it pass by returning the exact value that the test expects. It may look like cheating and wasting time; in fact, when we teach TDD we often hear complaints from learners about fake it. They tell us “this is silly; surely we don’t do this in real work”. Sometimes I hear this so much that I start to believe it; yet this programming episode reminded me that it’s when the going gets tough that you really need to shift to a low gear, go slowly, apply the TDD process as well as you can, and take really small steps. My git log after this has a different tone:

Screenshot of better looking commit messages
You can guess from the tone of the messages that the author is on a good path

and the code today looks quite different:

Screenshot of much improved JavaScript code

Thanks to 👨💻 Esko Luontola, J. B. Rainsberger, GeePaw Hill, Jessica Kerr for learnings and inspiration. And of course Kent Beck!