Code Coverage: What a High Percentage Can’t Tell You
A high code coverage percentage tells you that tests executed a large share of the code measured; it does not show that they checked the right outcomes or would catch important defects. Coverage is useful evidence about test reach, but it is not a score for test quality, correctness, or confidence.
What code coverage measures
Code coverage is a family of measurements that records which parts of a program ran while a test suite was executing. A report may show coverage for files, lines, functions, or branches. The percentage is generally the number of measured items exercised divided by the total number measured, though tools and settings differ in what they count.
For example, line coverage asks whether a line ran at least once. Function coverage asks whether a function was called. Branch coverage considers alternative paths at decisions, such as the true and false outcomes of an if statement. These measures answer different questions, so a percentage is meaningful only when you know its metric and scope.
Coverage tools usually collect execution data while tests run, then associate that data with source code. Results can vary with generated code, excluded files, runtime conditions, and tool configuration. A project reporting 90% line coverage is not necessarily comparable with another reporting 90% branch coverage.
What a high percentage cannot tell you
Coverage records execution, not whether a test would notice a wrong answer. Consider a function that calculates a discount. A test might call it with one ordinary input, causing every line to run, but never assert the returned price. The report can show full line coverage even though the test would pass if the function returned an incorrect value.
Even an assertion can be weak. A test might check only that a result is “not null,” although the important requirements concern the exact amount, rounding, or behavior at a boundary. Coverage cannot tell whether assertions express the specification or whether the test data represents realistic and risky cases.
Nor does coverage prove that all relevant behavior was considered. It can miss defects in untested environments, interactions between components, timing, concurrency, external services, or data combinations. A test suite may execute a code path without exercising the conditions that make that path fail.
- Correct expectations: Did the test check the intended result, not merely execute the code?
- Useful inputs: Did it cover boundaries, invalid values, and significant combinations?
- Failure detection: Would a realistic bug make the test fail?
- Relevant scope: Are important modules included, or are they excluded from the report?
Line, branch, and condition coverage
Line coverage is easy to understand, but it can conceal untested decisions. Suppose a function returns an access result based on whether a user is active and whether the account is paid. Two tests might run every line while exercising only combinations that produce access. Branch coverage can reveal that a decision’s alternative outcome has not run, but it still does not guarantee that every meaningful combination of conditions has been tested.
Condition coverage examines whether individual Boolean conditions have each evaluated both true and false. Modified condition/decision coverage, used in some safety-critical contexts, asks a stricter question: whether each condition can independently affect the overall decision. These measures have different costs and purposes. More demanding coverage can expose gaps, but none substitutes for understanding requirements and risks.
Coverage also depends on the source being measured. A tool may count generated files, boilerplate, or defensive code; teams may exclude some of them. Exclusions can make a metric more representative, or they can hide neglected areas. Reports should make the measurement method and exclusions visible.
How to use coverage without mistaking it for quality
Use coverage as a diagnostic: it points to code that tests did not reach and helps teams ask why. An uncovered line might be an important missing case, unreachable code, or an intentional exclusion. Review it rather than treating every uncovered line as an automatic failure.
- Choose the metric deliberately. Line coverage is a useful starting point; branch coverage can add visibility around decisions.
- Set a baseline and examine trends. A sudden drop can flag a change in testing, but a rising number alone does not prove improvement.
- Investigate uncovered high-risk behavior. Prioritize payment, permissions, data integrity, and other consequential logic according to the application’s risks.
- Review test meaning. Check assertions, test inputs, and whether the test fails when behavior is deliberately made incorrect.
- Combine evidence. Use code review, integration and end-to-end tests, static analysis, and production monitoring where appropriate.
Mutation testing can provide another perspective by making small changes to code—such as changing a comparison or removing a statement—and checking whether tests catch them. A surviving mutation suggests a possible weakness, though results require interpretation: some mutations are equivalent in behavior or irrelevant to requirements. Mutation testing is not a replacement for sound test design.
Targets, trade-offs, and common pitfalls
There is no universal coverage percentage that guarantees a reliable system. A target can help prevent untested code from accumulating, especially when applied consistently, but optimizing for a number can encourage superficial tests, excessive exclusions, or tests that are difficult to maintain. The right level depends on the cost of failure, the complexity of the code, and the time available.
Coverage can also be misleading when tests are flaky, slow, or isolated from the real behavior they are meant to protect. A unit test may cover a service method while an integration problem—such as incorrect database mapping—remains undiscovered. Conversely, a small number of well-designed tests may protect important behavior without driving every line to execution.
Use the percentage to start a conversation, not end one. Ask what is uncovered, what could still be wrong in covered code, and which failures matter most. That review is more informative than comparing raw percentages across projects with different languages, tools, and measurement rules.
Frequently Asked Questions
Is 100% code coverage possible?
It may be possible for a chosen metric and measured scope, but it means only that every counted item was exercised under that metric. It does not prove every behavior is correct or every defect will be caught.
Does high coverage mean my tests are good?
No. It shows that tests reached much of the measured code. Assess test quality by reviewing assertions, input choices, requirements, and whether plausible defects cause failures.
Is branch coverage better than line coverage?
Branch coverage reveals whether decision outcomes ran, which line coverage can miss. It is more informative for control flow in many cases, but it still cannot establish that all important inputs or condition combinations were tested.
What is a good code coverage target?
There is no universally correct target. Set a practical goal based on risk and maintainability, define exactly how coverage is measured, and avoid treating the threshold as proof of quality.
Can tests have high coverage but miss bugs?
Yes. Tests can execute code without checking its result, omit boundary cases, or miss defects in integrations and environments. Coverage alone does not show that a test would detect a bug.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.