4.5.15 · D5Software Engineering

Question bank — Code coverage — line, branch, path coverage

1,295 words6 min readBack to topic

The vocabulary here — line, branch, decision, condition, path, subsumes, short-circuit — is all built from scratch in the parent note. If any term feels shaky, read that first, then come back and test yourself.


True or false — justify

100% line coverage means every line has been tested for correctness.
False. Coverage records only that a line ran; a test can execute it and assert nothing, so "tested" (checked) and "executed" (ran) are different things.
100% branch coverage guarantees 100% line coverage.
True. Taking every true/false outcome of every decision forces control flow through every reachable line, so all lines must have executed at least once.
100% line coverage guarantees 100% branch coverage.
False. One test can run every line yet only take the True side of an if; the False outcome stays untaken, so branch can sit at 50% while line is 100%.
Branch coverage and condition coverage are two names for the same thing.
False. Branch (decision) coverage tracks the whole condition's T/F; condition coverage tracks each boolean sub-term's T/F. a and b is one decision but two conditions.
Achieving 100% branch coverage requires every combination of the branch outcomes.
False. Branch coverage only needs each outcome to occur at least once, not in every combination — that stronger demand is path coverage.
Path coverage is always achievable if you write enough tests.
False. A loop that can run 0..k times or many sequential ifs give exponentially many (or with unbounded loops, infinitely many) feasible paths, so full path coverage is often infeasible.
If a module has no branches at all, its line and branch coverage numbers are identical.
True. With zero decisions there are no separate outcomes to count, so "lines run ÷ lines total" and "branch outcomes taken ÷ total" describe the same straight-line code.
Higher coverage always means fewer bugs.
False. Coverage bounds your ignorance (what you never ran), not your bug count; tests can run everything and still assert nothing, catching zero defects.

Spot the error

"We hit 100% coverage, so the code is proven correct."
The error is equating execution with verification. Coverage says the code ran; correctness needs assertions that check the results — mutation testing is the honest follow-up question.
"grade(60) alone gives 100% branch coverage of if x>50: return 'pass'; return 'fail'."
Wrong: grade(60) only takes the True outcome, so branch coverage is 1/2 = 50%. You also need an input like grade(40) to take the False outcome.
"Two sequential independent ifs create 2 paths."
Wrong: they create paths (TT, TF, FT, FF). Two ifs create two branch outcomes each but their combinations — the paths — multiply, not add.
"To cover if a and b fully at the condition level, one True and one False test of the line is enough."
Wrong: condition coverage needs each sub-term (a and b) to be independently True and False. Short-circuiting means when a is False, b is never even evaluated, so a naive pair of tests can leave b's outcomes untested.
"Covering all lines automatically covers all conditions inside a compound if."
Wrong: a compound condition sits on one line, so running that line once marks it fully line-covered while user.active may never have been evaluated due to short-circuiting.
"Path coverage subsumes branch coverage, so path is cheaper to run once you have branch."
The subsumption direction is right but the cost claim is backwards: path is stronger and far more expensive ( paths), so branch is the cheap subset of path, never the other way round.

Why questions

Why is the branch denominator larger than the line denominator for the same code?
Because one if line contributes a single line to count but two branch outcomes (True and False), so decisions inflate the branch total beyond the line total.
Why can 100% branch coverage still miss a bug that only appears when a=True and b=False?
Branch coverage needs each outcome once, satisfiable with tests TT and FF; the specific combination TF (a distinct path) is never exercised, so a bug living only there stays invisible.
Why do we say coverage is a "lower bound on ignorance" rather than a measure of quality?
Because low coverage guarantees undertested code (you know you skipped it), but high coverage does not guarantee good tests — so it can only certify the bad case, hence a bound on what you're ignorant of.
Why does short-circuit evaluation hide a condition from coverage tools?
In a and b, when a is False the language stops and never evaluates b, so b's True/False outcomes may never be recorded even though the line ran.
Why do safety-critical standards demand MC/DC instead of plain branch coverage?
MC/DC (Modified Condition/Decision Coverage) requires each condition to independently flip the decision's outcome, catching short-circuit and compound-logic gaps that decision coverage masks — critical where a single missed case can be fatal.
Why does adding assertions matter more than adding coverage once you're near 100%?
Past a point extra coverage just re-runs code without new checks; assertions are what actually detect wrong behaviour, so they convert "ran" into "verified."

Edge cases

A test suite that calls a function but crashes before any assert — what does coverage report?
It still reports the lines and branches that executed before the crash as covered, because instrumentation records execution regardless of whether assertions were reached.
Unreachable ("dead") code that no input can trigger — what happens to your coverage ceiling?
It caps your achievable line/branch percentage below 100% no matter how many tests you write, because there is no input that reaches it — a signal to delete the dead code or fix the logic gating it.
An empty function body (just pass or return) — what is its branch coverage?
Branch coverage is undefined-or-trivially-100% because there are zero decisions; with no branches to take, the metric has nothing to divide and the code is fully "covered" by any call.
A loop that can iterate 0 times — does one test that always enters the loop cover it?
No; the 0-iteration case is a distinct branch outcome (the loop condition being False on entry), so you also need an input that skips the loop entirely for full branch coverage.
A function with no if, while, or for at all — how do line, branch, and path coverage compare?
They collapse to the same thing: straight-line code has one path, no branch outcomes to split, so a single execution gives 100% on all three levels simultaneously.

Recall One-line summary of every trap

Coverage measures execution, not correctness; branch is stricter than line but weaker than path; combinations (paths) explode as ; short-circuits and dead code create silent gaps; and no percentage replaces an assertion.