Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Beginning a new project using TDD from the outset is in many ways easier than attempting to use TDD with an existing project. By using the TDD methodology and design cycle, the full benefits can be realized in terms of the level of code produced and ensuring that the code written meets the defined needs exactly.

Red, Green, Refactor.

The first step for using TDD on a project is analyzing the task required by the customer before any code is written. What exactly does the customer need the code to do? What is the desired result for a given situation or input? A mock-up is incredibly helpful in many cases at this step, to get a clear picture of what the input and output should ultimately look like.

Once that is determined, the smallest initial step toward the end result should be identified, and a test for it written. Using unit testing, create the smallest test possible at first. The test should, when first run, fail before any code is implemented. Then code is written to have the test pass, using the simplest code possible just to get the test to pass. This is referred to as getting your tests to green, referring to the standard XUnit (such as JUnit) display of a green bar when all tests pass successfully. No code that is not being tested for should be written, despite the obvious need for such code in order to meet the customer's demands down the road. Only code being explicitly tested for should be written.

In TDD, even your class definitions are completely driven by the tests. Functionality is focused on first, and later what exactly the code being written is supposed to ultimately do. Write the simplest code possible to make the tests pass. If a test is dependent on a database that has not been implemented yet, for example, instead provide a front-end database interface that simply returns test data for the time being.

Once a task is complete, if there are certain situations that need to be tested for (negative numbers or other possible situations that need to be handled carefully), mock objects should be created within the testing framework to properly troubleshoot these special cases, standing in for the test database interface.

  • Each test should only verify one thing. Such as testing the creation of an object, making sure that the object was actually created. Or assigning a property to the object.
  • Avoid duplicate test code. Just as duplicate production code should be avoided, avoid duplication of efforts in test code. Many testing frameworks are helpful in this regard, with setup and teardown methods that can consolidate code shared between multiple tests.
  • Keep tests in a mirror directory of development code. This helps keep test code out of the way of production code, and avoids problems with languages that assume directories map to package names, such as Java.
  • If a functionality requires more than one class, add tests for each class before its implementation, then a test for the functionality. Build each step separately.

TDD is typically used with evolutionary design. Refactoring is a key step to making sure that the ultimate result is designed well, and the overall TDD cycle works hard to prevent overdesigning. Using the refactoring step of the cycle to clean up disorganized code and apply proper design patterns is one of the most important elements of using TDD with a new project.

Completing a task means all of the tests needed for that task are written, and they all pass. Once that is accomplished, a different task should be implemented, but the tests for the prior tasks are still run.

TDD provides, assuming the refactoring step is followed properly, well-organized code. Code that always does the same thing. And loosely coupled code, that is much more flexible and modular by design in order to pass the myriad of small tests.

Testing bad habits to watch out for
  1. Test code that doesn't properly test for anything, just merely compiles.
  2. Make sure the tests are testing for the actual data fed into the code by the test data, not the source of the test data itself.
  3. Ensure that the system is in a known state before tests are ran, rolling back things properly instead of leaving scraps of test results. Or, even worse, relying on those scraps being present.
Info

Information in this article from Test Driven Development by Example, Clean Code: A Handbook of Agile Software Craftmanship, and Head First Software Development