Path Coverage
Path coverage tests every possible execution path from a module’s entry point to its exit. It is theoretically the strongest structural coverage criterion — but exponential path growth makes full coverage impractical for most real code. Basis path testing provides a practical, mathematically grounded subset.
What it is
A path is a unique sequence of statements from a module’s entry point to its exit, following specific branches at every decision point along the way. Path coverage is achieved when every such sequence has been executed at least once by the test suite.
Path coverage subsumes all other structural coverage criteria: if every path is covered, then every statement, every branch, and every condition has been exercised. It is the theoretical ceiling of white-box coverage.
The practical problem is path explosion. Each additional independent decision in a function doubles the number of paths. A function with 10 sequential binary decisions has 210 = 1,024 paths. Add a loop that can execute 0 to N times, and the number of paths becomes infinite. This is why the ISTQB Foundation syllabus flags full path coverage as “usually impractical” and why the Advanced syllabus teaches basis path testing as the workable substitute.
White-box coverage hierarchy
The four major white-box coverage criteria form a strict subsumption hierarchy:
- Statement coverage — weakest. Every executable statement executed at least once.
- Branch coverage — every branch from every decision point taken at least once (true and false).
- Condition/MC/DC coverage — each atomic condition independently exercised both ways.
- Path coverage — strongest. Every unique execution path from entry to exit.
Each higher criterion subsumes all lower ones: 100% path coverage guarantees 100% branch coverage, which guarantees 100% statement coverage. The converse is not true. A suite achieving 100% branch coverage may cover only a fraction of all paths.
Cyclomatic complexity (McCabe’s metric)
Cyclomatic complexity (V(G), introduced by Thomas McCabe in 1976) is a software metric that quantifies the number of linearly independent paths through a module. It is calculated from the control flow graph using the formula:
V(G) = E − N + 2P
Where:
- E = number of edges (arrows/transitions) in the control flow graph
- N = number of nodes (processing steps) in the graph
- P = number of connected components (usually 1 for a single function)
A simpler equivalent for structured code: V(G) = number of binary decisions + 1. Each if, while, for, case, &&, and || in a compound condition adds one to the count.
Cyclomatic complexity gives the minimum number of test cases needed for basis path testing. It is also used as a code quality metric: functions with V(G) > 10 are generally considered too complex and candidates for refactoring.
Basis path testing
Rather than testing all possible paths (exponential), basis path testing tests a linearly independent set of paths that, taken together, cover every statement and every branch at least once. The number of paths in this basis set equals the cyclomatic complexity V(G).
Linearly independent means: each path in the basis set introduces at least one new edge (branch) not found in any other path in the set. These paths span the “basis” of all possible paths — any other execution path through the code can be expressed as a linear combination of the basis paths.
How to identify basis paths:
- Draw the control flow graph for the function.
- Calculate V(G) using the formula above.
- Identify a baseline path (typically the main/happy path).
- Modify the baseline by flipping one decision at a time to create V(G)-1 additional paths, each new path differing from the others by at least one edge.
- Design a test case for each path in the basis set.
Worked example
Consider a function that calculates a discount:
function calcDiscount(customer, orderTotal) {
let discount = 0; // Node 1
if (customer.isVIP) { // Decision D1
discount = 0.15; // Node 2
}
if (orderTotal > 100) { // Decision D2
discount = discount + 0.05; // Node 3
}
return discount; // Node 4
}
Control flow graph: 4 nodes, D1 and D2 each add 2 edges (true branch + false branch). Total edges E = 6, nodes N = 4, P = 1. V(G) = 6 − 4 + 2 = 4. Four linearly independent paths need to be tested.
| Path | Decisions taken | isVIP | orderTotal | Expected discount |
|---|---|---|---|---|
| P1 (baseline) | D1=false, D2=false | false | $50 | 0% |
| P2 | D1=true, D2=false | true | $50 | 15% |
| P3 | D1=false, D2=true | false | $150 | 5% |
| P4 | D1=true, D2=true | true | $150 | 20% |
Four test cases cover all basis paths. For this two-decision, loop-free function, full path coverage happens to equal the basis set — there are exactly four distinct paths and the cyclomatic complexity is four. For functions with loops, the basis set remains finite (one path per loop zero-iterations, one per loop one-or-more-iterations) even though full path coverage would be infinite.
When path coverage is practical
Full path coverage is practical only in specific circumstances:
- Safety-critical small modules — DO-178C Level A (aviation), ISO 26262 ASIL D (automotive), and IEC 61508 SIL 4 (industrial safety) require structural coverage at or exceeding MC/DC. For small, loop-free functions in these systems, full path coverage is achievable and mandated.
- High-cyclomatic-complexity refactoring targets — if a function’s V(G) is 3–5 and it is critical business logic, full path coverage is achievable (8–32 test cases) and worth the investment.
- Basis path testing as the default — for all other white-box work, use basis path testing. It is always practical (V(G) test cases), achieves 100% branch coverage, and is mathematically complete with respect to the independent paths.
Loops require special handling. A loop that executes 0, 1, or many times represents three paths, not one. Test: zero iterations (skip the loop entirely), one iteration, and a representative many-iterations case. This is sufficient for most loops; for safety-critical loops with known maximum bounds, also test the maximum.
ISTQB mapping
| Syllabus ref | Topic | Level |
|---|---|---|
| CTFL 4.3 | Path coverage mentioned as impractical for most systems — awareness only | Foundation |
| CTAL-TA 4.4 | Basis path testing — control flow graphs, cyclomatic complexity, deriving basis paths | Advanced / Senior |
| CTAL-TA 4.4 K4 | Analyse code to draw a control flow graph, calculate V(G), and identify basis paths | Advanced LO |
Foundation candidates are expected to know that path coverage exists, that it subsumes branch coverage, and that it is generally impractical. Advanced (CTAL-TA) candidates must be able to draw a control flow graph, calculate cyclomatic complexity, and identify a set of basis paths with corresponding test cases.
Common mistakes
- Confusing path coverage with branch coverage — branch coverage tests each decision outcome; path coverage tests each sequence of outcomes through the function. A test suite can achieve 100% branch coverage while covering only a fraction of paths.
- Attempting full path coverage on code with loops — a single loop with an unbounded iteration count creates infinitely many paths. Use basis path testing and add specific loop boundary tests (0, 1, max iterations) rather than pursuing full coverage.
- Miscounting cyclomatic complexity — remember that compound conditions (
A && B,C || D) add to the count. Each boolean operator adds one to V(G). Many teams count only explicitif/while/forstatements and undercount. - Not updating basis paths after refactoring — when code is refactored, the control flow graph changes and the basis paths change. Test cases designed for the old paths may no longer correspond to any actual path in the new code.
- Equating high cyclomatic complexity with many required tests — V(G) is the minimum for basis path testing. It does not mean you should write exactly V(G) tests and stop. You still need black-box tests, edge-case tests, and integration tests on top.
Related techniques
Path coverage is the top of the white-box hierarchy. Its practical substitute, basis path testing, requires the same foundation as Branch Coverage — understand and achieve branch coverage first.
Cyclomatic complexity is both a test planning metric (minimum test cases needed) and a code quality metric. Functions with V(G) > 10 are candidates for refactoring before testing. Condition Coverage and MC/DC are the appropriate targets for safety-critical decisions within complex functions.
Practice this technique: Try Test Lead Practice 07 — Test coverage gaps.