The Case for Test-Driven Development

Not everybody is convinced that Test-Driven Development (TDD) is a good idea. So here’s my attempt to change that ;). Changing the world is hard, though, so this will be a long post.

There is value in faster feedback: it allows us to steer earlier, giving us a better chance to accomplish our goals. In software development, the only reliable way to get feedback is by trying the software out in the wild, i.e. by having real end users perform their work with it.

Don’t be fooled into thinking that having someone review a design document is appropriate feedback. The reviewer does give feedback, of course, but not of the kind that will guarantee that we’ll ship a workable product. Just as the original author of the design document may miss things, so may the reviewer. The only way to be sure that our software works, is to see it working for our users. That doesn’t mean a design review isn’t valuable, just that it’s not enough. We need defense in depth all the way through.

So, the only real way of ensuring early feedback is to deliver working software frequently and get it into the hands of end users. This is done in iterations, short time-boxed periods of development. Since we value feedback, we want our iterations to be as short as possible. And short iterations require automated testing, because a manual tester simply doesn’t have enough time to test the entire application.

TDD is one form of automated testing. It is a way of coming up with lots of small, focused, and fast tests. That’s why it’s more suited for rapid feedback than automated acceptance testing. But again, we need to use all techniques in our defense in depth.

So that’s reason #1 for using TDD: it’s required to get real feedback early and often, which gives us a better chance at delivering on time, on budget by allowing us to steer often.

Quality: reliability
There are at least two aspects of quality in software development. The most obvious one is defect density. This is the external view of quality, the one experienced by our end users, and therefore the ultimate one.

Traditionally, quality was achieved using inspection, i.e. looking at some (intermediate) product and determining whether it was good enough. Manual acceptance testing falls into this category, but so do design and code reviews.

One problem with this approach is that testing comes at the end and is thus the most likely to suffer from schedule overruns. By writing the test before the code is written, you guarantee that testing will happen, since you can’t ship without the code.

Another problem is that inspection of products is expensive. You first put effort into creating a product, then into inspecting it, then into reworking it, inspecting it again, etc. That’s why many organizations changed their ways to build quality in from the start, i.e. to inspect the process instead of the product produced by the process.

TDD is a way to do just that: build quality in, i.e. prevent bugs from happening because you write the tests first. We know the code will work, since it passes the test. And since no code is written unless a test fails, we know that all code works. (Unless, of course, a test is wrong, incomplete, or missing, which is why we also need other tools in our defense in depth.)

So that’s reason #2 for using TDD: it builds in quality, leading to fewer defects, leading to happier customers and better productivity (through less time spent fixing bugs).

Quality: modifyability
But there is also the internal view of quality. Here we’re talking about code quality from a developer’s perspective. This is obviously less important than making our customers happy, but it’s significant nonetheless. Poor code slows us down when making changes, because it’s hard to understand. And it might be correct now, but if we don’t understand it well, we have a higher chance of introducing a defect when we change it.

So, what makes code difficult to understand? It’s the complexity of the code: the more things that are going on, the higher the chance that you’re missing some of the subtle interactions between them. The formal name is cyclomatic complexity, which is a measure of the number of possible paths of execution through a piece of code. Higher cyclomatic complexity is linked to higher defects.

Static code analyzers, like CheckStyle, can measure it and report warnings when limits are exceeded, so that you can then fix the code. But low cyclomatic complexity is also a byproduct of TDD, since simpler code is easier to test. Thus using TDD prevents the rework of fixing the CheckStyle warnings.

It’s a pain to have to set up a whole bunch of objects just so you can test one of them. That’s why writing the tests first naturally encourages having objects depend on not to many other objects. This is called low coupling, and together with high cohesion, it’s also a very important aspect of code quality.

So that’s reason #3 for using TDD: it improves code quality, which leads to better productivity and fewer defects (which leads to better productivity as well).

This is where the fun begins.”

Some people claim that TDD is not primarily a testing technique, but a design technique. They say TDD stands for Test-Driven Design and the tests are just a fortunate by-product of the design process.

To evaluate that claim we need to look at what design really is: moving from a problem (requirements) to a solution (working software that fulfills those requirements). Here’s a rough outline of how we do that:

  1. We take one requirement and think of how it constraints the solution.
  2. We then think of possible ways of satisfying those constraits.
  3. We take the next requirement and do the same.
  4. We modify any solutions for the first requirement that conflict with those for the second one.
  5. We repeat until we have satisfied all requirements.

Now compare that with how TDD works:

  1. We take one requirement and think of how it constraints the solution. Then we express these constraints with one or more tests.
  2. We write the simplest code that can make the test pass and then refactor to improve the code organization.
  3. We take the next requirement and do the same.
  4. We fix any test or code for the first requirement that broke while implementing the second one.
  5. We repeat until we have satisfied all requirements.

As you can see, doing TDD is doing design. In fact, TDD is a leaner, more effecient way of doing design, since it limits work in progress. And limiting work in progess is an important driver for productivity gains in Lean, as work in progress is seen as one of the 7 forms of waste.

So how does TDD limit work in progress? First, TDD prevents rework from testing to coding by limiting the amount of untested code.

Second, it limits the amount of design decisions awaiting implementation. Any programmer who has ever been handed a design written by someone else will know that the devil is in the details and those details will require the design to be modified. (In reality it’s likely that the design will not be updated, which is even worse.)

So that’s reason #4 for using TDD: it’s a leaner, more efficient way of designing.

Don’t be fooled by the lack of a design document in the above description. Not having a design document does not mean there is no design or that there is no thinking before coding. The design is in the code, and the tests document it. That doesn’t mean we can’t write documentation, just that we don’t have to.

Not only are the tests documentation, they are a superior form of documentation: you can check whether the code matches the design by compiling and running the tests. And since tests are code as well, you get the full power of your IDE to help you keep the tests up-to-date when refactoring your code under test.

So that’s reason #5 for using TDD: it’s a leaner, more efficient way of keeping the design documentation up-to-date.

There you have it, 5 very good reasons to practice TDD:

  1. It’s required to get real feedback early and often.
  2. It builds in quality, preventing rework.
  3. It improves code quality, making maintenance easier.
  4. It’s a leaner, more efficient way of designing.
  5. It’s a leaner, more efficient way of keeping the design documentation up-to-date.

What others are saying
Don’t just take my word for it, read what others have to say as well:

  1. TDD Illustrated
  2. The Art of Agile on TDD
  3. Test infected
  4. TDD doesn’t slow you down
  5. Faster, better, cheaper! TDD wins in a simple experiment

But there is more than just opinions. Although it’s always hard to scientifically test any activity that involves humans, some experiments have been performed to assess the effectiveness and efficiency of TDD.

Moving forward
So now that you’re convinced 😉 of the power of TDD, how do you start?

Well, read the book on it. And maybe read some more on the web, e.g.

  1. The Three Laws of TDD
  2. Unit test patterns, like Four-Phase Test
  3. The TDD Checklist (Red-Green-Refactor in Detail)
  4. Keep insignificant details out of tests
  5. Test data builders

Or, you could just give it a spin. Download one of the xUnit families of unit testing frameworks, like JUnit or NUnit, and get you hands dirty. And once you’ve been there and done it, get the T-shirt.


2 thoughts on “The Case for Test-Driven Development

Please Join the Discussion

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s