01 / The idea
What is MC/DC coverage?
A test can execute a line without challenging the logic on it. It can take both exits from a decision without showing why either exit was taken. MC/DC adds a more demanding question: can this condition change the decision when the influence of the other conditions is controlled?
The unit of evidence is an independence pair: two executions or model assignments that satisfy the selected MC/DC rule for a particular condition. Different conditions can reuse the same test. The full structural criterion also includes condition outcomes, decision outcomes, and entry/exit execution. A report limited to Boolean pairs must say that is its scope.[1]
| Term | Meaning | In this guide’s example |
|---|---|---|
| Condition | An individual Boolean test, such as temperature > 80. Negating that test does not add another condition.[11] | A, B and C |
| Decision | A Boolean expression with an overall true or false result; it need not be written inside an if. | (A && B) || C |
| Independence pair | Two cases that establish one condition’s independent effect under the declared variant. | 100 ↔ 110 isolates B in the complete Boolean model. |
Our teaching rule sounds an alarm when temperature is high and cooling has failed, or a manual alarm is active. Here, A means “temperature high,” B means “cooling failed,” and C means “manual alarm.” Treat the three inputs as independent Boolean values. Real sensor thresholds, timing, fault states and actuator behavior need their own requirements and checks.
#include <stdbool.h>
bool alarm(bool hot, bool cooling_failed, bool manual_alarm) {
return (hot && cooling_failed) || manual_alarm;
}
Start with two conditions
A first MC/DC example: A && B
For a simple AND decision, three cases are enough to demonstrate both conditions’ independent effects. Keep the other input true so that it cannot force the AND to false.
| Case | A | B | D = A && B | Pair to inspect |
|---|---|---|---|---|
| T1 | True | True | True | Shared by both pairs. |
| T2 | False | True | False | T1 ↔ T2 isolates A. |
| T3 | True | False | False | T1 ↔ T3 isolates B. |
Each pair changes one input and the decision. One test can support several pairs. The false/false combination adds no new independence witness here. This is a Boolean planning example; actual input feasibility, assertions and instrumented observations still need checking.
02 / Original experiment
100% branch coverage.
A condition still has no witness.
We compiled the equivalent C predicate with GCC 13.3.0 at -O0 --coverage and ran three cases. Then we inspected gcov -b -c. The result: all six compiler-reported branch outcomes were taken. Each of the three conditions also evaluated both true and false in these executions. Yet no pair establishes B’s independent effect.
measured by gcov
computed over the inputs
logical witness set
| Input ABC | A observed | B observed | C observed | Decision |
|---|---|---|---|---|
010 | False | — | False | False |
101 | True | False | True | True |
110 | True | True | — | True |
Look only at the two cases in which B is evaluated: 101 and 110. B changes, but the decision stays true. The supplied C input also changes; C is skipped in 110. There is no evidence here that B can change the final answer.
MC/DC test cases: a four-case worked example
Add 100 → false. Now 100 ↔ 110 isolates B, and 100 ↔ 101 isolates C. The original 010 ↔ 110 pair already isolates A in the complete-input model. Four cases establish all three logical independence obligations:
| Case | A: hot | B: cooling failed | C: manual | Expected D | Witness use |
|---|---|---|---|---|---|
010 | False | True | False | False | Pairs with 110 for A. |
100 | True | False | False | False | Pairs with 110 for B, and 101 for C. |
101 | True | False | True | True | Pairs with 100 for C. |
110 | True | True | False | True | Pairs with 010 for A, and 100 for B. |
These columns show supplied inputs, including values that short-circuit execution may skip. The interactive trace table shows which conditions actually get evaluated in the model.
The consequence is concrete: the wrong rule A || C, with the cooling condition accidentally removed, returns the same answers for all three original inputs. The added case 100 distinguishes them: the intended rule returns false; the broken rule returns true. That is one specific fault this extra case exposes, not a guarantee against every defect.
Raw branch report ↗Inputs, outputs & calculation ↗Reproduce the experiment ↓
How we checked the original result
There are eight complete input assignments for three independent Booleans, and 256 possible subsets of those assignments. We evaluated every subset against the strict rule: only the target input changes and the decision changes. 26 subsets qualify; the smallest have four cases; exactly two four-case suites qualify. They are {010, 011, 100, 110} and {010, 100, 101, 110}.
The calculation assumes all eight assignments are feasible. Python enumeration was checked against compiled C outputs for all eight rows, and the browser’s implementation was compared with all 256 expected subset results. These are reproducible findings about this example, not a claim about the number of tests every project needs. The kit contains the source, scripts, commands and recorded outputs.
03 / Count the obligations
How to calculate MC/DC coverage
First define what the report counts. For this guide’s logical condition-independence score, divide the number of conditions with at least one valid pair by the number of condition obligations, then multiply by 100:
| Selected cases | Conditions demonstrated | Calculation | Still missing |
|---|---|---|---|
010, 101, 110 | A | 1 ÷ 3 × 100 ≈ 33.3% | B and C under this strict input-pair model. |
010, 100, 101, 110 | A, B and C | 3 ÷ 3 × 100 = 100% | No logical independence obligation in this model. |
Count each condition obligation once, even if it has several valid pairs. Across decisions using the same counting unit, add the satisfied obligations and total obligations before dividing. For a hypothetical report with three obligations in one decision and five in another, (1 + 3) ÷ (3 + 5) = 50%; averaging 1/3 and 3/5 would incorrectly give about 46.7%.
The browser lab below implements this exact fixed model. It calculates test-pair adequacy; it does not instrument your application.
MC/DC test-pair calculator: explore this example.
In this illustrative alarm rule, A means temperature high, B means cooling failed, and C means manual alarm. The alarm is on when (A && B) || C is true. All eight input combinations are assumed feasible. This calculator uses one fixed expression. For one of your own rules, generate cases in Proof’s free Spec Lab micro-tool. For a whole project, see Proof’s portal and MCP workflow; for executed code coverage, see Proof’s MC/DC measurement paths.
Watch / 2 min 08 sec
Follow the missing pair.
The same example, one step at a time: the green branch report, the missing witness, and the extra case that changes what the suite establishes.
Narrated, with on-screen captions. Measured GCC branch coverage and calculated logical pairs are labeled separately.
Read the complete video transcript
Narrated, fully captioned walkthrough. Expression: D = (A && B) || C. A = temperature high; B = cooling failed; C = manual alarm. Teaching fixture. Branch coverage is measured with GCC/gcov 13.3.0 at -O0. The MC/DC witness analysis is logical strict unique-cause analysis, not an instrumented MC/DC report.
0:00–0:11 — A covered branch can hide an untested condition.
Caption, verbatim: This alarm rule has three Boolean conditions. It triggers when temperature is high and cooling has failed, or a manual alarm is active.
Visual information: On-screen rule: D = (A && B) || C. A: temperature high. B: cooling failed. C: manual alarm. Boolean teaching model; no timing or system-state claim.
0:11–0:27 — Three tests. A green branch report.
Caption, verbatim: The starter suite exercises all six compiler-reported branch outcomes. Each condition is observed both true and false. This measured GCC result still does not tell us whether each condition can independently change the decision.
Visual information: On-screen table: case 010 has A=0, B=1, C=0, D=false; case 101 has A=1, B=0, C=1, D=true; case 110 has A=1, B=1, C=0, D=true. Recorded branch result: 6 of 6 branch outcomes observed. GCC and gcov 13.3.0; -O0 --coverage; executed teaching fixture.
0:27–0:46 — Ask what changes on its own.
Caption, verbatim: For strict unique-cause pairs, change only the condition of interest and keep other input values fixed. Cases 010 and 110 isolate A. The starter suite has no such pair for B or C. Masking is a different rule.
Visual information: On-screen comparison: case 010 (A=0, B=1, C=0, D=false) versus case 110 (A=1, B=1, C=0, D=true). Only A changes. Under strict unique-cause logical analysis: A has a pair; B and C have no pair in this starter suite.
0:46–1:09 — One more case changes the picture.
Caption, verbatim: Add case 100, whose expected decision is false. Now 100 and 110 isolate B, while 100 and 101 isolate C. Together with the A pair, four tests provide a complete strict unique-cause witness set for this Boolean model.
Visual information: On-screen table: isolate A with 010 ↔ 110 while B=1 and C=0; isolate B with 100 ↔ 110 while A=1 and C=0; isolate C with 100 ↔ 101 while A=1 and B=0. Four tests; three witness pairs; strict unique-cause model analysis.
1:09–1:28 — Skipped does not mean false.
Caption, verbatim: The C program short-circuits its logical operators. A dash means a condition was not evaluated. Preserve that information: an assigned input value is not an observed condition result. A truth table cannot substitute for an execution trace.
Visual information: On-screen execution traces in A, B, C order: input 010 gives F–F and D=false; input 101 gives TFT and D=true; input 110 gives TT– and D=true; input 100 gives TFF and D=false. A dash means not evaluated. Assigned input is not the same as observed result.
1:28–1:49 — Three questions. Three kinds of evidence.
Caption, verbatim: This walkthrough computes logical pairs and measures compiler branch coverage. It does not claim an instrumented MC/DC result. A code coverage report also cannot establish that the requirement is complete or that every expected output is correct.
Visual information: On-screen evidence map: the rule model asks whether each modeled condition can matter, supported by logical witness pairs; running code asks whether implementation conditions were independent, supported by an instrumented MC/DC report; required behavior asks whether the program did what was required, supported by assertions and observed outputs. This video has measured branch results and logical pairs, and makes no measured MC/DC claim.
1:49–2:08 — Make the next test useful.
Caption, verbatim: Use Spec Lab to turn one decision specification into a reviewable test table. Review the interpretation, implement the cases, then measure the code and check its behavior. Keep the requirement, revision, results and remaining gaps together.
Visual information: On-screen closing: Write the rule. Plan the pairs. Run the code. Inspect the evidence. Try the free Spec Lab: one decision, a test table you can review, reqproof.com/spec-lab. Method footer: branch result uses GCC 13.3 at -O0; MC/DC pairs are Boolean analysis.
05 / The working method
How to design MC/DC test cases and measure coverage
Start from behavior you can explain. Use coverage to challenge the resulting test suite, and investigate what the measurement leaves unresolved. Writing tests solely to turn a report green can preserve the implementation’s mistakes.
1 Write the requirement and the expected result
Describe when the decision should be true, when it should be false, and which failures matter. State the units, boundaries and relevant state. For the alarm example, “temperature high” still needs a threshold rule; “manual alarm” needs an agreed meaning. Derive expected outcomes from that agreement, rather than copying what today’s function happens to return.
2 Inventory the actual decisions and condition occurrences
Identify the scoped functions, build configuration and expressions the tool can measure. Count each condition occurrence, including repeated occurrences, and confirm how the instrumenter maps them. (A && B) || (A && C) contains two occurrences of A; a calculator that silently merges them changes the coverage problem. Keep unsupported constructs visible.
3 Choose the variant and find feasible witness pairs
Write down whether you need strict unique-cause, a short-circuit-aware interpretation, or masking MC/DC. For each condition, find two feasible cases that satisfy that rule. Record why the other conditions cannot explain the decision change. If a proposed combination is impossible, preserve the constraint and investigate it; inventing an impossible test does not close a gap.
For strict pairs, choose a context in which the target can determine the result, then flip only that input. Here is how to construct the pairs for (A && B) || C:
| Target | Hold these values fixed | Why this exposes the target | Pair |
|---|---|---|---|
| A | B=true; C=false | The AND follows A, and C cannot force the answer true. | 010 ↔ 110 |
| B | A=true; C=false | The AND follows B, and C cannot force the answer true. | 100 ↔ 110 |
| C | A=true; B=false | The AND is false, so the final OR follows C. | 100 ↔ 101 |
Other contexts can expose C too. Whatever context you choose, verify that both assignments can reach the decision in the real application.
Download the test-pair worksheet to adapt the example. Record each requirement, condition, input pair, expected result, fixed context and feasibility constraint. After execution, add the code revision, test IDs, tool version and measured-witness reference; keep the status “planned” until that run exists. Import three-bit input assignments as text in a spreadsheet to preserve leading zeros.
4 Implement assertions and run the instrumented build
Translate each case into real inputs and assert the required output or state change. Preserve the language’s evaluation order and side effects. Run the compiler or source instrumenter that supports the chosen coverage criterion. A test plan, an imported line report and a successful test command are three different artifacts.
5 Inspect the missing obligations, not just the percentage
Read the per-decision conditions, outcomes, witness pairs and skips. Investigate unreachable combinations, unsupported expressions, deactivated configurations and unexpected executable code. A denominator reduced by exclusions needs a reviewable explanation. Make partial execution and missing instrumentation explicit rather than letting them look like a pass.
6 Keep the evidence tied to the change
Retain the requirement version, source revision, tool and version, build flags, test identifiers, inputs, assertions, results and exclusions. Name the reviewer or acceptance authority. When code, configuration or intent changes, determine which evidence needs rerunning. A passing report from another revision is historical evidence.
This workflow joins requirements-based verification with structural coverage analysis. The distinction is central to the DO-178C assurance discussion: coverage helps assess how thoroughly requirements-based tests exercised the structure.[3]
06 / What costs you later
MC/DC mistakes and their consequences
The expensive part is often discovering that the evidence cannot support the decision you already made. Catch these gaps while the requirement, code and tests are still easy to change.
| Mistake | What it costs | What to do |
|---|---|---|
| Treat a skipped condition as false | False confidence in pairs the program never observed. | Preserve true, false and not-evaluated as distinct values. |
| Count generated cases as executed coverage | An acceptance decision based on a plan rather than an observed run. | Label modeled pairs; obtain implementation evidence separately. |
| Write the oracle from the current implementation | A precise test that approves the same wrong behavior. | Derive the assertion from the approved requirement. |
| Force an infeasible pair or silently exclude it | Wasted test work or an unexplained hole in the denominator. | Record the constraint; review redundancy, scope and permitted disposition. |
| Merge stale profiles into the new build | Coverage that cannot be attributed to the candidate revision. | Control profile lifecycle and preserve build provenance. |
| Stop at 100% | Missed requirements, bad boundaries, state errors or concurrency defects. | Review the behavior and risk inventory alongside structural evidence. |
One useful review question is: What could still be wrong even if every obligation in this report passes? The answer helps decide which boundary, property, integration, fault-injection or concurrency checks to add.
07 / Know the denominator
MC/DC vs branch, condition and multiple-condition coverage
Coverage numbers describe different obligations. A “100%” label is meaningful only with its metric, supported scope and treatment of exclusions. Compiler branch edges also need not map one-to-one to the source-level decisions a reader sees.
| Criterion | Evidence it asks for | What it can leave unresolved |
|---|---|---|
| Statement / line | Execution of the tracked statements or lines. | Untested outcomes and compound logic. Lines and statements are also different counting units. |
| Decision | The complete Boolean decision takes true and false: two outcomes for this example. | Which condition independently affected a compound decision. |
| Compiler branch outcomes | All instrumenter-reported control-flow outcomes. GCC reports six for this compiled example. | A branch count is tool-dependent; it is not automatically the same denominator as source-level decision coverage. |
| Condition | Each tracked condition takes true and false. | Whether changing that condition changes the overall result. |
| MC/DC | Condition and decision outcomes, plus independent-effect witnesses under the selected variant; full definitions also include entry/exit execution. | Unwritten requirements and behaviors outside the measured structure and test inputs. |
| Multiple-condition | All required feasible combinations of condition values under the declared evaluation model. | Correct expectations, complete requirements and untested input values within each Boolean case. |
The three-test experiment above is a concrete counterexample to “branch plus condition coverage implies MC/DC.” Both ordinary metrics are satisfied, but B has no independent-effect witness. The four-case logical suite is also not exhaustive: the model has eight assignments and five distinct short-circuit traces, and that suite omits the trace F – T.
Unique-cause vs masking MC/DC
The target condition and decision must change. These selected interpretations differ in which other changes they permit and how they treat conditions that were not evaluated.
| Interpretation | Other conditions | Where it applies here |
|---|---|---|
| Strict unique-cause | All other supplied condition values remain fixed. | The lab’s complete-input Boolean model and subset calculation. |
| Unique-cause + short-circuit | GNATcoverage permits specified differences in conditions skipped by short-circuit evaluation. | GNATcoverage stmt+uc_mcdc; use its documented rules.[11] |
| Unique-cause + masking | The FAA study allows masking for strongly coupled conditions, retaining unique-cause restrictions for uncoupled conditions. | A named research variant; it is distinct from GNAT’s short-circuit mode.[2] |
| Masking | Other changes are allowed when the selected criterion shows their effects are masked. | GNATcoverage stmt+mcdc; follow the analyzer’s semantics.[11] |
In the starter suite, GNATcoverage’s documented masking interpretation can credit C using 010 ↔ 101: A && B is false in both cases. Strict complete-input unique-cause rejects that pair because A and B change too. B still has no witness in either analysis. This is why the lab labels its result “strict logical independence” instead of pretending all tools will report the same percentage.[11]
Does MC/DC always need n + 1 tests?
No. For strict unique-cause analysis with n independently demonstrable condition occurrences, n + 1 is a lower bound; some expressions attain it. Our three-condition example does, with four cases. It is not a universal exact count across expressions, input domains or MC/DC variants. Coupling can also make a particular obligation infeasible. Calculate the pairs and their feasibility rather than treating the count as a coverage certificate.[2]
For this model, “four cases instead of all eight assignments” is an exact result. It is not a promise to halve a project’s test suite. Each Boolean case can still need several real values, boundary checks, error paths and integration contexts.
When is MC/DC required?
The answer depends on the applicable assurance framework, software criticality, and agreed verification approach. Establish those first. A coverage percentage cannot decide which obligations apply to your product.
Airborne software: DO-178C / ED-12C
DO-178C increases structural coverage rigor with software level. NASA’s analysis of the standard summarizes the progression below. These are structural coverage objectives within a larger requirements-based verification process; achieving them does not complete certification.NASA
| Software level | Structural coverage summary |
|---|---|
| Level C | Statement coverage. |
| Level B | Statement and decision coverage. |
| Level A | Statement, decision, and modified condition/decision coverage. |
FAA AC 20-115D recognizes DO-178C as an acceptable means of compliance. The advisory circular explicitly says it is not a regulation or the only possible means. Applicable provisions must be followed when its approach is used.FAA
Also establish the analysis basis: source code, object code or executable object code. The FAA’s DO-178B/C comparison highlights additional verification for compiler-generated executable code that is not directly traceable to source statements, and coverage of data and control coupling between components. A source-level MC/DC percentage does not by itself resolve these obligations. The same comparison records an independence form that allows masking; this guide’s stricter logical model is a teaching choice.[16]
NASA safety-critical software
NPR 7150.2D §3.7.4, SWE-219, requires 100% MC/DC code-test coverage for identified safety-critical software components. Deviations require review and waiver with rationale and technical-authority approval. This scope does not extend automatically to every component in every space project.NPR
Automotive software: establish the applicable ASIL
ISO 26262-6:2018 covers software development and verification for automotive functional safety. For MC/DC decisions, consult the licensed edition’s software-unit verification and structural-coverage provisions, and your project’s ASIL and safety plan. Confirm the method recommendations and any required justification against that edition. The public catalogue establishes its edition and scope, rather than the full method requirements.ISO
Qualification depends on how the tool is used
For certification work, assess whether relying on the coverage tool requires qualification. AC 20-115D points to DO-178C §12.2 and DO-330 for this assessment. A supported tool or qualification kit still needs to fit the intended use, version, configuration, and project context.FAA §10
09 / Proof and coverage tools
MC/DC tools for your codebase
Start with the toolchain and target you must support. Then check the reported criterion, instrumentation limits, and evidence requirements. The best fit is the option whose results you can reproduce and explain within that environment.
MC/DC with Proof
For the languages you actually use.
Proof provides language-specific measurement and import paths for everyday software development and CI. The available evidence differs by path; Python’s current observation path does not reconstruct complete independence witnesses.[20]
- Go
- Rust
- C / C++
- Python
- Ruby
- Bash
- QML
- JavaScript
- TypeScript
- Java
- C#
- PHP
- Zig
- Solidity
Use the path for your language and build, then inspect supported decisions, witnesses, skipped evaluations and unsupported constructs. Ruby, Bash and QML have source-instrumentation engines. C/C++ imports compiler-generated MC/DC reports. Python uses CPython 3.12+ runtime monitoring or imports coverage.py branch data; those paths provide condition/branch observations, not reconstructed independence witnesses, so their observations alone do not establish MC/DC.[12][20] The published examples show the evidence to look for.
Proof’s MC/DC tooling is not certified. It is engineering tooling for your repository and CI. Projects seeking certification credit need a separate assessment against their applicable qualification and verification requirements.
From one rule to the whole project.
Spec Lab is Proof’s free micro-tool for an individual rule. In the Proof portal and via MCP, you’ll be able to apply the same requirement-to-test workflow across the whole project, keeping each formula, test plan and result connected to its requirement.
The portal gives teams a shared view of requirements and evidence. MCP brings that project context into a coding agent’s existing workflow. Proof’s Software Intent Graph connects requirements, code, tests, hazards and evidence; acceptance follows the project’s configured authority.[22]
| Path | Best fit | How measured | What to verify | Cost basis |
|---|---|---|---|---|
| Proof | Teams connecting MC/DC evidence to requirements and changes across languages. | Proof’s measurement workflow, using the integration for the language and build. | Version, supported constructs, variant, witness evidence and provenance. Non-certified tooling. | Free Spec Lab micro-tool for individual rules; project-wide portal and MCP workflows are scoped separately. |
| Clang / LLVM Clang | C/C++ projects already using a supported Clang release. | Compiler instrumentation, execution profiles, and llvm-cov MC/DC reports. |
Matching tool versions; expression limits; warnings and omitted code. | Open-source tooling; budget integration and assurance work separately.LLVM |
| GCC / gcovgcov | Projects using GCC 14 or later with matching gcov. | -fcondition-coverage instrumentation and gcov --conditions. |
Masking semantics, compiler transformations, target support, and report scope.GCC paper | Open-source tooling; budget integration and assurance work separately.GCC |
| GNATcoverageGNAT | Ada and mixed Ada/C/C++ projects, including teams evaluating qualification support. | Source instrumentation, execution traces, and analysis of selected units. | Chosen MC/DC variant, target/runtime support, and applicable qualification material. | Request a quote for the commercial support and qualification scope. AdaCore |
| RapiCover[17] | C, C++ and Ada teams collecting coverage on embedded targets. | Source instrumentation and execution-data collection through the integrated build and target. | Target integration, collection overhead, condition limits, selected criterion and qualification scope. | Request a quote covering licences, integration, support and any qualification material. |
| Simulink Coverage[18] | Teams measuring decisions in Simulink models. | Model coverage with configurable masking or unique-cause analysis. | Analysis mode, block support and test-generation settings; model results do not establish generated-code coverage. | Check the required product licences and your existing MathWorks agreement. |
Tool names alone do not establish equivalent measurements. For example, GNATcoverage distinguishes masking MC/DC, selected with stmt+mcdc, from unique-cause plus short-circuit handling, selected with stmt+uc_mcdc. Record the selected interpretation beside the result. Variants
How we compared the options
This guide is published by Proof and includes our own offering. It is a practical selection guide, not a scored benchmark. We checked official documentation for supported workflows, version requirements, coverage semantics, and instrumentation limits. Proof’s language scope also uses a 27 October 2026 implementation review, with the measurement limits stated above.[20] We did not run a comparative performance study, obtain commercial quotations, or evaluate a qualification package.
Before choosing, take one representative decision through the complete process: compile, execute, inspect its independence evidence, explain exclusions, and reproduce the report from a clean build. Check that someone reviewing the result can identify the code revision, tests, compiler settings, and criterion used. Treat qualification effort and ongoing maintenance as part of the decision.
For an embedded target, also confirm how execution data will leave the device, what storage the instrumented build needs, and whether instrumentation affects the behavior being assessed. Ask the supplier to demonstrate the workflow on your language constructs and build configuration. A successful desktop example establishes a useful starting point; target suitability still needs evidence.
A test-pair planner serves a different purpose: it proposes inputs under a logical model. Your coverage tool observes what the compiled program actually evaluates. Keep both artifacts when they are useful, and investigate differences in reachability, short-circuit handling, or the reported criterion.
Run it against real code
Proof: start with the measured Go example
With the Proof CLI already installed, download the example from the Go measurement guide and run this from its mcdc-cache directory:
proof mcdc measure ./cache --engine go --format json
The command instruments supported decisions and runs project tests. Inspect the resulting conditions, witness pairs and run provenance alongside the requirement assertions. The linked guide includes the recorded run; this command was not rerun for the present article.[13]
Independent C/C++ compiler workflows
Download the MC/DC evidence kit and open instrumentation-demo/. Its four-case fixture exercises (A && B) || C and checks the expected outcomes. The kit separately preserves the original three-case and four-case GCC 13.3 branch experiments under branch-experiment/.
Validation boundary: we executed the ordinary C fixtures and the GCC 13.3 branch experiment. The MC/DC recipes below were checked against release documentation but were not executed for this guide. Their output is something to measure in your environment; no compiler MC/DC result is claimed here.
Clang / LLVM 18.1.8
Use matching 18.1.8 versions of clang, llvm-profdata, and llvm-cov. Instrument the decision and compile the test runner separately. This keeps the teaching report focused on the decision. Clang
clang -std=c11 -O0 -fprofile-instr-generate -fcoverage-mapping \
-fcoverage-mcdc -c decision.c -o decision.o
clang -std=c11 -O0 -c runner.c -o runner.o
clang -fprofile-instr-generate decision.o runner.o -o mcdc-demo
LLVM_PROFILE_FILE=mcdc.profraw ./mcdc-demo
llvm-profdata merge -sparse mcdc.profraw -o mcdc.profdata
llvm-cov show ./mcdc-demo -instr-profile=mcdc.profdata \
-show-mcdc -show-branches=count decision.c
llvm-cov report ./mcdc-demo -instr-profile=mcdc.profdata \
-show-mcdc-summary decision.c
The MC/DC detail view and summary must be requested explicitly. LLVM 18.1.8 limits instrumentation to six leaf conditions per expression and warns about unsupported expressions. These are release-specific limits; inspect the documentation and warnings for any other version you use.llvm-cov Limits
GCC 14 or later, with matching gcov
Use a fresh copy of instrumentation-demo/ for this run. The additional condition flag collects the evidence needed by --conditions; a branch-only report does not answer the same question.GCC flagsgcov
gcc -std=c11 -O0 --coverage -fcondition-coverage \
-c decision.c -o decision.o
gcc -std=c11 -O0 -c runner.c -o runner.o
gcc --coverage decision.o runner.o -o mcdc-demo
./mcdc-demo
gcov --conditions decision.c
The GCC implementation author documents a 32- or 64-condition bitset limit in GCC 14, depending on the target and configuration. Check the diagnostics and release documentation for your exact build; do not transfer a version-specific limit to every later release.[19]
Keep the source revision, build commands, tool versions, test inputs, expected and observed results, report, and any exclusions together. Start independent campaigns with fresh profiles. A reproducible result gives the next reviewer something concrete to inspect.
For your own code, connect each added test to the intended behavior and a meaningful expected result. Confirm that the proposed inputs can reach the decision in the real program. Four convenient Boolean assignments do not establish a complete test plan for the surrounding feature.
10 / Put it to work
Choose your next step
For QA and test engineers
Use the pairs as a review surface. Ask what each test is trying to isolate and whether its expected result comes from the requirement. Keep concrete inputs and assertions beside the case ID. A reusable witness table is more useful than a collection of unnamed fixtures.
For developers working with coding agents
Give the agent a behavior to preserve, an input domain and explicit evidence obligations. Let it propose cases and run supported checks. Keep the authority to approve intent and accept the change clear. A generated test that agrees with generated code still needs an independently justified expectation.
For safety-critical and regulated teams
Start from the applicable standard, software level, target configuration and approved verification plan. Resolve variant, instrumentation, tool qualification and exclusion handling before relying on a report. Use this guide to understand the evidence; use the governing documents to determine your obligations.
For product managers and requirement owners
Use the truth table to expose decisions hidden inside prose: what happens at the boundary, when an override is present, or when inputs disagree? You do not need to choose compiler flags to review whether the modeled behavior matches the product promise.
When MC/DC is not enough
MC/DC is not a complete strategy for a distributed workflow, a timing guarantee, a concurrency argument, a security assessment or a certification decision. It also cannot discover a requirement simply because the code omitted it. Use it where decision logic matters, alongside checks for the other risks. Do not pursue a universal percentage before deciding which claim that percentage should support.
Who this guide is not for
If your immediate need is an end-to-end test plan for an entire product, start with its requirements, hazards and interfaces; this guide covers one part of that plan. If you need certification credit, work from the governing standard and your approved verification process. If the feature’s main risk is timing or concurrency, prioritize evidence that exercises those behaviors as well as any decision logic.
11 / Evidence behind the guide
Primary sources, with their limits
This guide uses NASA and FAA publications for the criterion and assurance context, official tool documentation for measurement, and Proof’s published evidence and implementation review for its capabilities. The original example is available separately as executable source and calculation data.
A Practical Tutorial on Modified Condition/Decision Coverage
Kelly J. Hayhurst, Dan S. Veerhusen, John J. Chilenski and Leanna K. Rierson. NASA/TM-2001-210876, 2001. A foundational explanation of the criterion, examples and analysis difficulties. It predates DO-178C; the current certification context is handled with separate sources.
Read the NASA tutorial ↗“Please note that requirements coverage analysis precedes structural coverage analysis.”
The sequence matters: establish the behavior and its tests, then use structural evidence to investigate what those tests left unexercised.
- NASA: A Practical Tutorial on MC/DC Definitions and analysis fundamentals. Technical guidance, not a claim that this page or Proof is NASA endorsed.
- Chilenski: An Investigation of Three Forms of the MC/DC CriterionFAA research report DOT/FAA/AR-01/18, 2001. Variants, coupling and test-set size. The report explicitly is not certification policy.
- NASA: Towards understanding the DO-178C / ED-12C assurance caseC. Michael Holloway, 2012. Structural coverage and requirements-based verification; not the normative standard itself.
- FAA AC 20-115D Recognition of DO-178C/ED-12C and tool-qualification context. An accepted means of compliance, not itself a regulation.
- NASA NPR 7150.2D §3.7.4, SWE-219 The MC/DC requirement for identified safety-critical software components, including treatment of deviations.
- ISO 26262-6:2018 Official edition and scope. Its public abstract does not expose the full ASIL method table.
- Clang 18.1.8: source-based code coverage Version-pinned instrumentation workflow and supported-expression limits.
- LLVM 18.1.8: llvm-covMC/DC report and summary options.
- GCC 14.3: instrumentation options Condition instrumentation, coverage data and profile behavior.
- GCC 14.2: invoking gcov The
--conditionsreport option and its meaning. - AdaCore: GNATcoverage source analysis Explicit MC/DC variants and coverage obligations. Rolling documentation; verify your installed release.
- Proof: engine and language capability matrix Measurement architecture and documented integration paths. Source 20 records the additional implementation review and its limits.
- Proof: measured MC/DC in Go Recorded implementation evidence and a separate failing requirement example.
- Proof Spec Lab Requirement-model test planning; it does not execute the visitor’s application.
- Testwell CTC++ 10.2: MC/DC measure pointsA concrete example of a reporting denominator that includes decision outcomes.
- FAA: DO-178B/C Differences Tool, revision 010 Structural-coverage analysis basis, additional generated code, coupling and masking; guidance, not a substitute for the standard.
- Rapita: RapiCover product documentation Source instrumentation, supported languages, target collection and qualification support; supplier claims, not a comparative benchmark.
- MathWorks: MC/DC in Simulink Coverage Model-analysis variants and configuration. Test-generation settings and generated-code evidence require separate attention.
- Jørgen Kvalsvik: MC/DC in the GNU Compiler Collection Implementation paper describing GCC 14’s masking approach and target-dependent bitset limit.
- Proof implementation review, 27 October 2026 Language integration paths were checked against Proof’s CLI dispatch and engine implementations for this guide. This is a source review, not a newly executed measurement campaign. Python’s witness limitation and C/C++ compiler-import boundary are stated above; published execution evidence is linked separately.
- Proof: published Omarchy audit checks Code-level MC/DC evidence availability for Bash, JavaScript and QML; menu and lock-screen scope, commit 1e43373287, recorded 4 October 2026.
- Proof: working with coding agents through MCP Project context, the public MCP example, and the distinction between graph access and actions in the customer’s repository.
Sources and product implementation reviewed 27 October 2026. Published measurements retain their own dates, tool versions and stated scope.
More examples / Numbers you can check
Two more examples that sharpen the distinction
1. A coupled predicate can make an obligation impossible
Let A be x > 0 and B be x > 10 in A && B. To make A independently affect the decision, B must be true while A changes. But x > 10 implies x > 0. The required A=false/B=true input cannot occur. Review the redundant predicate and requirement; an invented input cannot fix the problem.
2. An omitted requirement survives complete predicate coverage
Suppose the alarm must also sound when a sensor reports a fault. The predicate (A && B) || C never reads that fault. With A=B=C=false and a fault present, it returns false even though the added requirement expects true. Every existing condition can have a witness and the behavior can still violate the promise. The missing rule needs a requirement, implementation and regression check of its own.
That is the point of keeping intent and observed coverage together. If one decision in your code has become difficult to explain, bring that decision and the behavior it must preserve to Proof.
12 / Common questions
MC/DC questions, answered
What does MC/DC stand for?
Modified Condition/Decision Coverage. MCDC is an alternative spelling. It requires each condition to demonstrate an independent effect on its decision, alongside the other structural obligations in the full definition. A condition is an individual Boolean test; a decision combines conditions into an overall Boolean result.
How do I calculate MC/DC coverage?
State the report’s counting unit and variant first. In this guide’s strict logical model, count conditions with a valid pair and divide by three: the starter suite scores about 33.3%; adding 100 gives 100%. A tool may count different obligations. The worked calculation explains the denominator and aggregation.
Can I have 100% branch and condition coverage without MC/DC?
Yes. The compiled example does: inputs 010, 101 and 110 exercise all six GCC branch outcomes and every condition both ways. B is evaluated in two cases, but the overall result is true in both. Its independent effect has not been demonstrated.
What is the difference between unique-cause and masking MC/DC?
Strict unique-cause keeps other condition values fixed while the target condition and decision change. Masking can permit other changes when the selected criterion establishes that they do not explain the result. Short-circuit handling adds tool-specific rules. The variant comparison names each interpretation and shows why one pair can be accepted differently.
Does a decision with n conditions always need n + 1 tests?
No. The expression, feasible inputs, coupling and selected variant determine the actual obligation. Many simple strict unique-cause examples attain that bound. Here exhaustive enumeration establishes that four cases are minimal for three independent conditions. A different predicate can need a different suite, and some obligations can be infeasible.
Is MC/DC required by DO-178C or ISO 26262?
DO-178C’s structural objectives include MC/DC at software Level A within its broader assurance process. Automotive obligations depend on the applicable ISO 26262 edition, ASIL, method recommendations and verification plan. The standards section explains the scope; a coverage report alone does not establish certification compliance.
Does Proof support MC/DC beyond C and C++?
Proof has measurement and import integrations for Go, Rust, C/C++, Ruby, Bash, QML, JavaScript, TypeScript, Java, C#, PHP, Zig and Solidity. Python has a separate condition/branch observation path that does not currently reconstruct complete independence witnesses. Prerequisites and supported constructs vary by language. Proof’s tooling is not certified. See the language scope and published evidence before choosing a workflow.
How can I generate MC/DC test cases for my own requirement?
Start with Proof’s free Spec Lab micro-tool for one written rule: review its formula and export the resulting cases as Markdown or CSV. For a whole project, you’ll be able to apply that workflow through Proof’s portal and MCP, keeping the requirements, test plans and evidence connected. This page’s explorer teaches one fixed expression. Spec Lab analyzes your requirement model; code-level coverage comes from executing and measuring the implementation.
Draft revision history
Proof and Spec Lab update. Made the free test-case generator prominent, brought Proof’s Go and Omarchy evidence forward, added current language scope and non-certified status, and included Proof in the tool comparison and measurement workflow. Linked the opening outcomes to their sections and explained the whole-project portal/MCP workflow alongside the free micro-tool.
Search and format revision. Added a beginner example, explicit calculation, four-case table, clearer metric and variant comparisons, and broader tool-selection guidance after reviewing ten search intents. Aligned the title and H1, expanded the facts to five, and focused the FAQ on eight questions.
Initial draft. Added the executed GCC branch counterexample, all-256-subsets calculation, interactive witness explorer, captioned walkthrough, downloadable evidence kit, version-pinned recipes and scoped standards guidance.