- Only write production code that makes a failing unit test pass.
- Write the minimum amount of test code to get production code to fail. Not compiling counts.
- Write only enough production code to get a test to pass.
What I see the most from the test code I read is violations of rule two. Which is very understandable. Why would you not want to write more test code? Because tests, just like every other piece of the system, have three functions:
- implement the desired feature
- afford change
- reveal intent
Writing line after line of test code to cover every possible corner condition certainly meets the first, but violates the second and third. I've seen test code that looks something like:
public void testServicesStopped() {
assertServicesStoppedIfEnabled(false, false, false, false, false, false);
assertServicesStoppedIfEnabled(true, false, false, false, false, false);
assertServicesStoppedIfEnabled(false, true, false, false, false, false);
assertServicesStoppedIfEnabled(false, false, true, false, false, false);
assertServicesStoppedIfEnabled(false, false, false, true, false, false);
assertServicesStoppedIfEnabled(true, true, false, false, false, false);
assertServicesStoppedIfEnabled(false, true, true, false, false, false);
assertServicesStoppedIfEnabled(false, false, true, true, false, false);
assertServicesStoppedIfEnabled(true, true, true, false, false, false);
assertServicesStoppedIfEnabled(false, true, true, true, false, false);
assertServicesStoppedIfEnabled(true, true, true, true, false, false);
// every possible combination was covered
}
Certainly a very thorough test, but also very very intention hiding. Unit tests should function as documentation for the system. You should be able to read the test and determine what a module does and how it does it. To make matters worse,
testServicesStopped()
was the first method in the test and assertServicesStoppedIfenabled()
was near the bottom with many other tests and helper methods in between. To try and figure out what each boolean meant, you had to scroll up and down the file and try to remember 6 parameters. I would also bet a considerable sum that the bulk of this code was written after the production code was completely finished. Not only does this test code not reveal intent, but it's very rigid. It's hard to change because it's hard to understand. When the customer wants to change how this feature works, and the developer opens this test to write the failing test so he can write the feature changes, he'll have a very difficult time figuring out what to change.Having a suite of unit tests you can run to verify your system still functions is very important, but just as important is having those unit tests clearly document the system. The easiest way to achieve this is to use TDD the way it was designed to be used by following the Three Laws of TDD.