Here's an excellent article about why you should be doing Test-Driven Development.

No, really, it's excellent; go there and read it, then come back here.

A little harsh, isn't it? But very true. It's excellent.

However, something in it made me a little uncomfortable while reading, and it wasn't too hard to figure out what.

There's a lot of people out there under the misconception that unit tests and TDD are a QA method, and that if they do it right their software will have no defects (or “bugs”). That's a dangerous misconception. It's bad for your software, because it won't work; and it's bad for TDD, because when it blows up in your face, there's a pretty good chance you'll go out there telling other people that TDD doesn't work. It does work; and it probably did work for you. It just didn't do what you were mistakenly expecting it to do.

Now, if you will, go back to the article and search for any instance where Uncle Bob tells you TDD will make your software defect-free. He never claims that. The closest he says is “your software will work better”, which is true; TDD reduces bugs a lot, but most TDD champions (at least the ones who know what they're talking about) consider that a nice side-effect at best. (So if he doesn't make the wrong claim, why am I uncomfortable with the article? Because I can easily see proponents of the “TDD as QA” misconception misusing Uncle Bob's article as proof that they're right.)

TDD is not a QA tool. TDD is a development process, I'll even say a programming process. Its main benefits are, in order of (IMO) importance and relevance:

  1. Clearer and cleaner design. I'm talking about technical, architectural design, not visual. By forcing yourself to write down what you expect the software to do in a formal language (code), you come out with a clearer idea of what you're going to do; and by designing your internal APIs so that they can be easily called by unit tests, you end up with more modular and maintainable structures.
  2. Cleaner code. I've seen people whose unit tests are confusing but production code is crystal-clear. That's obviously not ideal, but it's much better than confusing production code. By focusing most of the effort in writing the test (therefore understanding what you're doing) and then writing the simplest code that makes the test pass, you make it harder to write convoluted code. (Harder, not impossible.)
  3. More confidence. Once you've written the test and you're confident that the test expresses the problem, you'll understand exactly what the solution is, and later after the code is written and deployed, you'll trust your old code a lot more.
  4. More reuse. To be honest, this isn't even about writing the test first, but in fact there's a step that often comes before writing the test: looking at the appropriate test file, reading the other tests, and checking if what you want is already there. (Because, you know, you need to find the right file in the tree and the right place in the file to add your test.) If there's something that does almost exactly what you want, and that you had never seen before, you'll write your new test and modify the existing functionality. If there's something that does exactly what you want, you save time and don't increase the code complexity.
  5. Faster. This is almost always difficult to claim, but it really does stand to reason. Think about the other benefits above; they alone make your coding a lot faster already, enough to offset the time you spend reading and writing tests. You'll end up writing less code, because you know exactly what you need and you won't write fluff. You'll end up rewriting your code less as you iterate, because writing the test made the solution clear to you. Writing code is much like the scientific method; you come up with a working hypothesis, check if it works, adapt as necessary. It might feel like we spend most of our time (in the non-TDD world) writing code, but in reality we spend most of our time figuring out stuff, followed by checking or rewriting code. Clearer code reduces time spent on the former, and writing your verification first as code reduces the latter.

As a nice side-effect, TDD also reduces defects. It does that by (a) making the design and structure cleaner and clearer; (b) making the code cleaner, therefore easier to work with later; (c) encouraging the programmer to think about the problem being solved and write “the right code”. See a pattern? And yes, (d) preventing regressions on the unit level by keeping the unit tests around to run later. But let's be honest: how many regressions are at the unit level? If your answer wasn't “very few”, there might be something else wrong with your process.

Now here's a few reasons why TDD will not take you to the magical no-bug land:

Conclusion: TDD is great for developers and you should use it everywhere. But it's not a QA strategy.