The Basis of Test-Driven Development: Writing Effective Test Cases
The premise in test-driven development is focusing the initial efforts on writing tests for a particular feature of function before writing code. This tends to result in simpler code that is easier to maintain. The developer has to write a “failing test” and then create enough code to allow the test to pass. Later, the code can be refactored and optimized.
The problem, as I see it, is we use the term test-driven but still focus on coding aspects, not writing good tests.
The first step in a test-driven approach has to be writing effective tests. Most tests at the code level tend to focus on the general concepts of code coverage. But several code-coverage measures ensure that decisions made in the code also work correctly, including:
- Condition coverage
- Decision condition coverage
- Modified decision/condition coverage (MC/DC)
- Multiple condition coverage
Each of these measures focuses on the nature of the decision construct, ensuring each atomic condition or compound condition is tested as both true and false. With compound conditions and conditions in a decision, the developer also has to account for the use of “short-circuit” operators in the language that will affect the testing.
Using a simple example, IF (X && N) requires two test cases to ensure all conditions and the decision are tested as both true and false and generate the appropriate action:
Variable X | Variable N | Result | |
Test 1 | True | True | True |
Test 2 | False | False | False |
If our decision is more complex than a simple Boolean condition, how will this affect the test cases?
Let us examine a decision that focuses on the domain characteristics of a variable. In this case, our decision checks a single variable for a range of values: IF (A ≥ 1 ││ A ≤ 10). There are two required tests:
Variable A | Result | |
Test 1 | 2 | True |
Test 2 | 12 | False |
Here the condition has been tested as both true and false and the decision has been tested as true and false. The code works and the structure is sound, but we have not tested the variable very well. If we look at the domain of variable A and use domain analysis techniques such as equivalence partitioning and boundary analysis, we need more test cases.
Variable A | ||
Domain 1 | Domain 2 | Domain 3 |
< 1 | 1 through 10 | > 10 |
With the above set of domains, we will require at least four test cases to ensure the condition and decision have been tested as true and false and that the domains have been properly verified:
Variable A | Result | |
Test 1 | 0 | False |
Test 2 | 1 | True |
Test 3 | 10 | True |
Test 4 | 11 | False |
The previous example represented a very simple condition in a decision. But what about more complex or multiple conditions?
IF ((A == B) && (C ≥ 10 ││ C ≥ 40) && (D > 9))
Imagine the possibilities if all we were to focus on are the Boolean characteristics of the compound condition; surely, we would miss something. The lesson here is that only testing all conditions and compound conditions as both true and false does not ensure good testing or that the code will function correctly.