Location: [CPSC 333] [Listing by Topic] [Listing by Date] [Previous Topic] [Next Topic] Condition Testing
This material was covered during lectures on March 31, 1997.
Condition Testing is another structural testing method that is useful during unit testing, using source code or detailed pseudocode as a reference for test design.
Its goal is the thorough testing of every condition or test that occurs in the source code. While path testing does provide some limited coverage of conditions, by assuring that every outcome (true and false) of every condition is covered by at least one test case, condition testing provides much better coverage for conditions that are ``complex.''
The method is easy to describe: It includes a ``checklist'' of cases to be covered for each kind of condition that one might find in source code, in the test for an if-then or if-then-else statement, or as the test for a while-do or repeat-until loop.
A boolean variable, or (application of a) boolean function, is one kind of simple condition.
Relational expressions are comparisons of two values of the same type. If v1 and v2 are values of the same type, and it is possible to test ``equality'' of values of this type, then two (kinds of) relational expressions involving this type are
If the type is ``ordered'' then additional relational expressions are also possible:
In all cases, a truth value (either true or false) is obtained whenever you assign values of the given type to each of the variables v1 and v2. Of course, ``v1'' and ``v2'' might also be (calls to) functions returning values of the given type, in the expressions given above.
Compound conditions are conditions that are obtained by combining together simple conditions, using ``logical connectives,'' such as ``and'' ( && ), ``or'' ( || ), or ``not'' ( ! ); additional connectives, such as ``implies'', ``nand,'' or ``exclusive or'' might also be used.
As mentioned above, a ``checklist'' or simple rule describing required test cases can be given for each of the kinds of conditions listed above.
In this case, two test cases should be covered:
Suppose the relational expression to be considered is
e1 op e2
where e1 and e2 are expressions of the same type, and op is one of the comparisons (==, !=, <, and so on) listed above.
If this is an ``ordered'' type, so that it makes sense to ask whether e1 is ``less than'' e2, then three cases should be covered by tests:
On the other hand, if the type is not ``ordered,'' then only two cases need to be covered (or, can be distinguished):
Note that either two or three cases need to be covered for each one of the simple conditions listed above.
As described above, a compound condition connects some number of simple conditions together, using logical connectives. Suppose that a complex condition connects k simple conditions with three cases, and h simple conditions with two cases, together. Then, it is possible - in principle - to define a total of
3k 2h
different combinations of these cases.
Some might be impossible to achieve. For example, if x, y, and z are variables of the same ordered type, and three of the relational expressions included in the compound condition are
then one of the (twenty-seven) combinations of cases that you'd get by considering these three relational expressions would be equivalent to the following condition on x, y, and z:
(x < y) && (y < z) && (x > z)
It should be clear that there is no way to assign values to x, y and z such that the above condition is satisfied.
The guideline for ``compound combinations'' is now easy to state: For each of the above combinations of conditions that is possible, include a test such that this combination of conditions holds at some point (during execution of the program) when the condition is checked.
The guidelines given above only mention two-way conditions and, of course, the kinds of conditions included in case or switch statements are a bit different. However, path testing appears to provide adequate testing for these statements, so we won't concern ourselves with these statements any further.
Once again, consider the sorting program that is being used as an ongoing example for unit testing. Recall that three tests were identified for this program when path testing was applied.
The source code (more accurately, pseudocode) for this program includes two conditions, that appear as the tests for the two while loops that it contains.
The test for the outer loop is (or can be written as)
i <= n
This is clearly an example of a relational expression. It involves integers, and integer is an ordered type - so there are three cases that should be covered by tests:
The second condition, which is the test for the inner loop, is a compound condition involving a pair of relational expressions:
(j >= 1) && (A[j] > A[j+1])
Both of the relational expressions involve an ordered type, so that there are 32 = 9 combinations that should be considered:
Some - but not all - of the above cases are covered by the tests that were specified for this program using path testing. In particular,
The second case (or requirement) for the first condition is covered by both Test #2 and Test #3, at the beginning of the first execution of the outer while loop.
The third case (or requirement) for the first condition is covered by Test #1 and, indeed, execution of every other test, just before the outer while loop terminates.
The first three cases that apply to the second condition require that j be strictly less than one. In these cases, A[j] is undefined, and this would probably cause problems on an attempt to compare ``A[j]'' to ``A[j+1]'' when this condition is checked.
It's to be hoped that if j is less than 1, then this condition will be assigned the truth value false immediately, without any attempt to access the undefined value A[j]. Whether this is true might possibly depend on the programming language used to write source code, the way the statement is written, or possibly even the compiler that's used. Thus, it's possible that the program should be written in so that it's guaranteed that A[j] is only compared to A[j+1] after it's been confirmed that j is greater than or equal to 1 (and so that no test results are unpredictable, or undefined).
For now, we'll treat these three cases as a single one - and we'll note that it's covered by Test #3, during the second execution of the inner while loop.
The fourth cases that applies to the second condition is covered by Test #2, at the beginning of the first execution of the inner while loop.
The sixth case that applies to the second condition is covered by Test #3, at the beginning of the first execution of the inner while loop.
The remaining cases - the first case applying to the first condition, and the fifth, seventh, eighth, and ninth cases applying to the second conditions - aren't covered by any of the test that have been developed so far.
We'll begin by covering the fifth case that is required for the second condition (because this is the only remaining case that can be covered using a test in which n=2):
Test #4
Inputs: n = 2; A[1] = 1, A[2] = 1
Expected Outputs: A[1] = 1, A[2] = 1
Now, the above case is covered by this test, at the beginning of the first execution of the inner while loop.
The first case for the first condition can be covered by any test for which n is greater than or equal to 3; it's always covered at the beginning of the first execution of the outer while loop. Thus, this condition is covered by all three of the remaining tests given below.
The fifth test will also cover the seventh case that applies to the second condition, at the beginning of the first execution of the inner while loop.
Test #5
Inputs: n=3; A[1] = 1, A[2] = 2, A[3] = 3
Expected Outputs: A[1] = 1, A[2] = 2, A[3] = 3
The sixth test will cover the eighth case that applies to the second condition, at the beginning of the first execution of the inner while loop.
Test #6
Inputs: n=3; A[1] = 1, A[2] = 2, A[3] = 2
Expected Outputs: A[1] = 1, A[2] = 2, A[3] = 2
The seventh test will cover the remaining ninth case that applies to the second condition - also at the beginning of the first execution of the inner while loop.
Test #7
Inputs: n = 4; A[1] = 1, A[2] = 3, A[3] = 2
Expected Outputs: A[1] = 1, A[2] = 2, A[3] = 3
A lab exercise based on this material is also available.
Location: [CPSC 333] [Listing by Topic] [Listing by Date] [Previous Topic] [Next Topic] Condition Testing