I’m lucky enough to work for a company where there’s a strong culture of promoting professional development for the software engineers. One of the forms this takes is training days where we all get together to do practical exercises to try recommended programming practices.
One of the practices we’ve been trying is TDD. While this has shown itself to have the benefit of improving code coverage, it did seem to have some drawbacks. Back in January, and again this month, one of the exercises we looked at was converting numbers to their representation in Roman numerals e.g. 1 = I, 2 = II. My pairings didn’t manage to complete the exercise in either session. It seemed that the application of the principle of writing the simplest possible code to make the test pass resulted in very clumsy implementations – pretty much hard-coding one case after another, rather than actually coming to an algorithm. We tried testing different numbers in an attempt to make the tests “smaller”, but with no success.
Having had more success with a different problem – an OCR parser for converting representations of numbers in underscores, spaces and pipes to the corresponding number – I realised what the trouble was. In trying to write “small” or “simple” tests for the Roman Numerals exercise, we had been getting bogged down in writing tests for test cases, instead of writing tests for the small or simple parts of the problem. We had been thinking along the lines of “1 returns I” or “6 returns VI” instead of testing that, for example, the method returned a string representation the of the number. In the OCR exercise, we sat down and discussed the problem before we touched the keyboard, and we didn’t focus on tests for the test cases but tests for the component parts of the problem (representing a digit, converting a digit representation to a number, etc.) – and we ended up with working code in the time available.
There’s a clue to the need for the latter approach in Uncle Bob’s blog post The Craftsman 62, The Dark Path. It quotes from The Moon is a Harsh Mistress
when faced with a problem you do not understand, do any part of it you do understand, then look at it again.
In fact, when I re-did the Roman Numerals exercise taking the small-problem approach, it turned out that a lot of time could be saved by identifying and testing the key features of the problem. After testing that the method returned a representation of a digit which mapped to a single Roman numeral, I next tried testing that it returned a representation of a digit which mapped to a repetition of a single Roman numeral. In fact, my next test turned out to be a better approach to the problem: I tested that the method returned the correct result for a digit which mapped to “addition” of Roman numerals, which covered the previous case as well. With this approach a solution presented itself surprisingly quickly.
So TDD doesn’t have to lead to clumsy implementation – by properly understanding the structure of the problem it’s possible to write small tests and still come up with a solution. Look forward to trying it out on our next training day.