Thanks go to Kuba Miara for inspiration.
Last time I was debating my colleague on pros and cons of TDD, I made a mistake. I stressed too much that TDD is good, because it quickly shows you when the design is wrong and punishes you for not writing clean, properly encapsulated code. I did not talk so much about the support TDD provides, just about the punishment. By doing this, I got two counterarguments. Both of these are interesting and worth discussing. Today, I'd like to tackle the first one.
Teaching by punishment is too harsh.
The argument was like this: "If TDD is so demanding, it will be hard for people to satisfy these demands. There are better and worse developers. Some people are having hard times writing good code, now they would need to know how to do it plus how to write good unit tests. Since the latter is a challenge on its own, they're just gonna pay double.". This is a vision of TDD as a "strict teacher", that only makes demands and punishes you severely, when you're unable to make it. This is also a vision of a "bad teacher" that ONLY makes demands and it's the only kind of help it can provide.
This, however, is not true. TDD is a good teacher. How do you recognize a good teacher? Such teacher would:
- tell you when you're getting things wrong.
- support you in getting things right.
In TDD, the first is achieved by using various heuristics when looking at unit tests. If you're doing things wrong, your unit tests will tell you this (Amir and Scott have a nice summary in two parts of these heuristics). Now I'd like to concentrate more on the "supportive" stuff that I kind of missed during the original discussion - there are various ways TDD can support you in doing the right thing(TM). Among these things are:
- Need Driven Design
- helps in discovering new classes, methods and collaboration models as well as gaining high encapsulation and outside-in insight into your code (crafting method signatures from the view of their concrete usage, not from the top of your head), at the same time providing you a way to be more "lean" and write only the code you really need. I will do a separate post on Need Driven Design soon.
- Test First
- helping in many thing, but this time I'd like to stress that it lets you create classes with high testability by default - since you're writing the test before the code, you design the production code with testability in mind and it brings with itself stronger cohesion and encapsulation plus looser coupling.
- Given When Then Analysis
- helps you to think about the code in terms of behaviors, discover holes in requirements, prioritize the implementation and enables deliberate discovery of new behaviors the system must support.
- TODO list
- helping you in keeping track of the things left to specify, refactor and implement, so that you can know when you're done. Additionally, TODO list helps you in keeping focus on single behavior at once ("hmm, here, I'm passing two strings, but what if the first one is null? I'll just add it to TODO list and get to it after I specify current behavior"). Kent Beck's Test Driven Development By Example provides some good examples on how to work with TODO lists.
- useful when driving implementation of a piece of code we don't have an idea how to implement. By specifying example after example of how we expect a single behavior to work from the outside and refactoring each time into something more general, we gradually gain understanding of the problem and drive towards the right implementation.
Each of these tools that come "bundled" with TDD has its own set of best practices that can make even a mediocre developer write good code quickly. There is only one requirement: motivation.
Ok, that's it for now, in the next post, I will try to discuss the second part of the argument. See ya!