I think there's way too much idealism around TDD.
TDD is not magic. It's a methodology to help REDUCE bugs or errors. Many people seem to assume it magically makes programming bulletproof.
What are bugs? Mistakes, made by the programmer (or maybe a requirements person, etc).
How does TDD help? It insists that you write as many tests as you can think of, and that you write the tests first... so that every bit of code has some sort of test covering it.
However:
- Programmer fallibility is one of the main reasons for TDD, and so obviously you're going to get the tests themselves wrong just as often.
- All you can say about code written to satisfy a set of tests is that it passes those tests. It doesn't say that the tests are right, or the code properly handles situations you haven't thought of...
- Claiming that the tests define the requirements and an incomplete test coverage situation means the requirements were wrong (not the code) is just a semantic technicality... it's still wrong and you're just playing a blame game.
- You're obviously writing a lot more code, and now more needs to change as requirements change.
- TDD is good at the component level, but at the systems level it gets fuzzy. As systems get more complex and the number of possible interactions go up, you're going to be writing a huge amount of crazy test code, and it's going to be harder and harder to instantiate and prepare everything you need in each test. Properly segmenting module responsibilities and tasks will help here, but at some point it will simply become too complex.
- Mocking external systems can only get you so far. If an external system has a problem, very often you have no recourse but to work around the bug. Mocking based on the ideal external component isn't going to guarantee anything at all. The tests are going to turn up SOME issues in YOUR code, but not all issues by any stretch of the imagination.
- TDD is a methodology that works best on projects that are highly specified up front. There are many problem domains where a highly iterative development process works much better, and iterative problems are best solved with a well thought out architecture designed for handling change. TDD processes at best are entirely orthogonal to that, and at worst add a substantial burden.
My personal opinion is that writing tests first, and only writing code to satisfy the tests and no further... is a concept that MOST people get horribly wrong. In the hands of novices it promotes an unthinking, reactionary code design... you're coding for tests, not to any sort of architectural vision.
Programming is about problem solving and design, not about learning how to apply a process.
The example mentioned by Cervo of a "big TDD blogger" that couldn't solve Sudoku is just totally demonstrating the problem. People THINK they're becoming highly skilled and effective, but they're just getting better at a process... not the core skills of their profession. I would not take art lessons from someone who could only draw stick figures, no matter how perfect he claimed his process was.
I personally believe TDD has value in providing validation that your components or API does not break in known ways between changes... but that's about it.