Live · Tue, Jul 14, 2026 · 04:06 UTC Block 843,917 Fees 14 sat/vB Fear & Greed 72 · Greed
Newsletter Pro Terminal Sign in
ITop Field News.
Subscribe →
Live · 04:06 UTC Block 843,917 F&G 72
Software development Software development desk

Test-driven development: what it actually takes to make it stick

Test-driven development has been talked about for decades, but most teams that try it quietly abandon it within weeks. Here is a clear-eyed look at why TDD fails and what it actually takes to make it stick.

Open laptop displaying code next to a plush toy, set in a bright room with plants.

Photo by Daniil Komov on Pexels

Test-driven development (TDD) is one of the most discussed practices in software engineering and one of the most frequently abandoned. The concept is straightforward: write a failing test before you write the code that satisfies it, then refactor with confidence. In theory, this produces tighter designs, fewer regressions, and faster feedback loops. In practice, teams that start with TDD often drift back to writing tests after the fact, if they write them at all. Understanding why that happens is the first step to doing something about it.

Why TDD fails in the first iteration

The most common failure mode is treating TDD as a testing strategy rather than a design strategy. Developers who approach it purely as a way to increase test coverage miss the point. The discipline of writing a test first forces you to think about the interface before the implementation. That is not comfortable, especially on unfamiliar problem domains or when deadline pressure is high. The temptation to write the code first and backfill tests is almost irresistible when a sprint board is filling up.

A second failure mode is the test suite itself becoming a burden. Slow tests, brittle tests that break on trivial refactors, and tests that require elaborate setup all erode trust in the suite. When running the tests becomes something developers dread rather than welcome, the feedback loop that TDD depends on collapses. You end up with tests that nobody runs, which is worse than no tests at all because they create a false sense of security.

Team culture also plays a role. TDD is much harder to sustain on a team where code review focuses on features and treats tests as an afterthought. If reviewers do not check whether a change was driven by tests, the discipline fades under workload pressure. This is closely connected to broader questions about what high-performing dev teams do differently in code review: the best teams treat the test suite as a first-class artefact, not a box to tick before merging.

The red-green-refactor cycle in depth

The mechanical description of TDD is the red-green-refactor cycle. Write a failing test (red). Write the minimum code to make it pass (green). Clean up the code without breaking the test (refactor). The simplicity is misleading. Each phase requires genuine discipline.

The "minimum code to make it pass" step is where most developers instinctively over-engineer. The urge to solve the general case before the test demands it is strong. TDD resists this by forcing you to stay close to the test's assertion. You write exactly enough code to satisfy the test, then let the next test drive the next increment of behaviour. This produces smaller, more focused functions and classes than most developers naturally write under time pressure.

The refactor step is the one most often skipped. After the test goes green, the code works, and moving on feels natural. But the refactor phase is where the design improvements actually happen: duplication is removed, names are clarified, responsibilities are separated. Skipping it means TDD delivers test coverage without the design benefits that justify the upfront cost.

What makes a test suite trustworthy

A trustworthy test suite has three properties: it runs fast, it fails for clear reasons, and it does not break when you change implementation details rather than behaviour. These are harder to achieve than they sound.

Speed matters because a slow suite breaks the feedback loop. If developers have to wait minutes to know whether their last change was correct, they stop running tests frequently. The target for a unit test suite is seconds, not minutes. Integration and end-to-end tests will naturally take longer, but the pyramid of testing (many unit tests, fewer integration tests, even fewer end-to-end tests) exists precisely to keep the fast feedback loop available for most changes.

Tests that break for unclear reasons are almost as damaging as slow tests. If a test named testUserCanLogin fails because an unrelated database schema changed, the diagnostic value is zero and the maintenance cost is high. Keeping tests narrowly scoped to the unit under test, and injecting dependencies rather than pulling them from global state, is the practical foundation for a suite that tells you what broke and why.

Brittle tests, those that couple to implementation details rather than observable behaviour, are the most common reason TDD adoption collapses. If every refactor breaks a dozen tests, refactoring stops. The fix is to test at the right level of abstraction: verify that the system behaves correctly, not that it achieves that behaviour through a specific sequence of internal calls.

Applying TDD to real-world Australian dev teams

Australian software teams face the same constraints as teams anywhere: delivery timelines, legacy codebases, and mixed skill levels. TDD is harder to introduce into an existing codebase with no tests than into a greenfield project, but it is not impossible. The most practical approach is to adopt the "strangler fig" pattern: apply TDD to new code and to changes in existing code, rather than trying to retrofit tests onto everything at once.

Legacy code without tests presents a specific challenge. Before you can apply TDD to a change, you often need to make the existing code testable, which means breaking dependencies and introducing seams. Michael Feathers' concept of "characterisation tests" is useful here: write tests that describe what the code currently does, not what it should do, so that any unintended changes during a refactor become visible. This is closer to test-after than test-first, but it creates the safety net needed to begin applying TDD to new behaviour layered on top.

Teams also benefit from pairing on TDD. Ping-pong pairing, where one developer writes the failing test and the other writes the code to pass it, forces both participants to engage with the discipline rather than one person silently drifting back to old habits. It is also a fast way to calibrate what "minimum code" means in practice, which varies considerably between developers with different instincts.

Architecture decisions intersect with TDD in ways that are easy to underestimate. A tightly coupled, dependency-heavy design is difficult to test at the unit level, which is one reason teams on legacy monoliths find TDD harder than teams building new services. This is one of the concrete tradeoffs involved in choosing between microservices and a monolith: smaller, independently deployable services tend to have clearer boundaries that are easier to test in isolation.

Measuring whether TDD is actually working

Coverage percentages are a tempting proxy for TDD discipline but a poor one. A codebase can have high coverage with tests that were all written after the fact and provide little design benefit. More useful signals include the average time between a code change and a test run (shorter is better), the rate of regressions caught by the test suite versus caught in production (higher is better), and the average size of functions and classes (TDD tends to produce smaller units).

Defect escape rate, the proportion of bugs that reach production rather than being caught earlier, is the most meaningful long-term measure. Teams that sustain genuine TDD practice over six months or more typically see this rate drop, along with the time spent debugging production incidents. The design benefits are real, but they accumulate slowly. The teams that abandon TDD after a month rarely stay long enough to see the compound return.

Getting leadership buy-in

Developers rarely have full autonomy over how they work, and TDD does require an upfront investment that feels like slower delivery in the short term. Getting engineering leadership on board means framing TDD not as a testing initiative but as a risk management practice. Fewer production defects, faster onboarding for new developers who can read tests as specifications, and reduced fear of refactoring are all outcomes that resonate with technical leaders worried about velocity and reliability.

The argument is strengthened by connecting TDD to the broader DevSecOps conversation. Shifting quality left, catching problems earlier in the development cycle rather than in production, is a principle that appears in security frameworks and continuous delivery practices alike. A team that practices TDD is already doing a version of shifting left; the tests are the earliest possible signal that a change is wrong. Teams building on CI/CD pipelines can surface that signal on every commit, making the value visible to everyone who sees the build dashboard.

TDD is not a silver bullet, and it does not eliminate the need for other forms of testing. But teams that invest in learning it properly, who understand the design discipline behind the testing mechanic, consistently report that it changes how they think about code before they write a single line. That shift in thinking is the real return on the investment.

→ The Confirmations · Daily newsletter

One email at 06:00 UTC. Six minutes. The only digest written for desks, not for retail.