MC/DC, worked through
Modified condition/decision coverage in one example: what an independence pair is, how many tests you need, and where the definition gets slippery.
MC/DC has a reputation for being complicated. The definition is short; the consequences are what take getting used to.
The definition
Modified condition/decision coverage requires, for a decision made up of several conditions, that:
- every statement in the program has been executed;
- every decision has taken all possible outcomes;
- every condition in a decision has taken all possible values;
- every condition has been shown to independently affect the outcome of its decision.
Points 1 to 3 are just statement, decision and condition coverage. Point 4 is MC/DC, and it is the whole story.
Independence, in one example
An MC/DC independence pair consists of two evaluations of the same decision in which the condition under examination changes value, the decision outcome changes, and the other conditions satisfy the independence rule selected by the project. Under unique-cause MC/DC, the other conditions remain fixed. Masking variants permit defined changes where those conditions cannot affect the decision outcome. The report must state which variant is used.
if (impact && occupied && armed)
fire_squib();
Three conditions. To show that impact independently affects the outcome, you
need two test cases that differ only in impact and that produce different
decision outcomes:
| Test | impact | occupied | armed | Decision |
|---|---|---|---|---|
| 1 | true | true | true | true |
| 2 | false | true | true | false |
That pair demonstrates independence for impact. Now the same for occupied:
| Test | impact | occupied | armed | Decision |
|---|---|---|---|---|
| 1 | true | true | true | true |
| 3 | true | false | true | false |
And for armed, pairing test 1 with a fourth case where only armed differs.
Four test cases cover three conditions - because test 1 is reused in every pair. For a decision with n conditions, n + 1 tests are a theoretical minimum in suitable cases where the required independence pairs are feasible and can share test vectors. Real decisions may require more tests, and coupled, masked or infeasible conditions may prevent ordinary unique-cause MC/DC for some obligations. The linear rather than exponential cost is precisely why the criterion exists: it gets most of the assurance of exhaustive testing without 2ⁿ cases.
Where it gets slippery
Short-circuit evaluation
In C and most of its relatives, && and || do not evaluate their right-hand
side if the outcome is already determined. In the example above, if impact is
false, occupied and armed are never evaluated at all.
This is helpful and awkward at once. Helpful, because it makes some independence pairs trivially constructible. Awkward, because “the condition took the value false” and “the condition was not evaluated” are different states, and a measurement has to distinguish them.
In short-circuit languages, a source condition may occur syntactically but not be evaluated on every decision execution. The obligation model therefore distinguishes the condition’s source occurrence, whether it was evaluated, its value when evaluated, and the resulting decision outcome.
Coupled conditions
If the same variable appears twice in a decision, or if two conditions are related by construction, you may not be able to vary one while holding the other fixed.
Consider x > 0 && x < 10. The second condition can be shown to affect
the decision independently while the first remains true: x = 5 gives
true && true, whereas x = 10 gives true && false. This is therefore not
a good example of logical coupling for the second condition.
A clearer coupled example is x < 0 || x <= 0. Whenever x < 0 is true,
x <= 0 is also true. The first condition cannot be toggled in a pair that
keeps the second condition fixed and changes the decision result. The
obligation therefore needs an explicit coupling or infeasibility disposition
rather than a fabricated independence pair.
The standards recognize this. What they ask is that the infeasibility be identified and justified, not that it be silently dropped from the denominator. A percentage that quietly excludes what it could not measure is a different number than it appears to be.
Decisions that reconverge
Some decisions have a control-flow shape where two different combinations of condition values lead to the same successor block. From that merge point on, the executed path no longer tells you which combination it was.
For a measurement that reconstructs condition values from the control flow - which is what any trace-based approach does - this is not a difficulty but a boundary: the information is not in the path. How often this shape occurs is workload- and compiler-dependent - the report should identify the affected obligations rather than assume a general prevalence. The boundary itself is independent of how well the tool is built or how the compiler is configured.
What this means for measurement
MC/DC is the point where the metric stops being a counting exercise and starts depending on structure. Statement coverage needs to know that an instruction ran. MC/DC needs to know which conditions held during each evaluation - which means the decision structure has to be observable.
A reconstructed object-code path can establish only the distinctions that remain in the generated control flow. It must not be used to infer a source-condition value that optimization has removed or merged unless a verified mapping or supplementation method supplies that evidence.
On an unoptimized build with short-circuit evaluation intact, control flow carries that information. On an optimized build, the compiler is free to replace the structure with something that computes the same result and observes nothing
- and then the information is not merely hard to extract, it is no longer present.
That is the practical reason MC/DC on production-representative firmware is a different problem from MC/DC on a host unit test, and why the two are often quietly conflated.
How each obligation is reported
Each MC/DC obligation should be reported as independence pair established, not yet established, coupled, infeasible with justification, deactivated, excluded by approved scope, mapping unresolved, or supplemented by CEDARtools.Anchor. These categories must remain visible in any aggregate summary.
Next: the mapping problem
Related terminology
In this context, the following expressions refer to the same criterion or its variants: MC/DC; modified condition/decision coverage; modified condition decision coverage; independence pairs; unique-cause MC/DC; masking MC/DC. This page uses MC/DC (modified condition/decision coverage) as the canonical term.
Sources
The normative definitions above come from:
- RTCA DO-178C / EUROCAE ED-12C, cited by section (licensed publication);
- the CAST-17 position paper Structural Coverage of Object Code (Certification Authorities Software Team, 2003 - no stable public URL, cited by title).
x > 0 && x < 10 - pair for the second condition
| Test | Input | x > 0 | x < 10 | Decision |
|---|---|---|---|---|
| A | x = 5 | T | T | T |
| B | x = 10 | T | F | F |
- ✓ condition changed (
x < 10: T to F) - ✓ decision changed (T to F)
- ✓ other conditions fixed (
x > 0stays T) - ✓ pair valid - independence shown
x < 0 || x <= 0 - why the first condition has no pair
| Test | Input | x < 0 | x <= 0 | Decision |
|---|---|---|---|---|
| C | x = -1 | T | T | T |
| D | x = 0 | F | T | T |
| E | x = 1 | F | F | F |
- ✓ condition changed (
x < 0: T to F, tests C/D) - ✕ decision changed - it stays T
- ✓ other conditions fixed (
x <= 0stays T) - ✕ no unique-cause pair - the conditions are coupled
The honest disposition is to report this obligation as coupled, with the coupling documented, rather than to fabricate a pair. The checklist above uses unique-cause MC/DC; how the masking variant treats short-circuit operators is discussed in the article.