From a management standpoint, Test-Driven Development (TDD) can be a difficult sell. It involves a relatively large upfront investment, has no obvious business benefits, and (most important of all) the customer is paying for functionality, not tests.
Tempting as it is to cut testing in order to reach the next milestone in the project, it is a mistake because, as we’ll see, TDD can help us accelerate development and reduce costs over a project’s lifecycle.
What is TDD?
TDD is a set of principles for software development that uses testing as the catalyst for design. The TDD cycle consists of three phases:
- Write a test: the test defines the code’s behavior, the input, and the expected outputs.
- Implement: write the minimal code that makes the test pass.
- Refactor: improve the implementation, and make it more general, faster, or more readable. The test ensures that the implementation remains valid during this phase.
TDD is not a synonym for testing. You cannot simply add some tests to your project and say you’re doing TDD. For example, the old-school waterfall development method also uses tests, albeit at the end of the process, and is pretty much the antithesis of TDD.
The problem is that the testing phase comes too late. By the time tests are executed, too many design decisions have been made without grounding feedback. Serious issues revealed by tests implemented at this stage often send the project back to the drawing board.
TDD’s defining characteristic is using tests to drive design. This means that tests appear at the very beginning of the design process, before any code is written.
TDD creates a short test<->implement feedback loop that promotes fast iteration. It allows trying different designs and multiple implementations during development. This helps developers find optimal solutions in a timely manner, giving them confidence in their code and keeps the project on track.
According to a survey conducted by IEEE, TDD has several benefits:
- Shorter time to market: TDD projects finished 30% faster on average.
- Fewer defects: TDD produced code with fewer failures. Bug reduction was found to be between 40% and 80%.
- Improved quality: some projects received an 80% bump in readability, understandability, and perceived code quality
Writing an automated test suite is an investment. It takes time, effort, money and a good degree of skill and experience. There are six principles that will guide us to write better tests:
- Tests should improve quality: tests should fail when bad quality code is checked in.
- Tests should reduce the risk of introducing failures: tests should be as simple as possible to avoid introducing errors in the test code itself.
- Testing helps to understand the code: it should always be possible to refer to the test case to understand what the code is supposed to do.
- Tests must be easy to write: we should choose an easy-to-use test framework and take the time to learn it well.
- A test suite must be easy to run: ideally, tests should run automatically without any intervention.
- A test suite should need minimal maintenance: tests should not be tied to the implementation details. This allows refactoring without breaking the tests.
How TDD reduces costs
To put things in more concrete terms, we’ll define effort as the amount of work needed to implement a new feature, fix a bug, or change part of an application.
The effort is not constant; it changes as the codebase grows. In the waterfall method, the effort starts low and rises steadily. The problem is, like we saw earlier, that feedback comes too late to be helpful, so developers need to rely on guesswork and intuition.
TDD switches things around. Initially, the effort is high because we’re writing tests instead of implementing features. Additionally, we need to factor in the fact that switching to TDD takes a while to get the hang of.
Once you zoom out, however, TDD does not increase development costs. In fact, after a certain threshold, TDD becomes much more efficient than waterfall.
In a world where time is money, the initial cost of TDD is offset by the effort saved down the road. As a project advances, changing code gets easier because:
- Tests save you a lot of debugging effort.
- You have immediate feedback when something breaks.
- Having a clear direction and a robust design results in more focused code.
- Code tends to be cleaner, more decoupled, and thus easier to change.
TDD is not something that you can just plug into your development workflow. Frequently, TDD requires a cultural change, both in management and engineering teams.
Writing tests is a different skill from writing code and requires experience to master. Implementing TDD in a half-baked manner won’t get you the promised payoff for the initial investment. In fact, a poorly-written test suite will block development.
Management must set clear expectations and a coherent roadmap, ensuring that developers are on board and properly trained before attempting to adopt TDD. Failing to understand and properly apply TDD causes poor results. This, in turn, results in developers feeling like they’re wasting their time and general disappointment throughout the organization. These are a few signs that indicate when TDD is not going according to plan:
- Teams are trying to reach 100% coverage.
- A prevailing mentality that tests are only about finding bugs.
- The test suite is not easy to run.
- Not all the tests are automated.
- The test suite runs slowly.
- The CI/CD pipeline takes more than 10 minutes to run.
- Tests are too tightly-coupled to the implementation.
- A prevailing belief that TDD is a fire-and-forget solution (as opposed to an investment that needs periodic maintenance in order to keep tests fast).
Getting started with TDD can be challenging, but it’s a game-changer. The increased speed and ROI cannot be compared with other development methods such as waterfall.
To continue on your testing journey, read these posts next:
- Test-Driven Development (TDD): A Time-Tested Recipe for Quality Software
- The Benefits of Acceptance Testing
- How to Find and Eliminate Flaky Tests
Thanks for reading!