(a) Executable lines.if n>0, return "pos", if n<0, return "neg", return "zero" → 5 lines. (The def header is not executable code that "runs logic"; we count the 5 statements.)
(b) Branch outcomes. Two decisions (D1, D2), each with True/False → 2×2=4 branch outcomes.
(c) Decisions. D1 and D2 → 2 decisions.
Problem. Using sign from L1.2, run a single test sign(5). Compute line %, branch %, and identify the untaken branch outcomes (keep units separate from lines).
Recall Solution
sign(5): 5 > 0 is True → runs if n>0 then return "pos". It stops there (early return).
Lines (unit = executable line, denominator 5):
Executed: if n>0, return "pos" → 2 of 5.
Line=52×100%=40%
Untaken lines: if n<0, return "neg", return "zero".
Branch outcomes (unit = branch outcome, denominator 4) — a different denominator, kept separate:
Taken: only D1-True → 1 of 4.
Branch=41×100%=25%
Untaken branch outcomes (the exact answer the prompt asks for): D1-False, D2-True, D2-False.
Notice we never mix the two lists: lines live in the 5-denominator, outcomes in the 4-denominator.
Problem. Find the smallest set of test inputs for sign that gives 100% branch coverage. Confirm it also gives 100% line, and explain why branch coverage drags line coverage up with it.
Recall Solution
We must take both outcomes of D1 and both outcomes of D2.
sign(5): D1=True → returns before D2. Covers D1-True, lines if n>0,return "pos".
sign(-5): D1=False, D2=True. Covers D1-False, D2-True, lines if n<0,return "neg".
sign(0): D1=False, D2=False. Covers D2-False, line return "zero".
Three tests {5,−5,0} hit all 4 branch outcomes → 44=100% branch, and every line ran → 100% line.
Why full branch forces full line (intuitive proof): every executable line lives on some branch — either the True side or the False side of the decision that guards it (a line with no guard sits on the always-entered "main" branch, which is trivially taken whenever the function runs). To reach 100% branch you must walk every True side and every False side at least once. But walking a branch means executing every line inside it. So once all branches are walked, no line has been skipped — line coverage is automatically 100%. The reverse fails: you can execute the single line if n>0 (100% of that line) while only ever taking its True outcome, leaving the False outcome — and its guarded lines — untouched. That is why branch ⊇ line but not the other way. See the figure below.
Problem. How many feasible paths does this have, if the loop body runs 0, 1, or 2 times only (input restricted)?
def g(items): total = 0 for it in items: # 0..2 iterations total += it return total
Recall Solution
A for is a decision "enter body?" checked each time. The distinct paths are:
0 iterations (empty list),
1 iteration,
2 iterations.
→ 3 paths. If iterations were unbounded (0..∞), path count is infinite — this is exactly why path coverage is often infeasible for loops. The figure below shows this fan-out.
def f(a, b): if a: x = 1 else: x = 2 if b: y = 1 else: y = 2 return x + y
Tests are f(True, True) and f(False, False). Compute branch % and path %, and name the untested paths.
Recall Solution
Branch: outcomes are D1{T,F}, D2{T,F} = 4. (T,T) gives D1-T, D2-T; (F,F) gives D1-F, D2-F. All 4 hit → 44=100%branch.
Paths: two sequential independent ifs → 22=4 paths: (T,T),(T,F),(F,T),(F,F).
Tested: (T,T),(F,F). Untested: (T,F) and (F,T) → 42=50%path.
A bug that only fires when a=True and b=False slips past 100% branch.
if user is not None and user.active: # C1 and C2 login()
Tests: user = None, and user = ActiveUser. Compute decision (branch) coverage and condition coverage. Which sub-condition is under-tested?
Recall Solution
Decision coverage (= branch coverage) = the whole condition's T/F.
user=None: C1 is False → short-circuits → whole decision False.
user=ActiveUser: C1 True, C2 True → whole decision True.
→ both decision outcomes seen → 22=100%decision/branch.
Condition coverage = each sub-term needs both T and F.
C1 = (user is not None): seen False (None) and True (ActiveUser) → covered.
C2 = user.active: only ever True (in the None case, short-circuit means C2 is never evaluated). C2=False is never tested.
→ condition coverage = 43=75%. C2 (user.active) is under-tested.
Fix: add user = InactiveUser so C2 evaluates to False. For safety-critical code use MC/DC (Modified Condition/Decision Coverage), which additionally requires each condition to independently flip the whole decision — see MC-DC Coverage.
Problem. A test suite gives 100% line and 100% branch on grade:
def grade(x): if x > 50: return "pass" return "fail"
The tests are grade(60) and grade(40) — but neither asserts anything. A mutation flips > to >=. Would any test fail? What does this reveal?
Recall Solution
With no assert, both tests just run the code. Flipping > to >= changes behaviour only at x=50 — and no test uses 50, and no test checks the return value anyway. No test fails. The mutation survives.
Reveals: coverage measured execution (100%) but the suite has 0 assertions → a vanity metric. Mutation Testing exposes this by killing mutants; here the surviving mutant proves the suite is worthless despite 100% coverage.
Why 4 is minimal (concrete argument). Each test is a single input t, and one input walks exactly one route to a return, so it produces exactly one outcome for each decision it reaches. There are 4 distinct return statements — "invalid", "cold", "normal", "fever" — and each is reachable only by a mutually exclusive range of t: t<0, 0≤t<37, 37≤t≤39, t>39. No single input can lie in two of these ranges at once, so no single test can trigger two returns. To hit branch outcome D3-False you must reach "fever" (needs t>39); to hit D2-True you must reach "cold" (needs t<37) — these are different tests. Working through all six outcomes, the outcomes D1-True, D2-True, D3-True, D3-False each force a distinct return, so you need at least those 4 returns → at least 4 tests. We achieved it with exactly 4, so 4 is minimal. ∎
Problem. For f(a,b) from L3.1, you already have (T,T) and (F,F). Which two additional tests reach 100% path? What is the total then? Sketch how the test count grows if there were n such ifs.
Recall Solution
Missing paths were (T,F) and (F,T). Add f(True, False) and f(False, True).
Now all 22=4 paths executed → 44=100%path, using 4 tests total.
The exponential blow-up, made concrete. Each independent ifdoubles the number of complete routes: 1 if → 2 paths, 2 ifs → 22=4, 3 ifs → 23=8, ..., n ifs → 2n. Full path coverage needs one test per path, so the test count doubles with every added decision. The figure below plots this — branch coverage stays linear (2n outcomes) while path coverage climbs exponentially (2n), which is why we reserve path/MC-DC for small, safety-critical cores.
Problem. For classify (L4.1), the Cyclomatic Complexity is V=D+1 where D = number of decisions. Compute V, and explain intuitively why V bounds the number of tests needed for branch coverage.
Recall Solution
Decisions D1, D2, D3 → D=3, so
V=D+1=3+1=4.
Where V=D+1 comes from (intuitive derivation). Draw the code as a Control Flow Graph: nodes are statements, edges are "flow to next". Start with the single straight-line spine — that is 1 path. Now every decision adds a fork — a brand-new edge peeling off to an alternative route that did not exist before. Each fork you add gives you one more independent way through the graph. Start at 1 (the spine) and add 1 per decision → V=D+1 independent paths. Because these independent paths between them traverse every edge (every branch outcome) of the graph, walking V of them is enough to take every branch outcome at least once. So V is an upper bound on the tests a smart tester needs for branch coverage (and a lower bound on independent-path testing).
Sanity check against L4.1: our minimal branch suite for classify used exactly 4 tests, matching V=4. The figure below shows the spine-plus-forks picture.
Problem. Module A: 95% branch, 90% of mutants killed. Module B: 100% branch, 40% of mutants killed. Which module's test suite is more trustworthy, and why? What single action helps B most?
Recall Solution
Module A is more trustworthy. Branch coverage is a lower bound on ignorance — B's 100% only proves every outcome executed, not that results were checked. B's low mutant-kill rate (40%) reveals missing assertions: mutations survive because tests don't verify behaviour. A, despite slightly lower coverage, actually detects 90% of injected bugs.
Best single action for B: add assertions on outcomes (or adopt Test-Driven Development / Unit Testing discipline so every test checks a result), then re-run Mutation Testing. Raising B's coverage from 100% to "100%" does nothing; raising its kill rate does everything.
def h(a): if a > 10: x = 1 # D1 else: x = 0 if a > 20: y = 1 # D2 else: y = 0 return x + y
There are 22=4 syntactic paths. How many are feasible, and what is true path coverage's maximum?
Recall Solution
Recall from the intro: a path is feasible only if some real input walks it; it is infeasible when its required choices contradict one another. Here the two decisions are not independent: a > 20 mathematically impliesa > 10 (any number above 20 is automatically above 10). So a path demanding D1=False (i.e. a ≤ 10) together with D2=True (i.e. a > 20) asks for a number that is both ≤ 10 and > 20 — impossible.
Checking all four combinations:
(D1-T, D2-T) — needs a>10 and a>20: e.g. a = 25 ✓ feasible.
(D1-T, D2-F) — needs a>10 and a≤20: e.g. a = 15 ✓ feasible.
(D1-F, D2-F) — needs a≤10 and a≤20: e.g. a = 5 ✓ feasible.
(D1-F, D2-T) — needs a≤10 and a>20: no such number ✗ infeasible.
So feasible paths = 3, not 4. True path coverage's maximum is 33=100% — you can reach 100% by covering exactly those 3 with tests a ∈ {25, 15, 5}. If a tool naïvely used the syntactic denominator 4, the best you could ever score would be 43=75%, permanently stuck below 100% through no fault of the tests. Lesson: the path denominator must count only feasible paths; correlated conditions shrink it, and a good coverage tool must prune the impossible combination before reporting.
Recall Master recall — one line each
Line % of sign(5) on the 5-line sign function ::: 40%
Branch % of a single test taking 1 of 4 outcomes ::: 25%
Untaken branch outcomes after sign(5) ::: D1-False, D2-True, D2-False
Tests needed for 100% branch on sign ::: 3 (e.g. 5, -5, 0)
Path % of f with tests (T,T),(F,F) ::: 50%
Condition coverage % in the short-circuit login example ::: 75%
Minimal tests for 100% branch on classify (4 exclusive returns) ::: 4
Cyclomatic complexity of classify (3 decisions) ::: 4
Feasible paths of h where a>20 implies a>10 ::: 3