Table of Contents
This chapter presents some design rework and implementation rework for testability purposes. While testability is very important, new programmers can be slowed to a crawl by the mechanics of building test drivers and test cases. We prefer to emphasize the basic design considerations first, and address testability as a feature to be added to a working class.
Additionally, we'll address some issues in construction of class instances and an idealized structure for the main procedure.
We have been studiously ignoring an elephant standing in the saloon. This is the problem of testing an application that includes a random number generator (RNG). There are two questions raised:
How can we develop formalized unit tests when we can't predict the random outcomes? This is a serious testability issue in randomized simulations. This question also arises when considering interactive applications, particularly for performance tests of web applications where requests are received at random intervals.
Are the numbers really random? This is a more subtle issue, and is only relevant for more serious applications. Cryptographic, statistical or actuarial applications may care about the randomness of random numbers. This is a large subject, and well beyond the scope of this book. We'll just assume that our random number generator is good enough.
We'll address this first issue by developing some scaffolding
that permits controlled testing. There are three approaches to
replacing the random behavior with something more controlled. One
approach is to subclass Wheel to create a more
testable version without the random number generator. An alternative
to changing wheel is to define a subclass of
java.util.Random (or Python's
random) that isn't actually random. A third
approach is to record the sequence of random numbers actually
generated from a particular seed value and use this to define the
exected test results. For more information on this third alternative,
see On Random Numbers.
Good testability is achieved when there are no changes to the
target software. For this reason, we don't want to have two versions
of Wheel, one for testing and one for normal
operations.
Instead of having two versions of Wheel, it's slightly better to
have a random number generator that creates a known sequence with
which we can test. To get this known sequence, we have a choice
between creating a non-random subclass of
java.util.Random or controlling the seed for
the random number generator used by Wheel. Both
will produce a known sequence of non-random values.
One consequence of either of these decisions is that we have to
make the random number generator in Wheel more
visible. Our favorite approach to making something more visible is to
assign an object through an official interface method or possible as
part of the constructor. In the case of Wheel,
we'd jave our overall simulation or test assign an appropriate number
generating object to the instance of Wheel
rather than have Wheel privately create a
generator.
When we are doing testing, we can associate a
Wheel with an instance of
NonRandom, or an instance of
Random initialized with a known seed. For
actual use, we can then associate a Wheel with
an instance of java.util.Random; this will
either use the RNG's default constructor to assure
unpredictability.
We'll need an additional constructor for
Wheel that allows us to provide an
appropriately initialized generator. Our current constructor secretly
creates an instance of a RNG, using the
RNG's default constructor. We'll need an additional
method that provides an RNG, already initialized with
an appropriate value.
For Python programmers, this is handled as a second parameter with a default value.
Note that, consistent with our principle of deferred
binding, we don't have to choose exactly which implementation
we will use. By allowing Wheel to take either
an instance of Random initialized with a given
seed or an instance of a new subclass,
NonRandom, we have given ourselves the
flexibility to choose either implementation. We'll provide
specifications for NonRandom, but using a fixed
seed for Random is seen by some as
simpler.