Chapter 11. Review of Testability

Table of Contents

Overview
Questions and Answers
Design
Wheel Rework
Java NonRandom Class
Python NonRandom Class
Deliverables

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.

Overview

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:

  1. 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.

  2. 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.