Jeffrey’s post on Rocky Lhotka’s off the cuff remarks about TDD is spawning a good discussion and several rebuttals. Several people have gone off on agile zealots as well. I’m pretty sure I’m included in the zealot bag there. We all like to think we’re one of the reasonable people and everyone else is the crazy zealot, but…
[EDIT 4/5/2006] Of course I also think the words “jihad” and “zealot” got tossed around far too casually and unfairly. If you go read Jeffrey’s post, he didn’t say anybody was a bad developer for not doing TDD, just that Rocky made some erroneous or mistaken statements about TDD.
Rocky’s response here:
I’ve seen several common misconceptions, or just plain concerns and doubts, about TDD repeated that I’d like to respond to. I’ll especially pick on Sahil’s comment on Rocky’s post just because he’s Sahil.
TDD can, and probably should, coexist with other design tools and methodologies. Rocky and several other people mention they prefer using Responsibility Driven Design with or without CRC cards. I wholeheartedly agree (but I’ve never managed to get into CRC cards). I had an article last year on using RDD to make TDD easier. Use anything that works for you (I still like to sketch out UML just before coding), as long as testability is always in the forefront of your design thinking. It will make TDD easier. Nothing is more difficult to me than just sitting in front of a blank screen trying to figure out how to write that first test.
The great thing about TDD as a design tool is that it really enforces loose coupling and high cohesion. Forcing yourself to write a unit test first, or at least immediately afterwards, makes loose coupling a necessity to retain the hair on your head. One way to think about it is that testability is frequently an accurate “test” of your design. External dependencies are always difficult to test, but code that is hard to test usually points to an issue with coupling or cohesion.
This is close to a cliche and it’s already been said several times, but TDD is primarily a design tool. The fact that the “design specification” is a large body of executable unit tests is an undeniably huge benefit as well.
Occasionally you’ll see a claim that TDD == 0 debugging. That’s obviously false, but effective usage of TDD drives debugging time down and that’s still good. From my experience, when the unit tests are granular the need for the debugger goes way down. When I do have to fire up the debugger I debug through the unit tests themselves. Cutting down the scope of any particular debugging session helps remarkably. The caveat is that you really must be doing granular unit tests. A lot of debugging usage is often a cue to rethink how you’re unit testing.
TDD isn’t enough testing by itself. Duh. I don’t think many people are trying to claim TDD does away with the need for testers. The fact that TDD isn’t a comprehensive set of tests doesn’t mean that the TDD unit tests don’t add a lot of value. We use a full Taxonomy of Tests, not just our TDD tests. I’ve seen a very correlation between the thoroughness of our unit tests and the bug counts in different areas of the code. Sahil mentions that TDD doesn’t directly address concerns like load testing, transactional rules, and concurrency. Ok, then do some load testing and exploratory testing too. You can test performance and transactions with TDD, it’s just harder. What TDD excels at is getting the business functionality and mainline logic correct. Get that out of the way and you should, at least in theory, be able to spend more time on other types of testing for non-functional qualities.
And the constant stereotype about developers being bad testers, that’s just unfair. It’s like saying developers don’t have good social skills, wait, nevermind.
You must write a test first! I probably write test first about 80-90% of the time. There’s always exceptions. I certainly don’t spend any time writing unit tests for simple getter/setters, especially when they’re codegen’ed by ReSharper. Just use your best judgement. One rule of thumb I’ve read from Kent Beck is that you just need to TDD any code that might change.
We’ll have to maintain all of those unit tests. True, but in general the unit tests should make your code easier to maintain, and not just because of the automated regression safety net. Unit tests can be a great living document about the system. The ability to refactor a system safely is priceless. Now, there is a need to write unit tests well in order to keep them from being a burden. I’ve bumped into places where the unit tests were written in such a way that I was afraid to change the code for fear of breaking the unit tests. It’s awfully important to treat your unit test code as something important and keep loose coupling between your tests and the implementation. That’s often a very real complaint about excessive usage of mock objects. There’s more than a little bit of art to writing non-brittle unit tests.
Just to head the comment off, there might come a day when our programming languages reach a high enough level of abstraction that the usefulness of TDD goes away because the code will perfectly express its intention. That would be nice, but we’ll just have to wait and see.
I do feel a bit for Rocky on this one and I know that I jumped the gun a little bit. I think if he’d just gotten to say something like “I haven’t done much TDD yet, but here are the things I don’t buy about TDD” this wouldn’t have been any big deal.
As far as being a zealot, yeah I’m definitely going to tone down my blog. I’ve never enjoyed working with runaway XP zealots, so I do get the irritation.