Condition Coverage
Condition coverage requires that each individual boolean sub-expression within a compound decision is evaluated as both true and false at least once. It is stronger than branch coverage for compound conditions — and the foundation of MC/DC, the coverage standard for safety-critical software.
1 The Hook
A NZ insurer builds a quote engine. A driver qualifies for the safe-driver rate only when age >= 25 && noClaims. The team writes two tests: a 30-year-old with no claims (qualifies), and an 18-year-old with claims (does not). Both tests pass, and the report shows 100% branch coverage — the decision has been seen both true and false. Sign-off.
What no test ever did was check a 30-year-old with a claim. The noClaims condition was only ever evaluated as true; its false value was never exercised on its own. A developer had typed || where they meant &&, and the bug only shows when one condition is true and the other false. Branch coverage was happy — the overall decision had gone both ways — but the individual condition that hid the fault was never tested both ways.
That is the gap condition coverage closes. A compound decision can reach both overall outcomes without each atomic condition inside it being exercised true and false. One condition can quietly dominate the result and mask a bug in another.
2 The Rule
Condition coverage requires each atomic boolean condition inside a compound decision to be evaluated both true and false at least once — not just the overall decision. It does not subsume branch coverage; for real assurance combine both (condition/decision coverage) or use MC/DC, where each condition is shown to independently change the outcome.
3 The Analogy
A flat that needs two keys to open the front door.
A student flat in Dunedin has a front door with two locks — a deadbolt and a latch — and the door only opens when both are unlocked. Checking that the door opens (both keys work) and that it stays shut when you forget your keys (neither works) is branch coverage: the door has gone both ways. But you have never checked the deadbolt key on its own, or the latch key on its own. If the latch key is secretly a dud, you would never know — the deadbolt being locked kept the door shut anyway.
Condition coverage is testing each key by itself: deadbolt-only, latch-only, so every lock is proven to work and to fail independently. MC/DC goes one step further — it proves that turning each single key, on its own, actually changes whether the door opens.
What it is
In most real code, decision points are not simple single conditions — they are compound expressions. if (age >= 18 && hasLicence) contains two atomic conditions: age >= 18 and hasLicence. Branch coverage only cares whether the overall decision evaluates to true or false. Condition coverage goes further: it requires each atomic condition to independently take both a true and a false value across the test suite.
This distinction matters because a compound condition can reach both overall outcomes (true/false) without every atomic condition being exercised on both sides. A bug in one condition might be masked by the other condition always dominating the result.
Atomic condition: a sub-expression within a compound boolean decision that cannot be decomposed further without breaking the boolean nature of the expression. In (A && B) || C, the atomic conditions are A, B, and C. The expressions (A && B) and (A && B) || C are not atomic — they are compound.
Condition coverage vs. branch coverage
Consider this decision: if (A && B)
Branch coverage requires:
- One test where the overall decision is true: A=true, B=true → overall true
- One test where the overall decision is false: A=false, B=anything → overall false
With these two tests, B has only ever been evaluated as true (in the first test where B=true) and as “don’t care” (short-circuit evaluation means B may not even be evaluated in the second test). A bug in the B=false branch of the implementation is never triggered.
Condition coverage additionally requires:
- A evaluated as true at least once
- A evaluated as false at least once
- B evaluated as true at least once
- B evaluated as false at least once
This forces at least one test where B=false is actually evaluated, catching the bug that branch coverage misses.
Important: condition coverage does not subsume branch coverage. It is possible to achieve 100% condition coverage while not covering both branches of a decision. In practice, teams require condition/decision coverage (both criteria combined) or the even stronger MC/DC.
Modified Condition/Decision Coverage (MC/DC)
MC/DC is the gold standard for safety-critical systems (avionics standard DO-178C, automotive AUTOSAR, medical device IEC 62304). It extends condition coverage with one additional requirement: each condition must independently affect the outcome of the decision.
For each atomic condition C, there must be a pair of test cases that differ only in the value of C, while all other conditions remain constant, and the overall decision outcome changes. This proves that C is not redundant and that a fault in C would be observable in the decision output.
MC/DC for a decision with N conditions requires at minimum N+1 test cases (compared to 2² for full combinatorial coverage). For a decision with 5 conditions, MC/DC needs at least 6 test cases, not 32.
Worked example: insurance eligibility
A function determines insurance eligibility with the condition:
if (age >= 18 && hasLicence && noClaims) {
eligible = true;
} else {
eligible = false;
}
Three atomic conditions: A = age ≥ 18, B = hasLicence, C = noClaims.
Branch coverage minimum: 2 tests (overall true, overall false). Condition coverage minimum: 3 pairs of tests, one for each condition evaluated both ways.
| TC | A: age ≥ 18 | B: hasLicence | C: noClaims | Eligible | Conditions exercised |
|---|---|---|---|---|---|
| TC1 | T (age=25) | T | T | true | A=T, B=T, C=T |
| TC2 | F (age=16) | T | T | false | A=F — covers A both ways with TC1 |
| TC3 | T (age=25) | F | T | false | B=F — covers B both ways with TC1 |
| TC4 | T (age=25) | T | F | false | C=F — covers C both ways with TC1 |
| Condition under test | Test case 1 | Test case 2 | A | B | C | Result changes? |
|---|---|---|---|---|---|---|
| A independently affects outcome | TC1 | TC2 | T→F | T (same) | T (same) | Yes (true→false) |
| B independently affects outcome | TC1 | TC3 | T (same) | T→F | T (same) | Yes (true→false) |
| C independently affects outcome | TC1 | TC4 | T (same) | T (same) | T→F | Yes (true→false) |
TC1 through TC4 achieve both condition coverage and MC/DC for this three-condition decision. Four test cases cover what branch coverage would achieve with two tests — but each of TC2, TC3, and TC4 targets a specific condition in isolation that branch coverage leaves unverified.
Deriving condition coverage test cases systematically
- Identify atomic conditions — decompose every compound boolean decision in the code into its atomic sub-expressions. Each sub-expression that cannot be split further is one condition to cover.
- For each condition, find a baseline — a test case where all other conditions are true (for AND logic) so the condition under test independently determines the outcome. This is your MC/DC anchor test.
- Create a partner — copy the baseline test and flip only the condition under test. Verify the overall decision outcome flips. This is the MC/DC pair.
- Verify coverage — check that every atomic condition appears as true in at least one test and false in at least one test across the complete test suite.
- Add branch coverage check — confirm the overall decision is true in at least one test and false in at least one test. Combined condition/decision coverage is now achieved.
ISTQB mapping
| Syllabus ref | Topic | Level |
|---|---|---|
| CTFL 4.3.3 | Condition coverage — basic definition and distinction from branch coverage | Foundation |
| CTAL-TA 4.3 | Condition coverage, condition/decision coverage, and MC/DC — full application | Advanced / Senior |
| CTAL-TA 4.3 K4 | Analyse code to determine test cases achieving condition coverage and MC/DC | Advanced LO |
Foundation candidates need to know that condition coverage exists and how it differs from branch coverage. Advanced (CTAL-TA) candidates must be able to derive test cases that achieve MC/DC for a given compound decision — this is a K4 (analyse) objective.
Common mistakes
- Confusing condition coverage with branch coverage — they are not equivalent. Branch coverage checks the overall decision outcome; condition coverage checks each atomic sub-expression. A test suite can achieve 100% branch coverage while leaving conditions untested in one direction.
- Forgetting short-circuit evaluation — in most languages,
A && Bdoes not evaluate B if A is false. A test where A=false may not exercise B at all. Design tests specifically to ensure B is evaluated (set A=true) when testing B. - Not testing each condition independently — basic condition coverage only requires each condition to be true and false somewhere. MC/DC additionally requires independence. Write the independence argument explicitly for safety-critical work.
- Applying condition coverage to trivial single-condition decisions — if a decision has only one atomic condition (e.g.,
if (isLoggedIn)), condition coverage is identical to branch coverage. Only invest in the distinction when decisions are compound. - Assuming 100% condition coverage means no bugs — condition coverage verifies that each condition is exercised both ways. It does not verify that the logical operator between conditions is correct. A developer who wrote
||instead of&&may not be caught by condition coverage alone.
4 Now You Try
Three graded exercises — spot, fix, then build. Write your answer, run it for AI feedback, then compare to the model answer.
A KiwiSaver first-home grant uses if (yearsContributing >= 3 && isFirstHome). Two tests run: (a) yearsContributing=5, isFirstHome=true → eligible; (b) yearsContributing=1, isFirstHome=true → not eligible. State which atomic conditions are evaluated true and false across these tests, and name the condition that is never tested both ways.
Show model answer
Condition A (yearsContributing >= 3): test (a) makes A=true (5 >= 3), test (b) makes A=false (1 >= 3 is false). A is covered both ways. ✓ Condition B (isFirstHome): both tests use isFirstHome=true, so B is only ever evaluated TRUE. B=false is never tested. ✗ Condition never tested both ways: B (isFirstHome). Why it matters: a defect that fires only when isFirstHome is false — for example, wrongly granting the first-home grant to a repeat buyer who has contributed long enough — would never be triggered. Condition coverage requires each atomic condition to be evaluated both true and false; a third test with isFirstHome=false is needed.
A tester claims the two tests below give full condition coverage for if (isResident && age >= 18) on an electoral-roll enrolment check. The claim is wrong. Rewrite the set so each atomic condition is evaluated both true and false, and add the branch-coverage check.
TC1: isResident=true, age=30 → enrol
TC2: isResident=true, age=15 → reject
"Both conditions covered — full condition coverage."
Rewrite for full condition coverage:
Show model answer
Correct condition-coverage set for (isResident && age >= 18): - TC1: isResident=true, age=30 → A=true, B=true → overall TRUE (enrol) - TC2: isResident=false, age=30 → A=false, B=true → overall FALSE - TC3: isResident=true, age=15 → A=true, B=false → overall FALSE Now isResident is both true (TC1, TC3) and false (TC2); age >= 18 is both true (TC1, TC2) and false (TC3). Each atomic condition is evaluated both ways. ✓ What was wrong with the original: - isResident was true in BOTH tests — its false value was never evaluated, so condition coverage was NOT achieved. - Only age changed between the two tests; isResident was never exercised as false. - The two tests do happen to give branch coverage (overall true and overall false), which is probably why it looked complete — but branch coverage ≠ condition coverage. Add the branch check explicitly: overall TRUE in TC1, overall FALSE in TC2/TC3 — both branches covered too.
A medical-device dosing guard for a NZ hospital uses if (A && B && C) where A = weightOk, B = ageOk, C = noAllergy. Design an MC/DC test set: show, for each condition, a pair of tests that differ only in that condition and flip the overall outcome. State the minimum number of tests.
Show model answer
MC/DC for (A && B && C), minimum N+1 = 4 tests: - TC1 (baseline): A=T, B=T, C=T → overall TRUE - TC2: A=F, B=T, C=T → overall FALSE (pairs with TC1 to prove A independently flips the outcome — only A changed, result changed) - TC3: A=T, B=F, C=T → overall FALSE (pairs with TC1 to prove B independently flips the outcome) - TC4: A=T, B=T, C=F → overall FALSE (pairs with TC1 to prove C independently flips the outcome) For an AND of N conditions, the baseline has all conditions true; each partner flips exactly one condition false, holding the others true, so the single changed condition is the sole cause of the outcome changing from true to false. That is the independence MC/DC demands. Four tests, not 2³=8 — MC/DC is far cheaper than full combinatorial coverage while still proving each condition matters.
Self-Check
Click each question to reveal the answer.
Q1: What does condition coverage require that branch coverage does not?
Condition coverage requires each atomic boolean sub-expression inside a compound decision to be evaluated both true and false at least once. Branch coverage only requires the overall decision to go true and false — which a compound condition can do while one of its atomic conditions is never exercised both ways.
Q2: Does condition coverage subsume branch coverage?
No. It is possible to achieve 100% condition coverage without taking both branches of the overall decision. That is why teams require condition/decision coverage (both criteria combined) or the stronger MC/DC, rather than condition coverage alone.
Q3: How does short-circuit evaluation complicate condition coverage?
In most languages, A && B does not evaluate B when A is false. A test with A=false may never run B at all, so B is not exercised. To exercise B you must set A=true and vary B — design tests deliberately so the condition under test is actually reached and evaluated.
Q4: What extra requirement does MC/DC add on top of basic condition coverage?
Independence: each condition must be shown to independently affect the outcome. For every condition there must be a pair of tests that differ only in that condition while all others are held constant, and the overall decision outcome changes. This proves the condition is not redundant and that a fault in it would be observable.
Q5: For a decision with five atomic conditions, how many tests does MC/DC need, and how does that compare to full combinatorial coverage?
MC/DC needs at minimum N+1 = 6 tests for five conditions. Full combinatorial coverage would need 2⁵ = 32. MC/DC gives strong, independence-proven coverage at a fraction of the cost, which is why safety-critical standards (DO-178C, IEC 62304) mandate it rather than exhaustive testing.
Related techniques
Condition coverage sits above Branch Coverage in strength: branch coverage is a prerequisite, and condition coverage adds the per-atomic-condition requirement on top.
For the complete white-box coverage hierarchy: Statement Coverage ⊂ Branch Coverage ⊂ Condition Coverage ⊂ MC/DC ⊂ Path Coverage. Each level subsumes the one below it, but adds test cases that the lower level misses.
The conditions you are testing with condition coverage often correspond directly to the rows of a Decision Table. Combining both approaches — decision tables to enumerate the specification logic, condition coverage to verify the implementation — gives strong coverage from both ends.
Practice this technique: Try Test Lead Practice 07 — Test coverage gaps.