Next / Previous / Contents / Shipman's homepage

8.5. Insuring case coverage

Consider the case of a prime that is a sequence of three alternations:

    #-- 1
    # [ if C1 -> T1
    #   else -> F1 ]
    ...

    #-- 2
    # [ if C2 -> T2
    #   else -> F2 ]
    ...

    #-- 3
    # [ if C3 -> T3
    #   else -> F3 ]
    ...

As discussed in Section 8.2, “Trace table for alternation”, in general there are eight different paths through this prime. However, in practice, quite often some of the cases contain terminal special forms like function returns or exceptions, which reduces the number of cases overall. In the above example, for instance, if F1 stops execution and F2 also stops execution, there are only four paths through the code: F1; T1F2; T1T2F3; and T1T2T3.

In the general case, how can we be sure that we have a trace table for each possible path through the code? In this section, we describe a way to insure this.

We will start with a rather generic intended function for an entire script that reads some input file and produces a PDF-format report on that file under control of some command line options.

We assume that this code is connected to a written specification that details all the critical definitions: the command line options and what they do, the format of the input file, and the appearance of the output report. In accordance with the principles discussed in Section 3.2, “Design factoring and separation of concerns”, these details are not the concern of the main program logic. All we care about at this level is the overall sequence of the three major steps: processing the command line options, reading the input file, and writing the output file.

#!/usr/bin/env python
# [ if (the command line options are valid) and
#   (the input file specified by those options is readable and
#   valid) ->
#     sys.stdout  +:=  a PDF report on that file using those options
#   else ->
#     sys.stdout  +:=  (anything)
#     sys.stderr  +:=  an error message ]

The refinement of this overall intended function follows. We assume the use of two classes defined elsewhere: an instance of class Args represents the command line options, and an instance of class Report represents the input file and the report to be produced from it.

#-- 1
# [ if the command line options are valid ->
#     args  :=  an Args instance representing those options
#   else ->
#     sys.stderr  +:=  an error message
#     stop execution ]
args = Args()

#-- 2
# [ if the input file specified by (args) is readable and valid ->
#     report  :=  a Report instance representing that file
#                 processed using (args)
#   else ->
#     sys.stderr  +:=  an error message
#     stop execution ]
report = Report(args)

#-- 3
# [ sys.stdout  +:=  (report) formatted as a PDF ]
report.pdf(sys.stdout)

Two conditions control the paths through this code. We'll call them C0 and C1:

C0: the command line options are valid
C1: the input file is readable and valid

Here is a standard truth table for the possible combinations of C0 and C1. In this table, “X” means “don't care”:

C0C1Case
FX(A)
TF(B)
TT(C)

In case (A), the command line options aren't valid, so we don't care about the state of the input file. We want only one state change in this case: an error message appended to sys.stderr.

State item[1]
sys.stderr an error message

In case (B), the command line options are valid, but there's some problem with the input file.

State item[1][2]
args an Args instance representing the command line
sys.stderr   error message

Case (C) is the successful case where we get all the way to prime [3].

State item[1][2][3]
args an Args instance representing the command line
report   Report instance representing input file
sys.stdout    PDF representing Report instance etc.

Of the lines in the trace table above, only the last one is external to the prime, and it matches the line in the overall intended function “sys.stdout +:= a PDF report on that file using those options”.

To be sure that you have covered all routes through the code and have a trace table for each route, build a truth table as above, and make sure the truth table covers all the cases. You could even write a program to verify that your truth table is valid; this project we will leave to the interested reader. The test is that every possible combination of the states of the conditions matches exactly one line of the truth table.