Code coverage — line, branch, path coverage
WHY does code coverage exist?
WHY do we care? Untested code is a blind spot. If a line never runs during testing, you have zero evidence it works. Coverage surfaces these blind spots cheaply and automatically.
WHAT it is NOT: Coverage measures execution, not correctness. A test can execute a line and still assert nothing (no assert), giving 100% coverage with 0% confidence. Steel-man this later.
The three levels (weakest → strongest)
We classify coverage by what unit we count. Bigger units = stronger guarantee = harder to reach.
| Level | Unit counted | Catches |
|---|---|---|
| Line (statement) | each executable line | unexecuted lines |
| Branch (decision) | each true/false outcome of every decision | untaken decision outcomes |
| Path | each end-to-end route through the code | unexercised combinations of branches |

Derivation: why branch ≥ line, and path ≥ branch (a hierarchy)
Take this function:
def grade(x):
if x > 50: # decision D1
return "pass"
return "fail"Step 1 — Count lines. Executable lines: if x>50, return "pass", return "fail" → 3 lines.
Why? We count statements that actually do something at runtime.
Step 2 — One test grade(60). Runs if, runs return "pass". That's 2/3 lines = 66% line.
The return "fail" never ran. Why this step? To show one test rarely hits everything.
Step 3 — Count branches. D1 has two outcomes: True and False → 2 branch outcomes.
grade(60) only takes the True outcome → 1/2 = 50% branch.
Why this step? Notice branch% (50) ≤ line% (66): branch is stricter, it punishes the untaken False path harder.
Step 4 — Full branch coverage. Add grade(40) (takes False). Now both outcomes hit → 100% branch, and incidentally 100% line.
Theorem (informal): 100% branch ⟹ 100% line (taking every decision outcome forces every line to run), but not vice-versa. So branch subsumes line. Likewise path subsumes branch.
Worked Example — Path explosion
def f(a, b):
if a: x = 1 # D1
else: x = 2
if b: y = 1 # D2
else: y = 2
return x + yStep 1 — Lines: ~5 → one test covers nearly all → easy 100% line.
Step 2 — Branches: D1{T,F}, D2{T,F} = 4 outcomes. Tests (T,T) and (F,F) cover all 4 → 100% branch with just 2 tests.
Why this step? Branch coverage does not require combinations, only that each outcome occurs at least once.
Step 3 — Paths: routes through the program = combinations: (T,T),(T,F),(F,T),(F,F) = paths.
Our 2 tests covered only TT and FF → 2/4 = 50% path.
Why this matters: A bug that only appears when a=True AND b=False is invisible to 100% branch coverage but caught by path coverage. That's the value path adds — and why it costs .
Worked Example — Short-circuit hides a branch
if user != None and user.active: # two conditions!
login()Step 1: Line coverage sees ONE line; one test running it = 100% line.
Step 2: Branch coverage (decision) sees the whole condition True/False.
Step 3: Condition coverage (a finer cousin) needs each sub-condition user!=None and user.active to be both T and F.
Why this matters: user=None short-circuits and never evaluates user.active. Tools reporting only line/decision coverage mask this. The fix: use MC/DC (Modified Condition/Decision Coverage) for safety-critical code, where each condition independently affects the outcome.
The 80/20 takeaway
- 20% you must know: Line ⊂ Branch ⊂ Path in strictness. Branch = every if-outcome at least once. Path = every combination = blowup. 100% coverage ≠ correct.
- The 80% real-world practice: aim for high branch coverage on critical modules, use path/MC/DC only for safety-critical code, and back coverage with mutation testing.
Recall Feynman: explain to a 12-year-old
Imagine your code is a maze and your tests are people walking through it.
- Line coverage: Did someone step on every floor tile? (Maybe, but they all walked the same way.)
- Branch coverage: At every fork, did someone go left AND someone go right? (Better — both directions tried.)
- Path coverage: Did people try every possible complete route from start to exit? (Best — but in a big maze there are millions of routes, so it's exhausting.) Walking through the maze proves you can reach a room. It does NOT prove the room is safe — for that you must actually check the room (assert). That's why "100% walked" isn't "100% safe."
Flashcards
What does code coverage measure?
Line (statement) coverage formula
Branch (decision) coverage formula
Why is branch coverage stricter than line coverage?
if is one line but two branch outcomes; running the line once gives 100% line but only 50% branch until both T and F are taken.Does 100% branch coverage imply 100% line coverage?
How many paths do n sequential independent if-statements create?
Why is 100% coverage not proof of correctness?
Difference between decision/branch coverage and condition coverage?
a and b = 1 decision, 2 conditions.What hides branches in a and b?
a is False, b is never evaluated, so its outcomes may go untested.What is MC/DC and when is it used?
What technique complements coverage to check test quality?
>→>=) and checks whether tests fail.Subset hierarchy of coverage strength?
Connections
- Unit Testing — coverage is measured by running unit/integration tests.
- Mutation Testing — fixes coverage's "did you assert?" blind spot.
- Cyclomatic Complexity — gives a lower bound on tests needed for branch coverage.
- Control Flow Graph — branches and paths are edges/routes in the CFG.
- Test-Driven Development — TDD naturally produces high branch coverage.
- MC-DC Coverage — the stricter standard for safety-critical systems.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, code coverage ka matlab simple hai: jab tum apne tests chalate ho, toh tumhare code ka kitna hissa actually execute hua? Ye ek thermometer jaisa hai — bukhaar batata hai, par ilaaj nahi karta. 100% coverage ka matlab ye nahi hai ki code sahi hai; iska matlab sirf itna hai ki wo line chali. Agar test mein assert hi nahi hai, toh line chal ke bhi kuch prove nahi hota — isko "vanity metric" kehte hain.
Teen levels hote hain, weak se strong: Line coverage (har executable line chali kya?), Branch coverage (har if ke dono raaste — True bhi aur False bhi — try hue kya?), aur Path coverage (har poora end-to-end route try hua kya?). Yaad rakho: ek if ek line hai par do branch outcomes deta hai, isliye branch coverage line se zyaada strict hai. Aur agar sequential if hain, toh total paths ho jaate hain — isliye path coverage exponentially badh jaata hai aur badi codebase mein practically impossible ho jaata hai.
Ek important trick: a and b jaise condition mein short-circuit hota hai — agar a False hai toh b evaluate hi nahi hota. Isliye line/branch coverage mein wo chhup jaata hai. Safety-critical software (jaise plane ka software) ke liye MC/DC use karte hain jahaan har sub-condition independently outcome ko affect kare. Aur coverage ke saath mutation testing jaroor use karo — wo check karta hai ki > ko >= banane par tumhara test fail hota hai ya nahi, yaani test ki quality check karta hai. Exam aur real job dono mein yahi practical samajh kaam aati hai.