Clear-Box Testing
The control/data flow graph extracted from a program's source code is
an important tool in developing clear-box tests for the program. To
adequately test the program, we must exercise both its control and data
operations.
In order to execute and evaluate these tests, we must be able to
control variables in the program and observe the results of
computations, much as in manufacturing testing. In general, we may need
to modify the program to make it more testable. By adding new inputs
and outputs, we can usually substantially reduce the effort required to
find and execute the test.
Example 5-11 below
illustrates the importance of observability and controllability in
software testing.
No matter what we are testing, we must accomplish the following
three things in a test:
- Provide the program with inputs that exercise the test we are
interested in.
- Execute the program to perform the test.
- Examine the outputs to determine whether the test was successful.
Example 5-11 Controlling
and Observing Programs
Let's first consider controllability by examining the following
FIR filter with a limiter:
firout = 0.0; /* initialize filter output */
/* compute buff*c in bottom part of circular buffer */
for (j = curr, k = 0; j < N; j++, k++)
firout += buff[j] * c[k];
/* compute buff*c in top part of circular buffer */
for (j = 0; j < curr; j++, k++)
firout += buff[j] * c[k];
/* limit output value */
if (firout 100.0) firout = 100.0;
if (firout < -100.0) firout = -100.0;
The above code computes the output of an FIR filter from a circular
buffer of values and then limits the maximum filter output (much as an overloaded speaker will hit a
range limit).
If we want to test whether the limiting code works, we must be able
to generate two out-of-range values for firout: positive and negative.
To do that, we must fill the FIR filter's circular buffer with N values
in the proper range. Although there are many sets of values that will
work, it will still take time for us to properly set up the filter
output for each test.
This code also illustrates an observability problem. If we want to
test the FIR filter itself, we look at the value of firout before the
limiting code executes. We could use a debugger such as dbx to set
breakpoints in the code, but this is an awkward way to perform a large
number of tests. If we want to test the FIR code independent of the
limiting code, we would have to add a mechanism for observing firout
independently.
Being able to perform this process for a large number of tests
entails some amount of drudgery, but that drudgery can be alleviated
with good program design that simplifies controllability and
observability.
The next task is to determine the set of tests to be performed. We
need to perform many different types of tests to be confident that we
have identified a large fraction of the existing bugs. Even if we
thoroughly test the program using one criterion, that criterion ignores
other aspects of the program. Over the next few pages we will describe
several very different criteria for program testing.