Skip to content

What Is Test Coverage? Line, Branch, and 'Good' Percentages (2026)

Test coverage measures how much code your tests execute, not whether they check anything real. Line vs branch coverage, what percentage counts as good, and where the metric breaks down.

İbrahim Süren
Founder · Jul 5, 2026 · 10 min read
What Is Test Coverage? Line, Branch, and 'Good' Percentages (2026)

Test coverage is the percentage of your codebase that runs when your test suite executes, most often measured as line, branch, or path coverage. There's no universal "good" number — industry guidance generally lands around 70-90%, and SonarQube's popular default gate requires 80% coverage on new code specifically, not the whole repository. The bigger catch: coverage tells you what's untested, not whether what is tested is correct — a line can execute with zero assertions and still count as "covered."

Key takeaways

  • Test coverage measures how much code executes during tests — it says nothing about whether the tests check anything meaningful.
  • The same function can have 100% line coverage and 50% branch coverage — line coverage only asks whether a line ran, not whether every decision path inside it ran.
  • There's no universal 'good' percentage: guidance commonly points to 70-90%, and Martin Fowler treats the upper-80s to low-90s as a sign of thoughtful testing, and 100% as suspicious.
  • SonarQube's default quality gate requires 80% coverage on new code only — a legacy codebase can carry lower overall coverage while still enforcing a high bar on every new commit.
  • Coverage works as a floor that catches regressions and a map to untested code; treated as a ceiling ('we're well-tested'), it becomes a vanity metric teams can game.

Test coverage is the percentage of your codebase that runs when your test suite executes, most often measured as line, branch, or path coverage. There’s no universal “good” number — industry guidance generally lands around 70-90%, and SonarQube’s popular default gate requires 80% coverage on new code specifically, not the whole repository. The metric’s biggest catch has nothing to do with the percentage: coverage tells you what’s untested, not whether what is tested is actually correct. A line can execute with zero assertions and still count as fully “covered.”

What does test coverage actually measure?

Coverage tools instrument your code, run the test suite, and record which parts of the code executed. The result is a ratio: covered / total, expressed as a percentage. But “which parts” can mean several different things, and each definition gives a different, non-interchangeable number for the same codebase.

  • Line (or statement) coverage — the percentage of executable lines that ran at least once. The most common and least strict measure.
  • Branch coverage — the percentage of decision outcomes that ran. Every if, ternary, or logical operator creates at least two branches (true/false), and branch coverage requires both to execute somewhere in the suite.
  • Path coverage — the percentage of distinct end-to-end sequences through the code’s decision points that ran. Two independent booleans already produce four paths; five conditions produce 32. Path coverage is combinatorial and grows too fast to chase directly, so testing bodies generally treat it as a theoretical ceiling above branch coverage rather than a practical target.

The same code, three different numbers

Here’s the gap between line and branch coverage in practice. Take a one-line ternary:

function greeting(user) {
  const name = user ? user.name : "Guest";
  return `Hello, ${name}`;
}

Now test it with a single case: greeting({ name: "Ana" }).

MetricResultWhy
Line coverage100%Both lines in the function executed at least once.
Branch coverage50%Only the truthy side of the ternary (user.name) ran. The "Guest" fallback never executed.

Nothing about that second line looks untested in a line-coverage report — it’s green, fully executed. But half the logic inside it never ran, and a bug in the fallback path (say, a typo that returns undefined instead of "Guest") would ship undetected. This is exactly why tools like Istanbul/nyc report branch coverage separately from line coverage on ternaries and logical operators: line coverage alone would hide the gap.

What’s a “good” test coverage percentage?

There’s no magic number, but there’s no shortage of real-world anchors either. Industry guidance clusters around 70-90% for most projects. Codecov’s own guidance cites Google’s internal rule of thumb — 60% “acceptable,” 75% “commendable,” 90% “exemplary” — and recommends most teams land around 75-85%. Martin Fowler goes further: “If you are testing thoughtfully and well, I would expect a coverage percentage in the upper 80s or 90s. I would be suspicious of anything like 100%” — because the last few percent usually means testing code that rarely breaks, or writing tests that execute code without checking it.

A concrete, widely-used real-world threshold: SonarQube’s default “Sonar way” quality gate, per SonarQube’s own documentation, requires new code test coverage of at least 80.0% — one of four conditions alongside no new issues, reviewed security hotspots, and capped duplication. We cover the other three conditions and how the gate behaves in CI in our guide to quality gates in CI/CD.

New code vs. whole codebase — the distinction that actually matters

The detail worth internalizing from that SonarQube example: the 80% threshold applies to new code only, not the whole repository. SonarQube calls this “clean as you code.” A ten-year-old monolith can sit at 40% overall coverage and still pass the gate cleanly, as long as every new commit meets the higher bar.

This reframes the “good percentage” question. Instead of asking “what should our whole codebase’s coverage be,” split it: an acceptable floor for legacy code — usually whatever it already is, unless a specific area is a known risk — and a bar for new code, where the 70-90% range and SonarQube’s 80% actually apply. Chasing the first number with a blanket rewrite is almost always wasted effort; enforcing the second on every pull request is where coverage thresholds earn their keep.

Why 100% test coverage doesn’t mean bug-free code

This is the part of the coverage conversation that gets skipped in favor of picking a target percentage — and it’s the more important half.

Coverage without assertions

A line of code can execute inside a test without the test ever checking what it produced. Codecov’s own writeup illustrates this with a function that’s supposed to add two numbers but multiplies them instead — add(2, 3) returns 6, not 5. A test that simply calls add(2, 3) and checks nothing drives that line to 100% coverage while missing the bug entirely. Only a test that asserts add(2, 3) === 5 would catch it. Fowler calls the extreme version of this “assertion-free testing” — tests engineered purely to touch lines, with no checks on behavior — and treats it as the logical end point of chasing a coverage number instead of testing intent.

The uncomfortable part: coverage tools can’t tell the difference. Both versions of that test report identically in a coverage dashboard.

Coverage as a vanity, gameable metric

Once a coverage percentage becomes a target — reported up the chain, or a gate that blocks a merge — it invites tests written to satisfy the number, not to verify behavior. A team under pressure to hit 80% can get there by testing getters and setters, wrapping calls with no assertions, or writing trivial tests against the easiest 10% of the codebase while the riskiest logic stays thin. The percentage goes up; actual confidence in the system doesn’t move.

Fowler’s framing of this is blunt: “test coverage is a useful tool for finding untested parts of a codebase, but it is of little use as a numeric statement of how good your tests are.” The number tells you where nobody has tested; it does not tell you how well the tested parts were tested.

Coverage as a floor vs. a ceiling

The healthiest way to hold both facts at once is a floor-versus-ceiling distinction:

  • As a floor, coverage is genuinely useful — it catches regressions in code that’s already meaningfully tested, and a coverage-on-diff check stops a pull request from quietly shipping a large untested function.
  • As a ceiling, coverage is dangerous — treating “we’re at 87%” as proof the codebase is well-tested creates a false sense of safety that a real incident will eventually correct.

Coverage percentage also isn’t evenly distributed by design. A codebase with strong unit coverage but almost no integration or end-to-end coverage can still hit an impressive-looking overall number while missing the exact defects that only show up when components interact. Coverage should be spread across the layers of your suite rather than concentrated in whichever layer is cheapest to test — see our breakdown of the test pyramid for how that distribution should actually look.

How to use coverage data usefully

Coverage is at its best as a discovery tool, not a scorecard to maximize:

  1. Run coverage on the diff, not just the whole repo. A new-code coverage check on every pull request catches the gap that matters most — code nobody has tested yet — without demanding a retroactive rewrite of everything already in production.
  2. Use uncovered lines as a review prompt, not an alarm. An uncovered branch in a rarely-touched utility is low priority; the same gap in billing, auth, or a payment path is worth a test today.
  3. Watch for coverage that isn’t backed by assertions. A spike in the percentage with no corresponding growth in meaningful test cases is a signal to look at what actually got written, not a reason to celebrate.
  4. Don’t set a team goal around the percentage itself. Set goals around testing specific risk areas and let coverage move as a side effect, not the target.

A percentage in a static report also can’t tell you whether the tests behind it are stable or trustworthy — pairing coverage with test observability data on pass rate, flakiness, and failure clusters turns “87% covered” into an actual read on release readiness, instead of a number that only looks reassuring in isolation.

Coverage numbers inflated by low-value tests are also how test debt quietly accumulates — a suite that looks well-covered on paper but is expensive to trust and expensive to maintain. Qualflare surfaces coverage trends alongside pass rate, flakiness, and failure clustering across every CI run, so a coverage percentage is read next to whether the suite behind it is actually reliable — not as a lone number on a dashboard.

Frequently asked questions

What is test coverage?

Test coverage is a metric that measures how much of a codebase runs when a test suite executes, expressed as a percentage. It’s most commonly measured as line coverage (which lines ran), branch coverage (which decision paths ran), or path coverage (which full sequences of decisions ran).

What’s a good code coverage percentage?

There’s no universal number, but industry guidance commonly points to 70-90% for most codebases. SonarQube’s default quality gate requires 80% coverage on new code specifically, and Martin Fowler considers a percentage in the upper 80s to low 90s a sign of thoughtful testing — while treating 100% as a red flag for tests written just to hit the number.

What’s the difference between line coverage and branch coverage?

Line coverage checks whether each line of code executed at least once. Branch coverage checks whether each possible decision path (both sides of an if/else, both outcomes of a ternary) executed at least once. A single test can drive a line to 100% line coverage while only exercising one of its branches, leaving branch coverage far lower.

Does 100% test coverage mean there are no bugs?

No. Coverage only confirms a line or branch executed during a test run — it says nothing about whether the test asserted the correct behavior. A test that calls a function without checking its return value drives that function’s coverage to 100% while catching zero bugs.

Should a legacy codebase with low overall coverage be rewritten to raise the percentage?

Usually not. The more common and more useful approach — codified in SonarQube’s default “clean as you code” quality gate — is enforcing a high coverage bar on new code while leaving overall legacy coverage where it is. A ten-year-old codebase can carry 40% overall coverage and still ship safely if every new commit meets a much higher bar.

How should teams actually use test coverage data?

As a map, not a scorecard. Use coverage reports to find untested functions and decision paths worth reviewing, especially on high-risk or frequently changed code, and check coverage on the diff for every pull request. Don’t set team goals around raising the percentage itself — that incentivizes low-value tests written to hit a number rather than to catch real defects.

Ready to ship with confidence?

Start free with Qualflare's AI-powered test management.