Seeing a first-time demo of TDD, students inevitably ask, “do you really code that way?” The answer is yes, even after almost ten years of doing TDD.
They are of course asking, do I always code the simplest possible solution first, instead of just introducing the obvious solution, the one that I know I’m absolutely going to need in another five minutes? And the answer is still yes.
For example, when testing a container, the first test inquires whether or not it’s empty:
assertEquals(0, container.size());
and the passing implementation is:
int size() {
return 0;
}
The next test usually involves adding an element to the container and asserting its size (which is of course only part of the complete verification):
container.add(element);
assertEquals(1, container.size());
Yes, I know I need a [hash map|array list|linked list|etc] in about 5 minutes. I still code:
void add(T element) {
count = 1;
}
int size() {
return count;
}
Not even a ++. That comes from writing a subsequent, failing assertion.
I offer many reasons:
-
It helps deeply ingrain the notion of incrementalism, which is at the heart of agile
-
It allows sustaining the fail-first, red-green rhythm, and thus keeps you out of trouble more often
-
Similarly, it allows adhering to the rule that insists on green bars every ten (five) minutes
-
It forces the addition of more assertions
-
It keeps with the spirit of YAGNI–sometimes you don’t end up needing what you think you will
This fine-grained incrementalism involves continual movement from the specific to the generalized: from hardcoded solutions to progressively-more-generalized solutions.
Fine-grained incrementalism is, as many things in software development, a tradeoff. It results in continual waste product, and thus a slower initial pace. A hardcoded value is discarded, and replaced with a counter, which too is soon discarded. But on the other side of this trade is the significant benefit of continual and consistent forward progress–“slow and steady wins the race.”
For the new TDD practitioner, and for even experienced TDD practitioners, it is very tedious and annoying. Often, developers simply discard rigid adherence to the practice. For an experienced TDD developer, that’s their prerogative, and I’ll admit that I have done so at times too.
For everyone else, I think it’s essential to first learn the value of something before discarding it. So I’ll define my use of “experienced TDD developer” in the prior paragraph as “someone who has not done enough fine-grained incremental development to understand how it helps.” Perhaps I’m slow–I still prefer to work this way.
A final justification (aka “the new thought” that entered my head this morning):
Yes, it’s slower, tedious, and produces continual waste, particularly as you build the rudiments of a class. But these complaints are focused at the early minutes in the lifetime of a class–a fraction of the effort and struggle involved with maintaining it. There’s a benefit I didn’t explicitly mention above, and that’s the ability to more easily and safely maintain a system, because of the increased number of assertion increments. The small amount of initial tedium is worth it.
Comments
James Grenning July 15, 2008 04:02pm
Even though the production code evolves slowly, and is continually reworked, expelling tiny bits of waste… The tests are real from the beginning. The simple minded, easy to get to solutions, prove that the test are right. These tests work and do their job while the production code evolves and gets more complex. It might seem like they slow you down in the beginning, but its these tests that keep you moving rapidly with confidence.
Jeff, you’re not slow. You are the fastest I’ve seen.
Jeff Langr July 21, 2008 12:53pm
Thanks James, I particularly like the part about “moving rapidly with confidence.” Hmm, someday I’d like to participate in a head-to-head speed-TDD challenge. Time to deliver is primary, but points would be taken off for insufficient tests, or inadequate refactoring.
Who else is fast?