Taste-Tested Coding
Use Kent Beck's Test-Driven Development By Example as a cookbook for your next project.
- By Mike Gunderloy
- September 01, 2003
Never stop testing, and your advertising will never stop improving.
David Ogilvy,
Confessions
of an Advertising Man
Ogilvy was talking about advertising (he came up with such classic ad
campaigns as the guy in the eyepatch wearing Hathaway shirts, and the
Maxwell House percolator in time with the music; see http://www.ogilvy.com/memorial/html/center.htm
for his firm's remembrance of him), but these days, more software developers
are of the same mind. I've mentioned test-driven development (sometimes
called TDD) in the past, but I think it's finally time to devote a full
column to it, because I recently had occasion to reread Kent Beck's Test-Driven
Development By Example (Addison-Wesley, 2003).
Two Simple Rules
Beck starts out his book with a brief introduction that captures
the heart of TDD in two simple rules:
- Write a failing automated test before you write any code.
- Remove duplication.
Of course, a book consisting only of an introduction isn't very salable,
so he spends another 200 pages or so expanding on these notions. The first
chunk of the book goes into "The Money Example", a classic illustration
of how TDD works. In this particular case, the goal is to add multi-currency
functionality to a Money object. Beck works in Java, but that doesn't
really matter; any reasonably experienced developer should be able to
follow along whether they know Java in depth or not.
Here's the general plan of action in a bit more depth:
- Quickly add a test.
- Run all tests and see the new one fail.
- Make a little change.
- Run all tests and see them all succeed.
- Refactor to remove duplication.
Beck walks through these steps over and over again in building the Money
component.
Tester, Heal Thyself
Having finished one example, Beck turns to a second one: xUnit,
this time in Python. What's xUnit? It's the generic sort of tool uses
by TDD proponents to run the tests. As Beck says, "Driving a testing
tool using the testing tool itself to run the tests may seem a bit like
performing brain surgery on yourself." But it works, and provides
another example of how to make TDD work. One suspects that the original
unit-testing framework wasn't developed in such an academically pure manner,
but it still makes for a good example.
Of course, by now the basic unit-testing framework (originally exposed
by JUnit for Java) has been ported to many languages. My own development
work these days is largely in .NET, and I often use the free .NET version
NUnit to run unit tests (http://nunit.org/).
There are other alternatives available as well, based on the same heritage
of running small tests and showing you the results quickly. A few that
I know about:
Patterns, Patterns Everywhere
In the final third of the book, Beck steps back to take a look
at the patterns of TDD, and tries to draw some conclusions. This section
ranges widely, from low-level tricks (how do you test an exception?) to
ways to refactor your code to eliminate duplication, from which languages
are good for TDD to how TDD fits in with Extreme Programming (XP). This
section could have become tediously pontifical, but the author keeps things
light and readable. That's in line with the whole notion of XP as being
a fun way to program. Possibly Beck is making things look easier than
they really are, but if so, he does it with enough humor that most readers
won't really mind.
The end result? If you're like most developers, you'll be itching to
try TDD by the time you reach the end of the book, if not before. And
that, I suspect, is exactly what the author is hoping to accomplish.
So Try It Already
On the other hand, most software developers seem to have a hefty
streak of skepticism in their makeup. I suspect this is because we've
all seen and heard about far too many silver bullets that were going to
make our development lives ten or a hundred times easier. Whether it's
object-oriented development or fourth-generation languages (or is it fifth-
or sixth-generation by now?) or UML, there's always something we can put
to work to turn coding from hard work into play. What makes TDD any different?
Well, for starters, no one really claims that it will make coding any
easier. In fact, it's pretty typical to start writing more code when you
commit to TDD. I find that my test harnesses run up to twice as large
as the code that they're testing. Of course, much of the test code is
pretty routine; it consists of creating objects, invoking methods, and
checking the return values. When I'm heavily into the TDD mindset, I might
write a dozen tests for a new method. When I can't think of anything else
to test, then the method is done, and it's time to go on to something
else.
The key that makes TDD work (at least for me) is the discipline of writing
the test before writing the code. That's the only way that I know of to
make sure that I really write the tests. Otherwise, they tend to be left
until, well, later. And later seldom (if ever) arrives. Writing the tests
first means the tests get written. It also means that I think about what
I'm building and how it might fail. The delay of writing the tests gives
me a little more time to mentally plan, and results in better code.
The Bottom Line
Writing better code is a major benefit of TDD, but it's not the
only one. I find that the most important plus to test-driven development
is the sense of confidence that it gives me in my code. It's difficult
to describe this feeling unless you've experienced it. By writing many
fine-grained tests, and knowing that the code passes those tests, I'm
sure that it meets the requirements, as embodied in the tests. This is
especially critical when new requirements come up that I didn't think
of when I was starting out. Surely you've been in the situation where
adding a new property required tinkering with code all over the place.
Scary, wasn't it? Well, with TDD, you can banish that fear forevermore.
The major problem with wholesale tinkering (whether exploratory coding
or refactoring) is that it might break something unexpectedly. But if
you've been doing TDD, you will have tiny tests that cover every bit of
code you've written. In that case, you can make your changes and run your
tests. Either they'll all pass (great!), a few will fail and you'll figure
out how to fix them, or things will be horribly brokenin which case
you can toss your changes out and start over. What you can avoid is the
horrible uncertainty of not knowing whether things are broken or not.
If you're like me, that translates into a direct productivity boost.
Erich Gamma coined a term for people who try and like TDD: He calls us
"test-infected." And by now you know one of the purposes of
this column is to pass on the bug. If you're using .NET, download NUnit
(or one of the other tools) and give it a spin.
Ready to try TDD? Or ready to dismiss this as just more snake oil from
XP proponents who don't ever tackle real projects? Let me know your experiences
either way by e-mail to MikeG1@larkfarm.com. I'll use the most interesting
comments in a future issue of Developer Central.