Where does “thinking” and “design” fit into the TDD picture?

I very much enjoyed @unclebobmartin‘s generous talk at the Java User Group Karlsruhe last night. In addition to covering rockets, linear accelerators and small nuclear bombs as a means to overcome the gravitational field of the earth, he gave a great introductory talk on TDD and professionalism. Always a pleasure to be reminded on why we are doing TDD in the first place.

One of the questions which came up during Q&A – I’m paraphrasing here – was:

“I tried TDD and was told not to design or think, just to write the tests and the code. The result was a mess”.

Bob Martin’s answer was along these lines:

If you’re a TDD newbie, you need to leave your old rationale of thinking things through upfront behind. So the recommendation is “Don’t think about the design or what’s next”, but to methodically follow the mantra “Write some failing test” – “Write some code just so the test is satisfied” – “Write some failing test” – “Write some code just so the test is satisfied” – …. As your start to grok the rhythm of TDD, you gradually let your design skills in.

I would like to humbly elaborate on this answer. The practice of TDD consists of three simple steps:

  • Step 1 – “Red” – Write some failing test
  • Step 2 – “Green” – Write some code just so the test is satisfied (implement the simplest thing that could possibly work (not sure who coined the term, probably Ward Cunningham))
  • Step 3 – “Refactor” – Remove all duplication from both your production and your test code

Resist your urge to design upfront, to think through algorithmic solutions and alternatives. Just write a failing test. Then, write some code to make the test pass. Don’t write more code. Just the amount of code to make the test pass.

Now, turn to step 3. If you skip it, you will create a mess.

Step 3 is where your design skills are more than welcome. Apply your design skills to ruthlessly eliminate the duplication you created (and you did create some duplication because you did “the simplest thing that could possibly work” in step 2, didn’t you?) – Just keep in mind that you are not supposed to create a design which anticipates future requirement changes, you are supposed to ruthlessly eliminate duplication to create “the simplest design that could possibly work” for your current code base.

So there are a lot of design skills involved in TDD. It’s just that most of the design skills are applied “after the fact”. They are applied after you made the failing test pass.

There’s another place for design skills in TDD. Look at the first step – “Red”. You are supposed to write a failing test. You will probably need to instantiate an object (which, keep in mind, doesn’t exist yet). Or call a function or method (which don’t exist yet, either) on an existing object. You can apply your API design skills right there: What’s the best way to name the class or function? Which parameters do you need? How do you minimize the number of parameters? How do you make it easy for the test (and future users of the class / function) to accomplish the task at hand?

You aren’t convinced yet? You still need more places to apply your superior brain powers? Good news, there’s even more “thinking” involved. In step 1, you are supposed to write a failing test. This requires a lot of thinking:

  • What do I test next?
  • What’s the next logical step in the evolution of my method / class?
  • What are the edge cases I need to test for?
  • Which test will small enough in scope that I can cope with the implementation right away?
  • Which test will bring me closer to the solution of the task i’m working on?

Conclusion

If you follow TDD and apply your thinking and design skills as described above, you will end up with a testable, highly decoupled design. It’s not a great design yet, but chances are it’ll be a much better and more usable design than the ones you’ve encountered so far.

Addendum

You may wonder why I didn’t mention the long and hard thoughts you need to think in step 2 while writing the code to make the test pass. Two questions for you: You did write some failing test, just enough testing code so the test fails, correct? Then you did the simplest thing that could possibly work to make the test pass, correct? Nothing more? Good. Go figure.