After attending Jason Goreman’s Intensive TDD workshop, I decided to have some practice at the triangulation aspect of Test-Driven Development. I strictly kept to the TDD mantra of:
Red: Write a failing test
Green: Make the test pass with the simplest implementation possible
Refactor: Remove duplication or improve the design
Repeat
I based my effort on the Scoring Project from RubyKoans. This already includes some tests but I wrote mine from scratch.
To recap the rules:
A set of three ones is 1000 points
A set of three numbers (other than ones) is worth 100 times the number. (e.g. three fives is 500 points).
A one (that is not part of a set of three) is worth 100 points.
A five (that is not part of a set of three) is worth 50 points.
Everything else is worth 0 points.
I started with what seemed like the simplest test possible:
Let’s make it green:
So, how do you decide what test to write next? It seems that rules 3 and 4 are dependent on rules 1 and 2, so let’s postpone those for later and write a failing test for rule 1:
Sticking the rule of only writing the minimum code needed to pass the test:
A confession here: This is how I initially approached the problem, but I found I got stuck at a ‘deadlock’ position where it wasn’t possible to make progress with small, simple refactorings. Looking at the set of rules from a distance, we can see this the end result is an accumulation of the scores from each rule. Therefore, having a series of branches for alternative conditions probably isn’t going to work.
Let’s refactor the code to this to give a better base to build upon:
Write another failing test for rule 1:
Make it green:
At this point we can see duplication starting to appear, so it’s time to refactor and generalise the solution:
Now let’s write a failing test for rule 2:
Make it green:
Write another failing test for rule 2:
Make it green:
Refactor to remove duplication:
Write another failing test for rule 2:
Make it green:
We can see some duplication creeping in - we would need five lines to cover three twos up to three sixes, so let’s refactor to generalise:
We also need to consider the case of more than three of the same value:
A simple change makes this green:
Let’s add a failing test for Rule 3:
Make it green:
And another failing test for Rule 3:
Make it green:
Another failing test:
Make it green:
Refactor and generalise:
Which can be further improved:
Write a failing test for rule 4:
Make it green:
Now let’s consider the slightly tricker case of interdependent rules:
The current implementation will give an incorrect answer of 700 because it’s counting the fives as a triple and then counting them again as single fives. We need to make sure they aren’t counted twice.
A simple way of doing that is to sort the array and then drop the first 3 values of the array:
I’m happy with that final solution, and it also passes the RubyKoans tests.
What I Learned
Triangulation is a useful technique but doesn’t bypass the need for the analysis and thinking required to solve tricky problems. If you try to do it on ‘autopilot’ you probably won’t succeed. The order you choose to write the tests, and the code you write early on can have a significant impact in how much effort is required discover to the algorithm.