Achieving 100% MC/DC coverage
The MC/DC metric
The Modified condition/decision coverage (MC/DC) metric is one of the “big guns” among the coverage metrics, giving quite good guarantee the covered code is sufficiently tested. Its definition is quite succint (copied from Wikipedia):
- Each entry and exit point is invoked.
- Each decision takes every possible outcome.
- Each condition in a decision takes every possible outcome.
- Each condition in a decision is shown to independently affect the outcome of the decision.
Condition vs. decision
Consider the following example (and ignore we can simplify the if
):
public static boolean doSomething(boolean a, boolean b, boolean c) {
if ((a || b) && c) {
return true;
} else {
return false;
}
}
Here our decision is (a || b) && c
. The conditions are the booleans that decision depends on: a
, b
and c
1. Our job is to find how to cover that decision with test cases to guarantee it’s 100% bug-resistant (according to the definition above).
The road to the 100%
The definition gives us a very good understanding what means to have 100% coverage, but not how to get there. Let’s focus on the 4 points in the definition. This will allow us to analyze getting to 100% with a minimal set of test cases.
- Covering entry/exit points - That’s easy to achieve. Just write a test case which reaches each
return
. - Covering every decision outcome - Slightly harder, but still relatively easy - just make sure we get the two different results for each decision.
- Covering each condition - Make sure we’ve actually made all the conditions
true
orfalse
. Easy again!2 - “Proving” the result depends on each condition - That’s slightly harder, even when we’re not proving it in the mathematical sense. This is what we’ll focus on.
Minimizing the test cases
OK, we figured out we need to prove the result of the decision is affected by the different conditions, but how to do it with a minimum amount of test cases? I mean less than 2^n
3?
We need at least n+1
test cases to cover point 4 of the MC/DC definition for a boolean decision4. How are we going to find these test cases? Note there are many solutions to this problem.
How to figure out the inputs?
I’ll sketch a procedure for informally finding out a set of test cases. This is not an algorithm in the strict sense, it just helps you sort the things out using your intuition.
In other words, this is not a 100% mathematically provable algorithm :)
Step 1 - construct a truth table
Let’s construct a truth table for the doSomething
method:
Case | a | b | c | Decision |
---|---|---|---|---|
1 | false | false | false | false |
2 | false | false | true | false |
3 | false | true | false | false |
4 | false | true | true | true |
5 | true | false | false | false |
6 | true | false | true | true |
7 | true | true | true | false |
8 | true | true | true | true |
That are all the 2^n
test cases we’d write to test all possible inputs. We’re trying to find a subset of them.
Step 2 - select a test case
Let’s start with a random line, e.g. case 3:
Case | a | b | c | Decision |
---|---|---|---|---|
3 | false | true | false | false |
Step 3 - find your next test case
Now we want to change a condition and verify it affects the decision. For example, changing c
to true
would change the result to true
(case 4). Add it to the table:
Case | a | b | c | Decision |
---|---|---|---|---|
3 | false | true | false | false |
4 | false | true | true | true |
Note: You should not necessarily always base the new test case on the last one you’ve added.
Step 4 - repeat
Now repeat step 3 until you get n+1
test cases.
For example, we can now change b
to false
, changing the decision to false
(case 2):
Case | a | b | c | Decision |
---|---|---|---|---|
3 | false | true | false | false |
4 | false | true | true | true |
2 | false | false | true | false |
Now we have to verify this for a
too. Changing it to true
would change the decision to true
:
Case | a | b | c | Decision |
---|---|---|---|---|
3 | false | true | false | false |
4 | false | true | true | true |
2 | false | false | true | false |
6 | true | false | true | true |
Profit!
Now we have a good set of test cases, ready to be turned into code:
Case | a | b | c | Decision |
---|---|---|---|---|
3 | false | true | false | false |
4 | false | true | true | true |
2 | false | false | true | false |
6 | true | false | true | true |
An indication we’re ready is that for each condition (a
,b
,c
) we have two different decision results for the two values of that condition.
Coincidentally, we have n+1
test cases, which means this is also a minimal set of tests! :)
Footnotes
Of course, these 3 guys needn’t be booleans. They can be any expressions! But it’s easier to treat them as “dumb” booleans, to avoid messing up with too much syntax. ↩
Unless you have conditions which can never be true (e.g.
1 == 2
) or false (e.g.1 == 1
). Then it’s impossible. Sorry! ↩Covering all the
2^n
inputs will guarantee we’ve covered each theoretically possible outcomes of the decision. Not always the best idea, though. ↩Sketch for an induction proof: For 1 condition we obviously need 2 test cases to verify it affects the decision. Now assume we have
n
conditions covered byn+1
cases, “proving” thesen
conditions affect the decision. Adding a new condition will require us to add at least one more test case - we can add one of the values (true/false) for the condition to the existing test cases (without affecting their decision verification), but in order to prove the new condition can change the decision we should add a new test case, ending up withn+2
cases. If we could go on and test the new condition without adding a new test case (just by modifying the existing ones), we’d surely “lose” a test case for another condition. ↩