← Test strategy

Practical guide

Code Coverage Analysis: A Pull Request Review Guide

Use coverage to find the parts of a change that deserve attention, then review the evidence behind the test instead of treating one percentage as a verdict.

Abstract review sheets connected to a changed code diff by orange paths

The short version

Code coverage analysis is a gap-finding tool. It reveals which executable paths a test run reached and, more importantly, which paths it did not. For a pull request, start with changed behavior, inspect the coverage around it, then ask whether the test makes a precise claim and would fail for the regression you are trying to prevent.

A coverage result can feel definitive because it arrives as a percentage. But a percentage is only a summary of execution. It cannot tell you whether an assertion checked the meaningful outcome, whether an error path was exercised, or whether a test would notice a regression that matters to a customer.

That does not make coverage less useful. It gives coverage a more useful job. Used during pull request review, it points engineers toward changed code, branches and boundary cases that deserve a closer look. The review then supplies the judgment a dashboard cannot.

What code coverage analysis actually answers

Coverage tools instrument a program while its tests run. They report whether statements, branches, functions or conditions executed. In the narrowest sense, code coverage analysis is the process of finding code that tests did not exercise. That is a valuable question because untouched code is a concrete place to investigate, not an abstract feeling that a suite may be incomplete.

The useful unit of analysis is usually a change, not the whole repository. A total score can remain steady while a new authorization branch, retry path or calculation is never reached. Looking at the pull request diff makes the signal more immediate: which new behavior was exercised, which path was skipped, and is the omission acceptable?

GitHub’s current coverage controls reflect this distinction. They can evaluate a minimum line-coverage level or a drop from the default branch, while requiring the normal test checks separately so a report is not mistaken for a completed test run. That separation is worth preserving in any workflow.

Abstract changed code branching into reviewed and unreviewed test paths

Start with the behavior, not the number

Before opening a coverage report, name the outcome the change is meant to produce. For a checkout change, that might be “a valid promotion changes the final amount once, and an invalid promotion changes nothing.” For a permissions change, it may be “a user without the required role cannot see or alter this record.” The phrasing should describe an outcome someone relies on, not only a method or file name.

Next, name the failure that would be costly. This narrows the review from “increase coverage” to a useful decision: should the test prove that an expired promotion is rejected, that the amount is calculated after tax, or that no partial record remains when payment fails? The highest-value test is rarely the one that reaches the most lines. It is the one that gives a clear answer about the failure the team cannot afford to miss.

Google’s testing guidance makes the same pragmatic point: coverage is best used to assess risk and find gaps, and there is no single ideal percentage for every product. The expected level depends on criticality, change frequency, complexity and the surrounding domain. A team reviewing a small display change should not use the same standard as a team reviewing money movement or access control.

Read a pull request coverage report in five passes

  1. Locate the changed behavior. Read the diff and identify the user-facing or system-facing outcome that changed. Keep the review tied to that outcome, rather than scanning every green line in the report.
  2. Check the changed lines and branches. Look for new logic that did not execute, especially conditionals around permissions, retries, fallbacks and invalid input. A missed branch is not automatically a bug, but it is a question worth answering.
  3. Find the test that claims to protect it. Read the test alongside the change. Confirm it reaches the relevant path and gives the behavior a specific input, not merely a broad happy-path setup.
  4. Inspect the assertion. Ask what would make the test fail. A check for a response status can be useful, but it may not prove the correct amount, stored value, emitted event or access rule.
  5. Decide whether the remaining gap is acceptable. If the untested path is deliberate or impossible to trigger safely, record why. If it represents meaningful risk, add or revise the test before treating the pull request as complete.

This sequence turns coverage into a review aid rather than a gate that invites shallow work. It also creates a record of the judgment behind an exception, which matters when the same risky area changes again.

Use diff coverage to focus attention

Total coverage answers a portfolio question: how much of the codebase executed across the suite? Diff coverage answers a review question: how much of the changed executable code executed? Both are useful, but they support different decisions.

ViewBest questionCommon mistake
Total coverageWhere has the test suite historically left large areas untouched?Assuming a stable total means new code is protected.
Diff coverageWhich changed lines and branches did the pull request's tests reach?Assuming reached lines have strong assertions.
Behavior reviewWould the test catch the regression this change could introduce?Skipping it because the coverage check is green.

Diff coverage works particularly well when it is visible beside a pull request, where reviewers can connect an uncovered branch to the code and test that own it. It should not become a pressure machine for tiny, low-value tests. A policy that blocks every exception without context often optimises for a prettier report rather than a more trustworthy release.

Engineering review checklist on a dark desk with orange marks and one green verified item

Know what a green report cannot prove

Coverage tells you that an instruction ran. It does not tell you whether a test checked the right value. A test may submit an order, execute the calculation and finish without an exception. That can produce excellent line coverage. Yet it may never assert the final amount, verify the customer’s currency or confirm that a failed payment did not create an order.

It also cannot discover missing intent. If the requirement never mentions a refund path, a coverage tool cannot demand a refund test. If a test is linked to a requirement but only checks a vague success response, the mapping may look complete while the behavior remains weakly protected. This is why teams need both a view of execution and a review of what the test actually claims.

When the risk justifies it, challenge the proof. Introduce a small controlled fault in a local or isolated environment, or use mutation testing to see whether the assertion reacts. Google notes that mutation testing is one way to assess whether tests adequately exercise and assert on a failure. The point is not to run an expensive experiment for every line. It is to reserve stronger evidence for behavior where a plausible failure would be expensive.

Set thresholds without encouraging theatre

A threshold can prevent coverage from silently sliding backwards, especially on code that changes often. It is most useful when it is paired with a clear escape hatch: a reviewer can explain why a path is not meaningful, impractical or already protected at another level. Without that context, teams can end up adding tests that execute code but do not help anyone make a release decision.

Choose different expectations for different risks. Authentication, authorization, billing, data deletion and migration paths deserve close attention. Straightforward view changes, generated glue code and defensive paths that cannot be reached in a realistic test may require a documented exception instead. NASA’s software engineering guidance similarly frames uncovered code as something to analyse and address with complementary techniques, rather than as a reason to chase a universal score.

Good policies make the next right action obvious: show the changed paths, show the test result, flag a meaningful drop and leave room for review. They should not reward a pull request merely for finding more ways to execute lines.

Where TestMojo fits

TestMojo helps teams move from a coverage signal to reviewable evidence. It maps a changed repository to the behaviors it may affect, the tests around those behaviors and the evidence each test provides. That makes it easier to investigate a green report with the question that matters: what specific regression would this test catch?

Start with the free local behavior map to see how a change connects to behavior and tests. Then compare the output against the worked evidence example or explore the evidence workflow. Teams assessing a broader evaluation can also see how an AI testing tool should support, rather than replace, engineering judgment.

A useful coverage review leaves a trail of reasoning

The goal is not a perfect percentage. It is a pull request where reviewers can explain what changed, which failure would matter, which test protects it and why any remaining gap is acceptable. Coverage analysis gives that conversation a map. Specific assertions and evidence make the map trustworthy.

For the companion explanation of what coverage measures versus what a test suite is intended to cover, read Code Coverage vs. Test Coverage.

Questions engineers ask

What is code coverage analysis?

Code coverage analysis shows which executable parts of a program ran while tests were executed. In a pull request, it is most useful for finding changed or risky paths that no test reached, then prompting a review of whether that gap matters.

What coverage should a pull request have?

There is no universal percentage that makes every pull request safe. Review the coverage of the changed behavior in proportion to its risk, then make sure important assertions, error paths and side effects are actually checked. A low-risk formatting change and a payment change do not deserve the same threshold.

Is diff coverage better than total coverage?

Diff coverage narrows attention to lines changed by a pull request, which makes it useful during review. Total coverage still helps reveal neglected areas across a codebase. Neither measurement can prove a test would catch the regression that matters, so pair either one with a look at the test's assertion and failure behavior.

Can a pull request pass coverage checks and still be risky?

Yes. A test can execute a line and still make a weak assertion, omit a permission or error path, or verify the wrong behavior. Treat a passing coverage check as an invitation to inspect evidence, not as proof that every meaningful outcome is protected.

Sources: OpenClover's definition of coverage analysis, GitHub's pull request coverage guidance, Google Testing Blog's code coverage practices, and NASA's coverage measurement guidance.