Cover a Conditional

How do you write tests that cover different types of control flows? Let’s look at the three types of structured programming constructs we use daily:

Let’s go over some rules of thumb for covering these control flows with tests. We are partway through covering max, which has a conditional in the form of an if-else statement. We’ve written a test that covers the first half.

Now let’s add a test to cover the second half. The conditional is if x < y, so let’s choose values that will make this if-statement false. We’ll apply the characterization test technique, giving the assertion a known mismatch. Running the test gives us the actual value. We plug that value into the assertion, giving us this test:

CodeCoverage/CodeCoverageTests/CoveredClassTests.swift
 func​ ​test_max_with3And2_shouldReturn3​() {
 let​ result = ​CoveredClass​.​max​(3, 2)
 
 XCTAssertEqual​(result, 3)
 }

This should give us 100% coverage. Sadly, we’re robbed of that satisfaction: The closing brace is marked as uncovered, as you can see here:

images/code-coverage/closing-brace.png

What’s going on? This is a side effect of multiple early return statements: program execution never reaches the end of the function. There are ways to avoid this, of course. But why? Code the way you want without being a slave to coverage metrics.

images/aside-icons/tip.png

Don’t fret trying to cover code you know to be unreachable.

It took two tests to cover this function: one for each side of the if-else. Even when there’s no else clause, an if statement calls for at least two tests. Code coverage won’t show it, but we need to test for what happens when a section of code behind a conditional isn’t run.

For max, we’re satisfied with our two tests. But with the greater-than and less-than comparison operators, you’ll often want a third test with an equal value. Equality is an interesting boundary condition for Comparable types.

Conditional expressions often use logical AND (&&) and logical OR (||) operators. For a && b, you’ll want to test:

ab

false

true

true

false

true

true

For a || b, you’ll want to test:

ab

false

false

true

false

false

true

Where’s the fourth row of these truth tables? Adding tests for them doesn’t add much value, so you can usually omit them. The three sets of inputs for each AND or OR as shown in the tables is enough to specify their behavior.

Stay aware that if statements aren’t the only conditionals in town. Swift has the following:

images/aside-icons/tip.png

Try to cover every side of a conditional. Remember that an if without an else still requires two tests, checking the behavior when the statements are not executed.