Menu

Searches titles, sections and text. ↑ ↓ to move, Enter to open, Esc to close. Opens with / or Ctrl+K.

    Contact

    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:

    1. every statement in the program has been executed;
    2. every decision has taken all possible outcomes;
    3. every condition in a decision has taken all possible values;
    4. 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:

    TestimpactoccupiedarmedDecision
    1truetruetruetrue
    2falsetruetruefalse

    That pair demonstrates independence for impact. Now the same for occupied:

    TestimpactoccupiedarmedDecision
    1truetruetruetrue
    3truefalsetruefalse

    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.

    Tree: each path is unique
    Tree: each path is unique
    if (a && b)
    if (a && b)
    Condition vector derivable from the path
    Condition vector derivable from the path
    branch trace is sufficient at -O0
    branch trace is sufficient at -O0
    Diamond: two paths, one block
    Diamond: two paths, one block
    if (a || b)
    if (a || b)
    The path alone stops distinguishing them
    The path alone stops distinguishing them
    independent of optimization level
    independent of optimization level
    T
    T
    F
    F
    a
    a
    T
    T
    F
    F
    b
    b
    act()
    act()
    skip
    skip
    F
    F
    T
    T
    a
    a
    T
    T
    b
    b
    F
    F
    reached two ways
    reached two ways
    skip
    skip
    Whether a decision reconverges into a diamond is decided by the compiler; how often it happens is workload- and compiler-dependent, and the report identifies the affected obligations.

    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

    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

    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

    Two evaluations; only the second condition changes, and the decision changes with it.
    TestInputx > 0x < 10Decision
    Ax = 5TTT
    Bx = 10TFF
    • ✓ condition changed (x < 10: T to F)
    • ✓ decision changed (T to F)
    • ✓ other conditions fixed (x > 0 stays T)
    • ✓ pair valid - independence shown

    x < 0 || x <= 0 - why the first condition has no pair

    Whenever the first condition is true, the second is true as well: the evaluation (T, F) does not exist.
    TestInputx < 0x <= 0Decision
    Cx = -1TTT
    Dx = 0FTT
    Ex = 1FFF
    • ✓ condition changed (x < 0: T to F, tests C/D)
    • ✕ decision changed - it stays T
    • ✓ other conditions fixed (x <= 0 stays 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.

    An independence pair in the worked form the definition uses: change one condition, hold the others, watch the decision. The second example is the reason aggregation needs a "coupled" category.