TDD Can Fail

As valuable as TDD is, I’ve also seen it utterly fail in some organizations. A client once told me, in no uncertain terms, that TDD doesn’t work. He said they’d had to abandon it. When I asked him why, he said it would take a day to clean up their code and a week to clean up their tests.

They were under crunch time, so they had the choice to either continue with TDD and cause the project to fail—and watch the whole company then go out of business—or stop doing TDD. If anyone is faced with that choice, abandoning TDD is most certainly the right thing to do.

Don’t try to implement TDD when you’re ready to release. Don’t put the burden of a new learning curve on your developers when they can’t handle it.

But this client was doing something wrong. They’d bought into code quality, they’d bought into CLEAN code, they’d bought into good development principles, and so on, but they failed to see tests as code, so they had enormous redundancy in their tests. They were seeing tests as things tacked on rather than things that are integral to the whole.

They put their QA hat on and said, “More tests are better tests,” and therefore they wound up writing too many tests and their tests were testing against an implementation—the way they do things—instead of testing against an interface—what they want done. As a result, it made it hard to clean up their tests when the time came to clean up their code. Remember, unit tests are about supporting you in cleaning up code, so we have to write tests with supportability in mind.

The same client said to me, “It takes hours to run our tests so we don’t run them very often. Why are they so slow?”

Then he added, “Well, we have to get a connection to the database, and then we have to use the database to do some stuff…”

And I said, “You’re not Oracle. You’re using the Oracle database, but you’re not Oracle. They’re down the street. Why are you testing Oracle’s code?”

“Our code interacts with the database so we need to open the database so we can interact with it.”

That’s not how we write unit tests.

Unit tests are only meant to test your unit of behavior.

If you interface with the rest of the world, you need to mock out the rest of the world so that you’re only testing your code. I showed that client a whole variety of techniques on how to make untestable code testable using mocking, shunting, dependency injection, and endo-testing, among other techniques.

All these techniques can help you only test the unit of code you want to test. When you only test the code you need to test, you wind up with tests that run really fast.