There's a common misconception that test-driven development is a testing technique when in fact it's a design technique. In this column, Jeff Patton explains this and how you might use your unit tests to explicitly guide and describe the design of your software.
For those already doing test-driven development, you probably know that test-driven development is not testing. So, go ahead and grab a cup of coffee, but you should keep reading to see if you agree or disagree with everything I'm saying.
For a few years I've been using unit testing frameworks and test-driven development and encouraging others to do the same. The common predictable objections are "Writing unit tests takes too much time," or "How could I write tests first if I don't know what it does yet?" And then there's the popular excuse: "Unit tests won't catch all the bugs." The sad misconception is that test-driven development is testing, which is understandable given the unfortunate name of the technique.
If I were designing a bit of code the old-fashioned way, I'd think about the object I was about to create, what it would do, and maybe even write it down or draw a UML diagram. Then I'd write the code. But now I'd start by writing a unit test in my favorite unit-testing framework. Here's where I get to have fun and pretend I've already written the code for my object. So, while still imagining my object, I'd ask, "What would its class name be? What other objects would it collaborate with? What methods would I call on it? What would they return?" In my unit test, I instantiate the imaginary object and ask it to do a bit of work for me, which I would assert it did flawlessly. Afterwards, I'm left with a little bit of code that uses the object I have to build, and describes exactly how the object will be used. I've designed the object—now I just need to write it.
If you're a java developer using a newer IDE, then you know the tests you write give your IDE the information it needs to write much of the code for you. For example, the IDE might offer to create the class and any methods you'd used in the test case, saving you lots of typing. After creating the class, I implement the methods I've described. Here is where it gets fun: I know I've implemented the code as designed by running the unit test!
Once you start looking at your test cases as a description of the software design, they start to look different. Pay attention to the names of your tests. Instead of writing tests named after a method on an object you're testing, try using the test name to capture the intended consequences.
Instead of a single test for the processFoo() method on your object:
If I convert those test names to sentences, I might end up with something like this:
- Processing a Foo produces a Bar.
- Processing a Foo logs timings to the Foo Log.
- Processing a Foo with bad input throws an Exception containing the bad data.
Now my names look more like design specifications! Folks who've been doing test-driven development for years see it this way. In Alistair Cockburn's book Crystal Clear, he specifically points out the importance of readability of the test case as a design document. Nifty open source tools like TestDox from Chris Stevenson actually let you browse and write test cases in your IDE as little bulleted design documents.