A few months ago I was visiting a client who was having a lot of problems using TDD.
“It takes over half an hour to run our unit tests,” he said.
“You are not doing TDD,” I said. “In order for tests to be valuable, all of them must run fast—within a few seconds, or developers will stop running tests frequently.”
“But how do I make them run fast?” he asked. “Just connecting to the database takes 30 seconds.”
So I showed him a technique called Dependency Injections that allowed him to insert a mock object instead of the database. “You don’t need to test the database,” I said. “Remember, a test should test everything NOT under its control, so you should mock out all dependencies to get them under your control.”
Another problem they were having, which I hear a lot lately, is that their tests seem to be more of a burden than a help. “It will take a day to refactor the code and three days to refactor the tests,” is a statement that I often hear from clients.
This problem stems from thinking about TDD as a form of QA. TDD is not QA. QA is about insuring something works correctly; TDD is about expressing the intention of the system using assertions. In QA, we try to assure quality by testing as many things as possible that could go wrong; in TDD we try to express the intention of the system in as few tests as possible.
Most developers that I’ve met believe that code quality is a good thing, so they strive to write high-quality code, and their code is always clean. But tests are also code, and I often see poor code quality propagate in tests.
For example, I have seen the same developers who carefully removed redundancy in their production code write a huge number of redundant tests. Redundant tests are bad for the same reason that any redundancy is bad—it makes extra work refactoring if something changes.
Tests are most valuable when they break—at least that is when I am most glad I have them. But for any conceivable single change to a system, only one test should break. In other words, no two tests should break for the same reason. This is easier said than done but if you follow this rule, you will find it much easier to refactor tests when you refactor your code.
In summary, tests are code too, and their quality should be kept high. Each intention in the system should be expressed once in a test, and no other test should fail for the same reason.
What do you think? I’d love to hear your thoughts, ideas and suggestions. Please feel free to share your comments below.