Some ranting ahead… …or just skip to the second paragraph
I’ve never, ever seen any serious minded person claim that TDD is a silver bullet that’s going to stop all defects from getting into your system. I have heard some people say TDD doesn’t work because it’s impossible to test the user interface, or threading code, or performance. Even assuming that that’s all true, and it’s not by the way, does that really mean you shouldn’t bother with TDD? A couple months ago there was a bit of a flame war going on kicked off by Rocky Lhotka’s remarks about TDD on DotNetRocks. A frequent point of severely flawed logic from the non-TDD camp was “yeah, but TDD doesn’t eliminate the need to test your code.” Agreed (duh), but in what possible way does this statement invalidate TDD as a practice? Compiling doesn’t find every possible coding problem, so should we throw that out too? The last straw was a troll-ish comment on my blog yesterday that went something like “ha ha, you had a bug, guess that TDD stuff doesn’t work after all. LOL!” Ignoring the irritating puerile nature of the comment, it’s a myopic point of view.
In Steve McConnell’s Code Complete (v2) book there is an entire chapter devoted to removing defects from code where he compares the effectiveness of a variety of practices in detecting and removing defects from code. As I recall from the book, no single technique was shown to find all problems, or even most of the defects. McConnell’s strongly stated advice was to combine as many practices as possible to create a layered defense against defects. That stack of practices could look like this:
- JAD sessions – we don’t do nearly enough
- Whiteboard designs to work through design and architectural issues
- Simply compiling your code find syntax problems
- Test Driven Development
- Integration tests
- Pair Programming / Code Inspections / Code Reviews
- Demo screens in progress to users
- Acceptance Testing with FitNesse, Selenium, etc. By being able to run automated tests on a developer workstation, we won’t even turn over code to testing until all of the acceptance tests pass. It’s a much, much faster feedback cycle than just throwing the code over the wall to testers.
- Continuous Integration – finds environmental setup issues, tests the deployment, continuously ensure the code compiles and all of the automated tests run successfully
- Manual Testing – no you can’t eliminate manual testing, but you should leave that for creative “exploratory” testing
- User Acceptance Testing – I’ve always thought it was really dumb to leave this strictly for dead last, but…
None of the practices solves every problem, but the combination of the practices is powerful.
A nearly indisputable piece of software lore is that defects are cheaper to fix the sooner they’re detected. Think about this for a second, how much faster and easier is it for you to fix a problem if you find it when you’re coding initially versus letting a tester find and file a bug report? How much more bureaucratic crap do you have to go through to fix an official bug versus simply changing the code on your own workstation?
One of the specific areas of value provided by TDD is the rapid feedback on your code. TDD is more effective than just plain old unit testing. Real “Test First” TDD gets unit testing injected earlier, and more thoroughly, into the coding lifecycle. Leaving unit testing for later can easily lead to code that’s difficult to test (I’ve seen this happen far too many times), and it’s difficult to go back over every case. The problems that are exposed by TDD can be fixed fast and immediately, long before a tester gets a hold of the code or you get stuck in marathon debugging sessions. If nothing else, you can provide more time for the testers to work through the harder, exploratory testing instead of writing up endless bug report lists for mainline business functionality.