Blog / Test the layers that pay: coverage is not a strategy

Test the layers that pay: coverage is not a strategy

Asked to estimate 'adding unit tests' to a mature codebase, the honest answer is that the estimate is the wrong question.

Published

June 2026

Length

2 min read

Topics

Testing · Delivery

Asked to estimate "adding unit tests" to a mature application, the honest answer is that the estimate is the wrong question.

The request arrived as a number: 104 developer-hours to stand up a test framework and write happy-path unit tests for "controllers and repositories." Before arguing with the hours, I counted the estate they were meant to cover: 38 API controllers exposing some 260 public actions, 38 repositories, 63 UI components plus another 30-odd services — against six front-end test scaffolds and a .NET test project holding 13 tests, 12 of them failing. The only mocked-repository test in the suite failed inside its own hand-rolled mock harness, which is a fair preview of what happy-path mock tests buy you.

Spread 104 hours across that surface and you get a thin film of tests that verify plumbing: that a controller calls its repository, that a mapper maps. Tests that fail when you refactor and never when the business breaks. The research says the quiet part out loud here — Inozemtseva and Holmes measured coverage against test-suite effectiveness across 31,000 generated suites and found the correlation drops to low-to-moderate once suite size is controlled for; their conclusion, verbatim, is that coverage "should not be used as a quality target."

What survived scrutiny was a layer strategy at the same total budget, reallocated. Integration tests against a real database for the five to seven repositories that change most, because the data layer is where a decade of business rules actually lives. Unit tests reserved for genuinely branchy logic: a handful of calculation-heavy controllers and the currency-conversion code, where bugs are silent and expensive. End-to-end tests — expanding the Playwright suite that already existed — over the few user journeys the business runs on: sign-in, the deal-pipeline flow, the approval chain. And a skip list with names on it: seventeen thin CRUD controllers and a pile of lookup repositories get nothing, deliberately.

One coverage number did survive: a gate on new code only. That's the version of the metric even Google's testing blog defends — a high number guarantees nothing, but a low one does guarantee untested code, so hold the line where code is being written today rather than buying retroactive percentages on ten-year-old modules.

The principle: test value comes from choosing the layer, not raising the count. A coverage percentage is the easiest metric to move and the least correlated with sleeping well after a deploy.