Taste-Tested Coding

Use Kent Beck's Test-Driven Development By Example as a cookbook for your next project.

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:

  1. Quickly add a test.
  2. Run all tests and see the new one fail.
  3. Make a little change.
  4. Run all tests and see them all succeed.
  5. Refactor to remove duplication.

Beck walks through these steps over and over again in building the Money component.

Developer Central Newsletter
Want to read more of Mike's work? Sign up for the monthly Developer Central e-newsletter, including product reviews, links to web content, and more, at http://lists.101com.com/

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 broken—in 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.