Monday, February 21, 2005

Variations on TDD

TDD is the cornerstone of XP. TDD requires one to write tests ahead of code. The basics of TDD are laid out in books by Kent Beck (Test Driven Development By Example) and others. However, as time has gone by and XP has evolved, it appears as though different ideas about how to write code test-first have emerged, all claiming to be TDD. Since I am a strong advocate of XP and TDD, I thought I'd lay out some of these variations I've seen in the wild:

  1. Traditional TDD: Development is done one simple test at a time. Refactoring is applied frequently at a small scale. One starts with an intentionally naive design and allows the addition of new tests to guide the evolving design to greater levels of complexity. One can do some up front thinking about a problem, including brainstorming, using diagrams or crc cards, but the focus is to keep the design as simple as possible with each new test
  2. Proactive TDD: Proactive TDD is similar to traditional TDD in that one uses small-scale tests. However, one generally develops a fairly established design ahead of time and the tests are written to realize that design. This approach is less flexible than traditional TDD, but the feedback cycle is still short and provides the opportunity to re-think the design if it looks as though it is going in the wrong direction.
  3. End-To-End TDD: In this variation, one does not write small-scale tests. Instead one starts off working with what are essentially acceptance tests: High level tests that cover the execution of code for a story or a substantial part of a story. Such tests may use something like the FIT framework or they may involve calling a high level API inside of xUnit. To make one test pass, quite a bit of code must be written through all the layers of the application. The presumed goal of this approach is to avoid having to modify many small tests as the application logic changes. The drawback is that this is not really a good design technique since one cannot apply a short test-code-refactor cycle. Overall I would say this approach discourages frequent refactoring and leads to low quality code. If one does decide use this approach, In my opinion one should be prepared to do a lot of careful design up front.
  4. Mock-Driven Development: I am unfamiliar with this approach, but there appears to be a school of developers who produce even more granular tests with the extensive use of mock objects. The sense I get is that this kind of development is perhaps more similar to Design By Contract and ties tests very closely to the implementation details of an API. So far I can't evaluate this method, but I have to admit that I am biased in favour of testing state before and after a test rather than worrying about the actual calls made.
Based on my experience thus far, I consider Traditional TDD to be the best approach in most cases and Proactive TDD to be acceptable in some cases. I believe that all in all traditional TDD minimizes the risk of ending up with unmanageable code. While I am a strong supporter of acceptance tests, I believe that all application code should be developed using a short test-code-refactor cycle, and therefore one should use traditional TDD techniques to write the code that will eventually make the acceptance test pass. Without the traditional TDD, one loses the ability to frequently refactor the code in small chunks and to get a good sense of its overall effect on the design.

No comments: