The standard way that TDD is described is as Red-Green-Refactor:
- Red: write a failing test
- Green: get it to pass as quickly as possible
- Refactor: improve the design, using the tests as a safety net
- Repeat
TL;DR; I’ve found that step 1) might be better expressed as:
- Red: write a failing test, or make an existing test fail
Print Diamond
One of the katas that I use in my TDD training is “Print Diamond”. The problem statement is quite simple:
Given a letter, print a diamond starting with ‘A’ with the supplied letter at the widest point.
For example: print-diamond ‘C’ prints
A
B B
C C
B B
A
I’ve used Cyber-Dojo to demonstrate two different approaches so you can follow along with my example, but I recommend you try this kata on your own before reading further. [The link is to Cyber-Dojo’s dashboard view where you can see every change made in the source code by two different teams – Gorilla and Moose].
Gorilla
The usual approach is to start with a test for the simple case where the diamond consists of just a single ‘A’:
> PrintDiamond('A')
A
The next test is usually for a proper diamond consisting of ‘A’ and ‘B’:
> PrintDiamond('B')
A
B B
A
It’s easy enough to get this to pass by hardcoding the result. Then we move on to the letter ‘C’:
> PrintDiamond('C')
A
B B
C C
B B
A
The code is now screaming for us to refactor it, but to keep all the tests passing most people try to solve the entire problem at once. That’s hard, because we’ll need to cope with multiple lines, varying indentation, and repeated characters with a varying number of spaces between them.
Moose
The approach that I’ve been playing with is to start as usual, with the simplest case:
> PrintDiamond('A')
A
For the second test, however, we start by decomposing the diamond problem into smaller constituent parts. This time I chose to write the following test:
[Test]
public void B_should_give_character_sequence()
{
Assert.AreEqual("AB", Diamond.Create('B'));
}
Getting this to pass solves the problem of looping over the character sequence that makes the top half of the diamond. Once that’s at green, we can then bite off character repetition, so we rewrite the test as:
[Test]
public void B_should_repeat_characters()
{
Assert.AreEqual("ABB", Diamond.Create('B'));
}
Next we go for separate lines, rewriting the test as:
[Test]
public void B_should_have_separate_lines()
{
Assert.AreEqual("A\nBB\n", Diamond.Create('B'));
}
And now, in subsequent rewrites of the same test, we can address indentation, then inter-character spaces and finally the symmetry of the bottom half of the diamond. You can follow along in the Cyber-Dojo dashboard.
Conclusion
By recycling the second test multiple times, each time modifying the expected output to become closer to what we actual want, we’ve allowed ourselves to be guided gradually to a solution.
When working on user stories, I often encourage teams to deliver low-fidelity slices early, and this is a similar strategy, but applied at a lower level. It won’t work in all cases, but if you find that the next refactor is harder than you’d like, think about trying to get there in smaller steps.
Leave a Reply