A test suite can report high coverage and still miss an important bug. The problem is often not the percentage itself, but what the percentage measures.
Consider a decision such as isMember && hasCredit. A test may execute the if statement, both outcomes of the decision, or each individual condition in different ways. Those are different kinds of evidence. Treating them as interchangeable makes coverage numbers more reassuring than they should be.
This article builds a practical mental model for statement coverage, branch coverage, and condition coverage. You will see what each criterion demonstrates, what it does not demonstrate, and how to choose tests for conditional logic without turning coverage into a target for its own sake.
Coverage measures execution, not correctness
Start with one rule:
if isMember AND hasCredit:
approveDiscount()
else:
rejectDiscount()Suppose the test suite contains only this case:
isMember = true
hasCredit = true
expected = approveThe test executes the decision and the approveDiscount statement. If the assertion passes, it provides useful evidence for that input.
It does not show what happens when either condition is false. It also does not prove that the implementation matches the intended rule for every input.
That distinction is the foundation for using coverage well:
Coverage tells you which parts of the program tests exercised. Assertions tell you whether observed behavior matched expectations for those executions.
Neither one replaces the other.
Statement coverage asks whether code ran
Statement coverage measures whether executable statements were executed by the test suite.
For the example above, reaching 100% statement coverage requires tests that execute both approveDiscount() and rejectDiscount().
Two tests can do that:
| isMember | hasCredit | Result |
|---|---|---|
| true | true | approve |
| false | true | reject |
Both outcome statements run, so the example can achieve full statement coverage.
But notice what remains unknown: no test has exercised hasCredit = false.
Statement coverage is therefore useful for finding code that tests never reach. It is weak evidence about the completeness of decisions with several inputs.
Branch coverage asks whether each decision outcome occurred
Branch coverage, often called decision coverage, checks whether each outcome of a decision has been taken.
For a Boolean if, that usually means exercising both the true and false outcomes.
The same two tests provide both outcomes:
true AND true -> true
false AND true -> falseSo they provide full branch coverage for this decision.
Branch coverage is stronger than merely knowing that the decision was evaluated. It catches a common gap where tests exercise only the successful path or only the failure path.
It still does not require every atomic condition to take both values. In the two tests above, hasCredit is always true.
That matters when a decision combines several independent facts.
Condition coverage asks whether each input condition varied
A condition is an individual Boolean expression inside a larger decision. In:
isMember && hasCreditthere are two conditions: isMember and hasCredit.
Condition coverage asks whether each condition has evaluated to both true and false across the test suite.
A small set that does this is:
| Test | isMember | hasCredit | Decision |
|---|---|---|---|
| A | true | false | false |
| B | false | true | false |
Across these tests, each condition is true once and false once.
However, the whole decision is false in both tests. The suite has condition coverage but not branch coverage because it never takes the approval branch.
This is an important result: condition coverage and branch coverage measure different gaps. Neither automatically implies the other for arbitrary compound decisions.
Combine criteria when the decision deserves it
For this simple AND rule, three tests can exercise both decision outcomes while also making each condition true and false:
| Test | isMember | hasCredit | Decision |
|---|---|---|---|
| A | true | true | true |
| B | false | true | false |
| C | true | false | false |
Now:
- the decision is both true and false;
isMemberis both true and false;hasCreditis both true and false.
This is more informative than choosing tests only to raise a percentage. Each case has a reason to exist.
The third test, for example, shows that membership alone is insufficient. The second shows that available credit alone is insufficient.
Coverage criteria are most useful when they expose a missing question about behavior.
Short-circuit evaluation changes what actually runs
Many programming languages use short-circuit evaluation for Boolean operators. With A && B, if A is false, evaluating B may be unnecessary because the whole expression is already false.
That has two practical consequences.
First, source code that visually contains two conditions does not guarantee that both conditions are evaluated in every test. A coverage tool may distinguish between the expression being present and a particular condition actually being evaluated.
Second, a condition with side effects makes the decision harder to reason about:
isMember && chargeLookupService()If isMember is false, the lookup may not happen at all. Tests should verify the intended observable behavior rather than assume every operand runs.
Coverage reports are tool- and language-dependent in how they display compound expressions, so interpret the report according to the tool’s documented measurement model. The engineering principle is broader: understand which decisions and operands your tests actually exercise.
Coverage can reveal a missing test without telling you what to assert
Suppose production code accidentally implements:
if isMember OR hasCredit:
approveDiscount()instead of the intended AND rule.
A coverage report can help reveal that some combinations have never been exercised. It cannot determine the business rule and tell you that OR is wrong.
The test must contain an expectation derived from the intended behavior:
isMember = true
hasCredit = false
expected = rejectThat case distinguishes AND from OR.
This is why coverage should guide test design rather than define correctness. The useful question is not “How do we reach 100%?” It is “What behavior have we not challenged yet?”
More combinations are not automatically better
With n independent Boolean conditions, an exhaustive truth table contains 2^n combinations. Four conditions produce 16 combinations; ten produce 1,024.
Testing every combination can be appropriate when the input space is small and the rule is critical. It can also create a large, repetitive suite when many combinations are equivalent for the behavior under test.
Coverage criteria help identify dimensions that have not varied, but they do not decide which combinations are semantically important.
For business rules, a decision table can be a better companion technique. It starts from meaningful rules and expected outcomes, then collapses combinations that do not affect the result. Coverage can then act as a check that the implementation paths expected from those rules are actually exercised.
Do not confuse a threshold with a testing strategy
A project may require a minimum coverage percentage in continuous integration. That can prevent obvious regressions such as adding substantial untested code.
A threshold cannot tell whether the tests contain strong assertions, useful boundary cases, or meaningful failure scenarios. It can also encourage low-value tests if the team treats the number as the goal.
A practical use of coverage is diagnostic:
- write tests from requirements, risks, and known failure modes;
- inspect coverage to find important code or decisions that remain unexercised;
- decide whether each gap represents missing evidence or intentionally irrelevant code;
- add a test only when it improves confidence in behavior.
The percentage is then a signal produced by the testing strategy, not the strategy itself.
Choose the criterion from the risk
Statement coverage is a useful baseline when the main question is whether substantial code remains untouched by tests.
Branch coverage is more useful when alternative outcomes matter: success versus failure, accepted versus rejected, retry versus stop, or enabled versus disabled.
Condition coverage becomes valuable when a decision combines several facts and a condition could remain effectively untested even though both branches execute.
For small, important decisions, combining branch and condition thinking is often reasonable. For complex rules, derive cases from the rule itself rather than mechanically enumerating operands. For safety-critical or regulated software, required structural coverage criteria may be defined by the applicable standard or assurance process; use those requirements rather than a general rule of thumb.
Common mistakes
One mistake is treating 100% coverage as proof that the code is correct. Tests can execute every line while asserting the wrong result or asserting almost nothing.
Another is adding tests that execute uncovered code without checking meaningful behavior. A test that merely calls a function can improve a metric without improving confidence.
A third is assuming one coverage criterion subsumes another. Compound Boolean decisions provide simple counterexamples: branch coverage can leave an individual condition fixed, while condition coverage can leave the overall decision with only one outcome.
Finally, avoid rewriting clear production logic solely to satisfy how a particular coverage tool counts expressions. If the code is genuinely difficult to test or reason about, simplify the design. If the issue is only report presentation, understand the tool before changing the program.
Conclusion
Coverage is evidence about execution, not a certificate of correctness.
Statement coverage asks whether code ran. Branch coverage asks whether each decision outcome occurred. Condition coverage asks whether each Boolean input to a compound decision took both values. Those questions overlap, but they are not equivalent.
Use the weakest criterion that still exposes the risks you care about, and strengthen it when compound decisions or important failure paths justify more evidence. Most importantly, let requirements determine what tests assert. Let coverage show you where your evidence may still be incomplete.