XP / Engineering Practices

Test-Driven Development

A software development practice where developers write automated tests before writing the production code, then write just enough code to pass the tests, and refactor continuously.

Junior Senior Test Lead

What It Is

Test-Driven Development (TDD) is an Extreme Programming (XP) engineering practice that inverts the traditional coding workflow. Instead of writing production code first and adding tests later, you begin with a failing test, write the minimum code to make it pass, and then refactor the result. This tight feedback loop produces cleaner designs, fewer bugs, and a comprehensive safety net of automated tests.

By forcing yourself to articulate expected behaviour before implementation, TDD acts as a design discipline as much as a testing discipline. The resulting test suite becomes executable documentation that guards against regressions and gives you confidence to refactor.

Core principle: No production code is written except to make a failing unit test pass. If a behaviour is not driven by a failing test, it does not get built.

The Red-Green-Refactor Cycle

TDD revolves around a three-step micro-loop that typically lasts only a few minutes:

  1. Red: Write a small, focused test that describes one piece of behaviour. Run it and watch it fail. The failure must be for the right reason, confirming that the test is actually exercising the code you intend.
  2. Green: Write the simplest possible production code to make the test pass. Do not worry about elegance or duplication at this stage; correctness is the only goal.
  3. Refactor: Clean up the implementation and the test. Remove duplication, improve names, simplify structure. The test suite must stay green throughout. If it turns red, undo and try again.
Tip: Keep each cycle small, ideally under five minutes. If you find yourself writing dozens of lines before running tests, you are probably working in steps that are too large.

When to Use It

TDD is especially valuable in the following situations:

  • New feature development: When building fresh functionality where requirements are understood well enough to write specific examples.
  • Bug fixes: Start by writing a test that reproduces the bug. Once it fails (red), fix the bug and watch it pass (green). The test then prevents the bug from returning.
  • Complex logic or algorithms: Mathematical, financial, or rule-heavy code benefits from the explicit examples that TDD forces you to create.
  • Legacy code with poor coverage: Use characterisation tests to pin down existing behaviour before refactoring or extending.
  • Code that changes frequently: If a module is under active development, TDD gives you the confidence to modify it without fear.

TDD may be less productive for throwaway prototypes, pure UI exploration, or spikes where the goal is learning rather than deliverable code.

Key Concepts

Red-Green-Refactor

The canonical TDD rhythm. Red proves the test is meaningful; green proves the behaviour exists; refactor ensures the code stays maintainable. Skipping the refactor step is the most common cause of test-induced design damage.

Test First vs Test After

Writing tests after the fact often leads to tests that are tightly coupled to the implementation, harder to read, and less likely to cover edge cases. Test-first development ensures the test is validating behaviour, not merely mirroring the code.

Unit Tests as Design Tool

Well-written TDD tests reveal how a class or function wants to be used. If a test is awkward to write, the underlying API is probably awkward to use. TDD surfaces design pain early, when it is still cheap to fix.

Concept Description Why It Matters
F.I.R.S.T. Fast, Independent, Repeatable, Self-validating, Timely A mnemonic for qualities that keep tests useful and maintainable
Arrange-Act-Assert Set up context, invoke behaviour, verify outcomes Creates readable, predictable test structure
One reason to fail Each test should check a single concept Makes diagnosis faster when something breaks

Common Pitfalls

Writing Tests After Code

Post-hoc tests tend to confirm what the code already does rather than probe what it should do. They also miss edge cases the developer did not consider during implementation. The discipline of test-first prevents this by design.

Testing Implementation Details

Tests that assert on private methods, internal state, or specific call ordering are brittle. When you refactor, these tests break even though behaviour has not changed. Focus on inputs and outputs, not on how the sausage is made.

Abandoning TDD Under Pressure

Deadlines often trigger the urge to skip tests and just ship. This is precisely when TDD pays for itself: the short-term speed gain is quickly erased by regression bugs, manual retesting, and fear-driven development. Teams that maintain the discipline under pressure emerge with cleaner code and fewer surprises.

Remember: TDD is not about writing more tests; it is about writing better code. The tests are a side effect, not the end goal.

The New Zealand Context

New Zealand software teams are typically lean. Many companies operate with small QA headcounts or embedded testers, meaning developers carry a larger share of the quality burden. TDD reduces the manual regression burden by automating confirmation of correctness at the unit level.

Local clients, particularly in government, finance, and primary industries, often demand audit trails and evidence of due diligence. A comprehensive TDD suite serves as executable documentation that demonstrates exactly what the system does and does not do. This can shorten security reviews and compliance sign-offs.

Remote work is common in the New Zealand tech sector. TDD enables asynchronous collaboration: a failing test clearly communicates a requirement across time zones far better than a verbal description in a stand-up recording.

Career Level Guidance

Level Focus Milestones
Junior Learn the mechanics of Red-Green-Refactor; write small, readable tests for pure functions Can TDD a calculator or validation rule; understands Arrange-Act-Assert; runs tests before every commit
Senior Use TDD to drive clean architecture; refactor with confidence; teach the practice to juniors Can TDD across layers (API, service, repository); mocks only external boundaries; spots test smells instantly
Test Lead Set team standards; review test strategy; integrate TDD into CI/CD and Definition of Done Defines test coverage policies; mentors team on test design; ensures TDD discipline survives project pressure
Tip for juniors: If TDD feels slow at first, that is normal. The speed comes from spending less time debugging and less time fearfully reading code you wrote three weeks ago. Stick with it for two sprints before judging the payoff.
← Back to Agile Techniques Next: BDD →