Learning TDD

A friend described recently what happened when his some of his team started learning TDD.

They were doing the Roman Numerals kata, where you write a class to convert arabic numerals into their roman equivalents. So for example; an input of ‘7’ gives an output of ‘VII’.
The code to support the first test was easy.

public String arabicToRoman(int input) {
    return "1";
}

So was the second.

public String arabicToRoman(int input) {
    if (input == 1) {
        return "I";
    } else {
        return "II";
    }
}

However by the time they got to the third there was a problem. The code turned into a switch statement. Everyone knew that as the numbers got larger, a better algorithm would be needed, but didn’t know when make the change.

What was missing?

One of the key steps in TDD is to refactor to remove duplication after you get the test working. That’s key, but isn’t always fully appreciated.

  • Duplication refers not just to code blocks but of logic as well. That seems to be the case here
  • The open-closed principle may also help when deciding when to refactor. In this case the switch statement must be modified every time a number is added.

Refactored logic might look like this. An aggressive refactorer might even have done this at the second step:

public String arabicToRoman(int arabic) {
    String roman = "";
    for (;arabic > 0; arabic--) {
        roman = roman.concat("I");
    }
    return roman;
}

Of course the algorithm will continue to evolve as we add support for more numbers. And the tests need to be refactored as we go, just as aggressively as the production code.

So what?

To some, this might seem obvious and maybe simplistic. But its easy to miss subtleties when trying out the mechanical aspects of a new practice such as TDD – I’m sure I did something similar. Exploring a few blind alleys is a natural part of learning.

.

Advertisements

2 thoughts on “Learning TDD

  1. Jake Radakovich April 3, 2013 / 1:20 am

    Great post. One problem I’ve noticed with TDD (actually, unit testing in general) and the use of dependency injection frameworks is the fact that you have to tie the tests to the implementation (i.e. expecting method calls and specifying the data they return). I’ve found this to be counter productive to the essence of unit testing and the ability to refactor. If you change the method you are testing, it will often break the tests. Do you have any insight into this?

    • njd101 April 7, 2013 / 6:24 pm

      Thank you.
      Not sure I have any great insights here. If you change the method signature or the data returned then that breaks the tests, but that to me seems to be fine. If the contract changes then so must the test.
      Refactoring the internals should not break the tests. One practice that does (for me anyway) seem to make the tests more brittle is explicit tests for all the helper methods that are not part of ‘public’ contract. If you have those then almost every refactoring is likely to also require refactoring at least one test.
      If those helpers can be tested though the main ‘public’ method, then maybe they don’t need explicit tests of their own and so refactoring is easier.

Comments are closed.