Is 100% test coverage worth it? No, and the reason isn't laziness

Is 100% test coverage worth it? No — and not because you're lazy. Coverage counts lines executed, not risk covered, and the climb from 80% to 100% is where the worthless tests breed. What to optimize for instead of a number.

opiniontest-coverageqatesting-strategy
monito

Is 100% test coverage worth it? No, and the reason isn't laziness

opiniontest-coverageqatesting-strategy
August 17, 2026

There's a specific kind of pride I've learned to distrust: the team that puts a green "100% coverage" badge in their README. Not because the number is fake — it's usually real — but because of what it quietly implies, which is that testing is a solved problem here, and the metric proves it. I've shipped bugs from codebases with 100% coverage. Everyone who's chased the number has. The line ran during a test; the test asserted nothing that mattered; the bug went to production anyway. The badge was green the whole time.

So: is 100% test coverage worth it? My answer is no, and I want to make the case carefully, because the lazy version of this argument ("tests are hard, aim lower") is worthless and gives the number's defenders an easy target. The real argument is sharper. Coverage measures the wrong thing, chasing it to 100% actively degrades your suite, and the question it's standing in for — am I testing enough? — has a better answer that isn't a percentage at all.

What the number actually measures

Start with what coverage is, mechanically, because the word "coverage" oversells it. A line-coverage tool watches your test suite run and records which lines of code got executed. That's it. It does not check that the line did the right thing. It does not check that any assertion looked at the result. It records execution, and then reports execution as a percentage, and the percentage borrows a feeling of completeness the underlying measurement never earned.

Google's testing team — who have run coverage at a scale almost no one else has — put the limit bluntly in their code coverage best practices: "Code coverage does not guarantee that the covered lines or branches have been tested correctly, it just guarantees that they have been executed by a test." And on the specific target this post is about: "Focusing on getting the number as close as possible to 100% leads to a false sense of security."

Sit with the gap between those two sentences and the README badge. The badge says "tested." The measurement says "executed." Those are not the same claim, and the entire misunderstanding of coverage lives in the space between them. You can execute every line in a payment module and never once assert that the total is right. The coverage tool will congratulate you.

Why targeting 100% makes the suite worse, not just no better

Here's the part people underrate. Chasing 100% isn't merely low-value at the margin — it's negative-value, because of what you have to do to close the last stretch.

Martin Fowler made the mechanism explicit years ago in his note on test coverage, and it has aged perfectly: "If you make a certain level of coverage a target, people will try to attain it. The trouble is that high coverage numbers are too easy to reach with low quality testing." This is Goodhart's law wearing a lab coat — the moment a measure becomes a target, it stops measuring what you cared about. Fowler goes further, and I've never seen a reason to disagree: "I would be suspicious of anything like 100% — it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing."

The tests you write to get from 80% to 100% are, disproportionately, tests you would never write for their own sake. The defensive branch that can't actually be reached. The trivial getter. The error path you have to contort a mock into triggering. Each one runs a line, bumps the number, and then sits in your suite forever as something a future engineer has to understand and maintain when they refactor. Google names the cost directly: pushing the number up "could also be wasteful, burning machine cycles and creating technical debt from low-value tests that now need to be maintained." And they point out the returns collapse: "The gains of increasing code coverage beyond a certain point are logarithmic." The last 20% costs more than the first 80% and buys the least.

There's an even quieter tax. Brian Marick, whose work on coverage Fowler cites, has a line that reframes the whole exercise: "If a part of your test suite is weak in a way that coverage can detect, it's likely also weak in a way coverage can't detect." Coverage catches the dumbest gap — a line no test touched. The gaps that actually hurt — the untested edge case inside a line that is covered, the assertion that checks presence but not correctness — are invisible to it by construction. A suite optimized to satisfy coverage is optimized against the one class of weakness coverage can see, and left exposed to every class it can't.

The question coverage is standing in for

Nobody actually wants a coverage number. They want the thing the number is a proxy for: confidence that the code works and that they can change it without fear. Fowler's definition of "enough testing" throws the percentage out entirely and names the two outcomes that matter:

  • You rarely get bugs that escape into production, and
  • You are rarely hesitant to change some code for fear it will cause production bugs.

Read those twice, because they're doing something the number can't. Both are about consequences in the world — shipped bugs, and your own willingness to touch the code — not about lines executed in a report. A suite that delivers both of those at 72% coverage is finished. A suite that delivers neither at 100% is a liability with a green badge. The percentage was never the goal; it was a cheap-to-compute stand-in that everyone forgot was a stand-in.

Which points at the real move: stop asking "what percentage of lines did we execute" and start asking "which behaviors would actually hurt if they broke, and are those verified." Google lands in the same place from the other direction — the most useful thing coverage tells you isn't the score but the map of what's missing: "What's not covered is more meaningful than what is covered," and "more important than the percentage of lines covered is human judgment over the actual lines of code (and behaviors) that aren't being covered." Coverage is a decent flashlight for finding untested corners. It's a terrible trophy.

The whole flow is the part coverage can't see

Now the part that matters most for anyone shipping a web app, and the reason I care about this beyond the philosophy. Line coverage is a unit-level measurement. It sees inside functions. It is structurally blind to whether the flow a user actually performs works end to end.

You can have 100% line coverage on your checkout module — every function exercised, every branch hit, badge green — and still have a checkout that's broken in production, because no test ever drove the whole flow in a real browser: log in, add to cart, enter a real test card, watch the confirmation render, confirm the order shows up in history. The unit tests proved each brick is sound. They said nothing about whether the wall stands. The bugs that page you at 3am — the redirect that drops a query param, the third-party script that shifts the layout, the state that doesn't survive a reload, the race between two requests — live in the seams between the covered units, which is exactly where line coverage has nothing to say. This is the same reason scripted E2E tests miss whole classes of bugs: both the coverage number and the brittle script are looking at the app through a keyhole, one unit or one selector at a time, and the user's experience is the thing in the room that neither can see.

So the honest scorecard has two axes, not one. There's code coverage, which is a fine internal signal for your unit and integration tests — keep it, look at the gaps, don't gate your soul on the last 20%. And there's critical-flow confidence: do the handful of flows whose failure would actually cost you — checkout, signup, auth, the core action of your product — work end to end, right now, on a real render? Those two axes are almost uncorrelated, and a team staring at 100% on the first one while the second goes unmeasured has optimized the metric they can see and ignored the one their users live in.

What an agent optimizes for instead

This is where an AI QA agent fits, and I want to be precise about the claim, because overselling it would undercut the whole point of a post about honest metrics. An agent does not raise your code-coverage number. It doesn't measure code paths at all — it never sees your source. What it optimizes is the other axis: given a flow described in plain English, does that flow actually work for a human, end to end, on the rendered page.

Hand it the intent — "sign up, log in, add two items, check out with the test card, confirm the order appears in history" — and it drives the real browser and judges the outcome the way a person would, instead of asserting that line 47 executed. The unit of confidence is the flow, not the line. That's a direct answer to the question coverage was failing to answer: not "did we run this code" but "does the thing a user cares about still work." It's also why an agent's coverage can't be gamed the way a percentage can — there's no number to satisfy by writing an assertion-free test, because the artifact of a run is an evidenced verdict about a user-visible flow, not a tally.

I'm not telling you to delete your unit tests. Determinism at the unit level is a real virtue — a pure function's output should be pinned exactly, in milliseconds, every run, and a fast unit suite is the right tool for that. Keep it, and stop torturing it to hit 100%. The mistake isn't having coverage; it's mistaking a high number for safety, and letting the flows that would actually hurt go untested because they don't move the metric. Put the unit suite on your logic. Put critical-flow confidence on the things that page you. Report both, gate on outcomes, and let the badge be 84%. For where this fits against the cost of a human QA hire, I worked through what QA actually costs; for the broader argument that owning a giant scripted suite is the wrong default, there's the case against writing your own Playwright tests.

Where the number still earns its keep

To stay honest: coverage isn't useless, and I'd push back on anyone who reads this as "ignore coverage." A low number is a genuine alarm — Google's own framing is that "a low code coverage number does guarantee that large areas of the product are going completely untested by automation on every single deployment." If you're at 30%, that's real information and the direction is up. The failure mode isn't having the metric; it's turning a diagnostic flashlight into a target and marching it to 100% at the cost of everything the target was supposed to represent. Use coverage to find the dark corners. Don't use it to tell yourself the lights are on.

Try the other axis on your riskiest flow

Pick the one flow in your product whose failure would actually cost you money or trust — the one you'd most hate to find broken from a customer email. Coverage has an opinion about the functions underneath it. Here's how to measure the thing coverage can't. Save it as a Test Scenario and point it at staging:

Test the checkout flow on https://staging.yourapp.com.

Log in as test@example.com / Password123!, add any two products to
the cart, and complete checkout with the Stripe test card
4242 4242 4242 4242, any future expiry, any CVC.

Then verify the end-to-end outcome, not just that the button worked:
- The order confirmation renders with a real order number
- The charged total matches the sum of the two item prices plus any
  tax or shipping shown at checkout
- The order appears in the account's order history after a page reload
- No console errors or failed network requests occur along the way

Report the order number and the totals you saw. Flag anything that
looks broken, slow, or inconsistent even if the main flow completes.

Each run is roughly 8–13 credits (about $0.08–$0.13), and every run returns a Monito Session — screenshots, network log, and the agent's reasoning at each step — so a failure is something you read, not a red line number you re-run. Wire it into CI on every deploy and that one flow is verified end to end on every ship, which is a stronger statement than any coverage percentage will ever make. Your first run is free. Bring the flow you'd least want a customer to find broken — and notice that your coverage number had nothing to say about it either way.

All Posts