4.5.15Software Engineering

Code coverage — line, branch, path coverage

2,004 words9 min readdifficulty · medium4 backlinks

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

Figure — Code coverage — line, branch, path coverage

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 + y

Step 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) = 22=42^2 = 4 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 2n2^n.


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 = 2n2^n 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?
The percentage of source code that is executed while the test suite runs — execution, not correctness.
Line (statement) coverage formula
(executable lines executed ÷ total executable lines) × 100%.
Branch (decision) coverage formula
(branch outcomes taken ÷ total branch outcomes) × 100%; each decision has 2 outcomes (T/F).
Why is branch coverage stricter than line coverage?
One 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?
Yes — taking every decision outcome forces every line to execute. The reverse is false.
How many paths do n sequential independent if-statements create?
2n2^n — path coverage grows exponentially, often becoming infeasible.
Why is 100% coverage not proof of correctness?
A test can execute code without asserting anything; coverage measures execution, not whether results are checked.
Difference between decision/branch coverage and condition coverage?
Decision = whole condition's T/F; Condition = each boolean sub-term's T/F. a and b = 1 decision, 2 conditions.
What hides branches in a and b?
Short-circuit evaluation — if a is False, b is never evaluated, so its outcomes may go untested.
What is MC/DC and when is it used?
Modified Condition/Decision Coverage — each condition independently affects the outcome; required in safety-critical (e.g. avionics) software.
What technique complements coverage to check test quality?
Mutation testing — it mutates the code (e.g. >>=) and checks whether tests fail.
Subset hierarchy of coverage strength?
Line ⊂ Branch ⊂ Path (and Path subsumes Branch subsumes Line).

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

answers

collected by

formula

denominator = lines

denominator = branch outcomes

denominator = paths

one if is 2 outcomes

n ifs give 2^n routes

grows exponentially

weakest, path strongest

measures execution not correctness

Code Coverage metric

How much code ran during tests

Instrumenting code

hit ÷ total x 100%

Line coverage

Branch coverage

Path coverage

Often infeasible

Hierarchy branch >= line, path >= branch

Thermometer not a cure

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 nn sequential if hain, toh total paths 2n2^n 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.

Go deeper — visual, from zero

Test yourself — Software Engineering

Connections