After hearing the discussion about TDD by Martin Fowler, Kent Beck and DHH. I thought of putting down my 2 cents about TDD - why I follow it, and why I find it useful as a thought process for solving design problems.
I can't design or write production code, till I know the use case (which is synonymous with the test case). So, I start by thinking of a use case for which I want to write code. Now this thinking itself is ‘test driven’ for me. And based on the test cases identified, I start mulling over my design. I need to write tests before I run the code; and to confirm that my code works, I need to test it.
While building test cases on the whiteboard or as pending test cases, I identify the design patterns to be implemented. Now should I start writing my test and code, without design pattern abstraction and refactor to the desired design pattern, or jump directly into defining classes and tests using my desired design pattern? Both approaches work for me. I like to start small, so I write my first test case and write code to implement that. However, sometimes I like thinking of design upfront to avoid immediate test cases implementation rework, but will not code for it. And so I draft my test cases with the design already in mind.
Lots of times I don’t have a clue, can’t think of abstraction and design upfront, in that case starting flat is good. And after implementation of a few test cases, my code starts shaping up and I get more insight to find better abstractions. Sometimes it is difficult to write tests without even having a ‘code structure skeleton’ in place. Even though I feel drafting a ‘code structure skeleton’ is OK at times, you can avoid it by avoiding doing a 1-1 mapping of your “test class” with your “code class”. TDD also helps me with good naming, because I started with the use-case. Since I code the client (call) first, that helps me in naming it from the usage perspective and not from implementation perspective.
To get the most out of TDD, don’t be too dogmatic about its implementation; instead use it as a technique to influence the way you design. For instance, TDD is really helpful in identifying smells in design. I have seen code all test driven, but full of switch case constructs without abstractions, which entirely misses the point of TDD. In my view, if I am following TDD, it does not mean my design is good, but rather I should use it as a technique to drive my design. Here are few obvious TDD test smells that help me identify bad design,
- Difficulty in writing tests: Especially when there are too many dependencies that require too much setup code. In the Rails world for example - ‘Fat Controllers’ without services or helper classes.
- Excess tests for a single unit: This happens when classes have too much responsibility. In the Rails world, the example would be ‘Fat Models’.
- Excess assertions: This happens when one method is doing too many things or tests are written at the wrong level. One test is trying to test too many things at once.
- Cascading effects of code modification on unrelated tests: This is the case when a change in code, leads to changes required in other unrelated tests. This happens due to unnecessary mocking or testing at the wrong level. For example a change in the code for the model requires changes in the controller tests without any of the controller code having changed.
Practicing TDD also helps me with slicing and dicing stories, because TDD is a technique that helps one think of the minimal use-case that should be implemented to get started.
What are your thoughts on using TDD?