Doing TDD Well

The TDD Cycle

Ensure that you follow the rule of running your test through your test tool first, to observe test failure. Not following this rule takes you out of the TDD rhythm. I've seen people burn an hour because they hadn't been compiling before building. They thought the constant green bar was a good thing.

Take smaller steps than you are taking today. Rapid feedback is part of what makes TDD work. The shorter amount of time between introducing a problem and corresponding negative feedback, the easier it becomes to correct, and thus the more rapidly a quality solution can be built.

Adhere to the ten minute rule: If you haven't seen a green bar in the last ten minutes, your solution is going awry. Discard code until you revert to the last green bar, and start over again. Take smaller steps this time.

Run All the Tests

Don't skimp on the number of tests you run for each change you make. Many developers will run only the tests for the current class they're developing. Others will run only the tests for the current package. Few run "all the tests, all the time," as the TDD mantra goes. It's seemingly faster this way.

But, not running all those other tests can temporarily mask problems. The longer it takes before you find out that you've introduced a problem in another area of the system, the longer it generally takes to resolve. Further, the insistence on running all the tests may go a long way toward ensuring that the tests do run fast, and toward ensuring that they are closer to true unit tests.

Keep the Build Green

I've seen some teams allow developers to check in code without running a complete suite of tests. The premise is that the continuous integration server will kick off soon enough, notifying the team so that any mishaps can be corrected. Although this may save time for the individual, the rest of the team that happens to check out the offending code is only going to lose time on the occasions that the integrated code is broken. Further, effort that the offending developer expends between the time of code checkin and getting failure notification is often wasted.

Refactor Zealously

The mark of an expert TDD developer is not so much in the quality of their tests but in the vehemence with which they attack problems in code during the refactoring portion of the TDD cycle. Newly introduced duplication and unexpressive code is corrected immediately so that the master developer doesn't let the code base get any worse. A single case of two lines of duplication isn't too trivial to eliminate. A construct name can often be improved, and that might happen several times.

Don't Forget OO 101

Fundamental design concepts, such as the notions of high cohesion and low coupling, aren't discarded when practicing TDD. The refactoring portion of the TDD cycle allows the more experienced developers to take advantage of all such design tools and concepts in their background.

Heed Cohesion in Tests

A single unit test is a test case—a single scenario. A given public method might have a half dozen or more tests around it. Tests should be short, concise, and verify one thing; their name should accurately describe the goals of the test. Too many asserts is an indicator of either a test or a design that's doing too much.

Don't hesitate to split a test class into two, if that allows you to take the most advantage of setup common to a subset of the tests.

Rename Tests Continually

Don't dwell on the first name for a test, because you should revisit it frequently. Upon completing the test, revisit its name, and improve upon it. Revisit the name within the context of other tests for the same class, and ensure that the names are consistent. Consider naming your tests using BDD (Behavior-Driven Development) naming conventions.

Don't Overuse Mocks

Mocks by definition violate encapsulation: They require test client knowledge of a target class's implementation details. Used judiciously, mocks are an extremely valuable tool. Used extensively, the violation of encapsulation can actually inhibit refactoring: Changes that only move around implementation specifics can break tests, requiring developers to take the time to fix them. Therefore, most developers faced with this problem choose not to refactor. Making refactoring more difficult is a rapid path to a rigid, lower quality system.

Master Good Tools

Although this has little to do with TDD, I've seen too many TDD developers struggle with their tools. This includes editors/IDEs, languages, build tools, operating environments, the keyboard itself, and so on. Most importantly, a developer must be the master of his or her preferred developer's environment. Pairing can allow developers to learn tips and shortcuts that they might not learn otherwise. The rapid cycles in TDD are most effective when the programmer is capable of rapid coding through tool mastery.

Use a real tool, and take advantage of it. Eclipse and vi are real tools when mastered. Notepad is not. Generic programmer's editors like UltraEdit are good, but you can usually do better.

Don't Code for the Future

Yes, you will need a std::vector in about five minutes, once you've gotten the current assertion to pass. For now, a simple counter is sufficient. Sometimes, the need for the vector never comes, and you've added unnecessary complexity (and thus cost) to the system. More importantly, you're adhering to the true incremental nature of TDD. This will force you to write more assertions, to drive specific solutions into generic ones.

TDD is not a mindless technique. Instead of worrying about the near or distant future, you're best off thinking, and hard, about the present. Relevant actions include digging hard to ferret out all of the code's problems in the refactoring portion of the TDD cycle.

As you work, you'll no doubt be reminded of things that you must take care of at some point in the future. Keep a simple to-do list, whether it be in comments or on a napkin. You might check in these to-do items, but resist letting them ship past an iteration.