Test-Driven Development

Last modified by chrisby on 2024/08/27 08:42

Before writing the production code, developers write automated tests that should pass once the production code is implemented. You can find a concrete example workflow that includes TDD and versioning with git here.

Benefits

  • Testing is Cheaper: Bugs are expensive, writing tests is cheap.
  • High Quality: Writing testable code forces the developer to apply design best practices on the fly, resulting in a testable design.
  • Speed and Safety: Code changes that inadvertently introduce new bugs are caught immediately, ensuring correct code behavior at all times, enabling faster overall development.
  • Courage: There should be no fear of making changes to the code, such as refactorings. Testing ensures behavioral stability while leaving the implementation open to change.
  • Fun: Writing tests first is more fun because writing production code afterwards becomes a challenge to be solved by trying to pass the test. On the other hand, writing production code first makes writing test code boring and feels like an unnecessary burden because we already know how the code works.

Three Laws of TDD

  1. Write no production code except to pass a failing test.
  2. Write only enough of a test to demonstrate a failure.
  3. Write only enough production code to pass the test.

Remarks:

  • Not compiling is a failure: For example, if the test code requires a function from the production code that has not yet been defined, compilation will fail. Since the test fails, you must next go to the production code and make the test pass.
  • Oscillating Workflow: In practice, you start writing a test until it fails, then switch to production until it passes, switch back to test code until it fails, and so on until the feature is complete. This leads to a very fast oscillation of switching between test code and production code. Run tests on the code you are working on at least every few minutes.
  • Minimal Debugging Time: The very short TDD cycles result in much less time spent debugging. Bugs still occur, but they are contained in the last incrementally changed small snippet of code, making them trivial to find and fix. Experienced developers are capable of debugging, but they do not spend much time on it because of the consistent use of TDD.
  • The tests need to run fast so as not to block the developer from doing his task. See this test execution speed enhancing article.

Code Coverage

  • Incomplete Validation: Passing tests doesn't mean that everything works, just that the tested parts aren't broken. Code coverage is a good first indicator of whether there are enough tests.
  • Almost 100% Code Coverage: Code coverage needs to be high, ideally 100%, but in reality the practical achievable is in the high 90s. But code coverage alone is no guarantee of useful tests, so it should be taken with a grain of salt. It is better to look at the tests to get an idea of the actual quality of the test suite.
  • Technical Metric: TDD is a technical agile practice, and therefore code coverage is a metric for developers to make decisions about, not the business.

Red-Green-Refactor

This is a simple model that describes the iterative TDD and refactoring workflow:

  1. Write a test that initially fails. (red terminal output)
  2. Implement production code to pass the test. (green terminal output)
  3. Refactor and make sure the tests still pass.
  4. Repeat until done.