Session 3.6 — Statement Coverage & Branch Coverage
Module 3: Dynamic Testing — White-box Techniques | Duration: 1 hour
Learning Objectives
- Define statement coverage and compute statement coverage percentage for a given test set.
- Identify the limitations of statement coverage that make it insufficient for high-quality testing.
- Define branch coverage and design a minimum test set that achieves 100% branch coverage.
- Explain why branch coverage subsumes statement coverage.
- Use coverage measurement tools to interpret coverage reports.
Concept Overview
Statement and branch coverage are the two most fundamental white-box coverage criteria. Statement coverage ensures every line of code runs. Branch coverage goes further: it ensures every decision in the code has been exercised in both its true and false outcomes. Understanding both — and the gap between them — is essential for effective unit testing.
The weakest but most commonly reported baseline. Every executable statement in the code is executed at least once by the test suite.
Requires every decision point to be taken in both directions (true and false). Stronger than statement coverage and subsumes it.
A test set can achieve 100% statement coverage while leaving an entire
else branch completely untested — branch coverage closes this gap.Statement Coverage
Statement coverage (also called line coverage) measures the percentage of executable statements in the code that have been executed by at least one test case.
An executable statement is any line of code that performs an operation when run (assignments, function calls, conditionals, return statements). Comments and blank lines are not counted.
SC% = (Number of statements executed ÷ Total number of executable statements) × 100
- List all executable statements in the code (map to CFG nodes).
- For each test case, trace which statements are executed.
- Mark statements as "covered" if executed by at least one test.
- Compute SC% = covered / total × 100.
- Add test cases to cover any remaining uncovered statements.
For very simple, linear code with no branching. Also useful as a minimum baseline to detect completely untested modules.
Many CI pipelines report SC as a first gate. Falling below 80% SC typically blocks a merge. However, SC alone is never the final quality bar.
Statement Coverage — Worked Example
Consider the following function that applies a discount to a cart total:
Total executable statements: 9 (S1 through S9, excluding the if conditions themselves which we count as S2, S4, S6).
| Statement | Executed? | Reason |
|---|---|---|
| S1: discount = 0 | Yes | Always executed |
| S2: if member | Yes | Always evaluated |
| S3: discount += 10 | Yes | member = True |
| S4: if coupon == "SAVE20" | Yes | Always evaluated |
| S5: discount += 20 | Yes | coupon matches |
| S6: if total > 500 | Yes | Always evaluated |
| S7: discount += 50 | Yes | total = 600 > 500 |
| S8: final = total - discount | Yes | Always executed |
| S9: return final | Yes | Always executed |
SC% = 9/9 × 100 = 100% with a single test case!
TC-1 never tests: member=False (S3 skipped but S3 exists in the false branch), coupon mismatch (S5 skipped), total ≤ 500 (S7 skipped). The false branches of S2, S4, and S6 are never taken by TC-1. This is exactly the limitation that branch coverage addresses.
Limitations of Statement Coverage
Statement coverage does not distinguish between a statement being executed inside an if branch versus outside it. A single test that takes the true path of every condition achieves 100% SC but never tests any false path.
Bugs hiding in else branches, elif arms, and default cases of switch statements are invisible to statement-coverage-only suites.
With TC-1 (amount=100, balance=500), 100% SC is achieved. The bug in the else branch (deducting when it should not) is completely missed. Only a test that triggers amount > balance (i.e., the false branch) would expose it.
Branch Coverage
Branch coverage (also called decision coverage) requires that every possible outcome of every decision point in the code is exercised at least once. For a boolean decision, this means both the true branch and the false branch must be taken by at least one test case.
A branch is a single outgoing edge from a decision node in the CFG. Every decision point with N outcomes has N branches. For a binary if, there are always 2 branches: T and F.
BC% = (Number of branches executed ÷ Total number of branches) × 100
- From the CFG, count all outgoing edges from decision nodes — these are the branches.
- An
ifwith noelsehas 2 branches: theifbody (T) and the fall-through (F). - An
if-elsehas 2 branches: theifbody (T) and theelsebody (F). - A
switchwith N cases has N branches (one per case, plus one for default if present). - A
whileloop has 2 branches: enter the loop body (T) and exit the loop (F).
If both branches of every decision are exercised, then every statement reachable from an entry node must be executed. Therefore 100% BC ⇒ 100% SC. The converse is false: 100% SC does not imply 100% BC.
Branch Coverage — Worked Example
Using the same apply_discount function from earlier:
| Decision | Branch T | Branch F |
|---|---|---|
D1: if member | B1: member is True | B2: member is False |
D2: if coupon == "SAVE20" | B3: coupon matches | B4: coupon does not match |
D3: if total > 500 | B5: total > 500 | B6: total ≤ 500 |
Total branches = 6 (B1 through B6)
| Test | Inputs | Branches Covered | SC% | BC% |
|---|---|---|---|---|
| TC-1 | total=600, member=True, coupon="SAVE20" | B1, B3, B5 | 100% | 50% |
| TC-2 | total=400, member=False, coupon="NONE" | B2, B4, B6 | 78% (S3,S5,S7 not executed) | 100% |
TC-1 + TC-2 together: SC = 100%, BC = 100% using just 2 tests.
| Statement | Executed? | Reason |
|---|---|---|
| S1: discount = 0 | Yes | Always |
| S2: if member | Yes | Evaluated as False |
| S3: discount += 10 | No | member = False, branch B2 taken |
| S4: if coupon == "SAVE20" | Yes | Evaluated as False |
| S5: discount += 20 | No | coupon mismatch, branch B4 taken |
| S6: if total > 500 | Yes | Evaluated as False |
| S7: discount += 50 | No | total=400, branch B6 taken |
| S8: final = total - discount | Yes | discount=0, final=400 |
| S9: return final | Yes | Returns 400 |
Statement Coverage vs Branch Coverage
| Aspect | Statement Coverage | Branch Coverage |
|---|---|---|
| What is measured | Each executable statement executed at least once | Each branch outcome (T/F) of every decision taken at least once |
| Formula | Executed statements / Total statements | Executed branches / Total branches |
| CFG element | Nodes (basic blocks) | Edges from decision nodes |
| Subsumption | Subsumed by branch coverage | Subsumes statement coverage |
| Minimum tests (example function) | 1 test can achieve 100% | Typically 2+ tests needed |
| Bugs found | Missing/unreachable code | Incorrect branching logic, missing else handling |
| Industry threshold | 80% SC as CI gate (minimum) | 80–100% BC as standard quality bar |
100% branch coverage guarantees 100% statement coverage, but NOT vice versa. Always target branch coverage as the minimum acceptable criterion for unit testing.
Coverage Measurement Tools
Run with
coverage run -m pytest then coverage report -m. Shows line and branch coverage per file. HTML report available via coverage html.Integrates with Maven/Gradle. Produces XML and HTML reports showing statement, branch, and line coverage per class and method.
Used with Jest or Mocha. Reports statement, branch, function, and line coverage. Threshold enforcement via
.nycrc.GCC built-in coverage tool. Instruments code at compile time. lcov provides HTML visualisation of line and branch coverage.
| Report Column | Meaning | Action if low |
|---|---|---|
| Stmts | Total executable statements | Baseline count |
| Miss | Statements never executed | Add tests targeting these lines |
| Branch | Total branch outcomes | Baseline count |
| BrPart | Branches partially covered (one direction only) | Add test for missing T or F outcome |
| Cover | Coverage percentage | Compare against project threshold |
| Missing | Line numbers not covered | Go directly to those lines and design covering tests |
Common Mistakes
Achieving full statement coverage with one happy-path test and calling the suite complete. Always verify branch coverage as well.
The condition itself is a decision node, not a separate statement. Count the body statements (true branch and false branch bodies) separately.
The uncovered 20% of branches may be the most critical error-handling paths. Justify every uncovered branch explicitly.
A test that executes code but never asserts the output contributes to coverage but provides no defect detection value.
Class Activity
You are given the following function for a ride-share fare estimator:
- Label all executable statements S1, S2, …
- Identify all decisions and their branches (B1, B2, …).
- Design one test case that achieves 100% statement coverage. Compute SC%.
- Design a minimum test set that achieves 100% branch coverage. Compute BC% for each individual test and the combined set.
- Identify a bug scenario that your branch-covering test set would detect but statement-only test would miss.
- 2 marks: Correct statement labelling and count.
- 2 marks: Correct branch identification and count.
- 3 marks: Valid minimum test set achieving 100% BC with traced coverage.
- 2 marks: SC% and BC% correctly computed for each test.
- 1 mark: Valid bug scenario that SC misses but BC catches.
Exit Ticket
- A function has 20 executable statements and 3 binary decisions. What is the maximum number of branches? What is the minimum number of tests needed for 100% BC?
- True or False: If BC = 100%, then SC = 100%. Justify.
- A coverage report shows 90% statement coverage with two branches "partially covered". What does "partially covered" mean, and what test do you need to add?
Summary & Assignment
Statement coverage is the minimum structural baseline but leaves false branches untested. Branch coverage subsumes statement coverage and requires every decision to be exercised in both directions. Branch coverage is the recommended standard for unit testing and is enforced by modern CI/CD pipelines through tools like JaCoCo, Coverage.py, and Istanbul.