Levels of Testing, Integration Testing
Levels of Testing, Integration Testing
As the object-oriented paradigm proceeds, details “bubble up” through specification, design, and
coding phases, but at each stage, some of the “flow” drops back to the previous phase(s). This model
captures the reality of the way people actually work (even with the traditional approaches).
As part of the specification and design process, each functional component is normally expanded to
show its inputs, outputs, and mechanism. We do this here with pseudo-code (or PDL, for program
design language) for three modules. This particular PDL is loosely based on Pascal; the point of ny
PDL is to communicate, not to develop something that can be compiled. The main program
description follows the finite state machine description
The ValidatePIN procedure is based on another finite state machine, in which states refer to the
number of PIN entry attempts.
If we follow the pseudocode in these three modules, we can identify the “uses” relationship among the
modules in the functional decomposition.
level behavior, integration threads correspond to integration level behavior, and unit threads
correspond to unit level behavior. Many authors use the term, but few define it, and of those that do,
the offered definitions aren’t very helpful. For now, we take “thread” to be a primitive term, much
like function and data. In the next two chapters, we shall see that threads are most often recognized in
terms of the way systems are described and developed. For example, we might think of a thread as a
path through a finite state machine description of a system, or we might think of a thread as something
that is determined by a data context and a sequence of port level input events, such as those in the
context diagram of the SATM system. We could also think of a thread as a sequence of source
statements, or as a sequence of machine instructions. The point is, threads are a generic concept, and
they exist independently of how a system is described and developed.
Structural Insights
Everyone agrees that there must be some distinction, and that integration testing is at a more detailed
level than system testing. There is also general agreement that integration testing can safely assume
that the units have been separately tested, and that, taken by themselves, the units function correctly.
One common view, therefore, is that integration testing is concerned with the interfaces among the
units. One possibility is to fall back on the symmetries in the waterfall life cycle model, and say that
integration testing is concerned with preliminary design information, while system testing is at the
level of the requirements specification. This is a popular academic view, but it begs an important
question: how do we discriminate between specification and preliminary design? The pat academic
answer to this is the what vs. how dichotomy: the requirements specification defines what, and the
preliminary design describes how. While this sounds good at first, it doesn’t stand up well in practice.
Some scholars argue that just the choice of a requirements specification technique is a design choice
The life cycle approach is echoed by designers who often take a “Don’t Tread On Me” view of a
requirements specification: a requirements specification should neither predispose nor preclude a
design option. With this view, when information in a specification is so detailed that it “steps on the
designer’s toes”, the specification is too detailed. This sounds good, but it still doesn’t yield an
operational way to separate integration and system testing.
The models used in the development process provide some clues. If we follow the definition of the
SATM system, we could first postulate that system testing should make sure that all fifteen display
screens have been generated. (An output domain based, functional view of system testing.) The
entity/relationship model also helps: the one-to-one and one-to-many relationships help us understand
how much testing must be done. The control model (in this case, a hierarchy of finite state machines)
is the most helpful. We can postulate system test cases in terms of paths through the finite state
machine(s); doing this yields a system level analog of structural testing. The functional models
(dataflow diagrams and structure charts) move in the direction of levels because both express a
functional decomposition. Even with this, we cannot look at a structure chart and identify where
system testing ends and integration testing starts. The best we can do with structural information is
identify the extremes. For instance, the following threads are all clearly at the system level:
3. Insertion of a valid card, a correct PIN entry attempt, followed by a balance inquiry.
6. Insertion of a valid card, a correct PIN entry attempt, followed by an attempt to withdraw more
cash than the account balance.
Behavioral Insights
Here is a pragmatic, explicit distinction that has worked well in industrial applications. Think about a
system in terms of its port boundary, which is the location of system level inputs and outputs.
Every system has a port boundary; the port boundary of the SATM system includes digit keypad, the
function buttons, the screen, the deposit and withdrawal doors, the card and receipts lots, and so on.
Each of these devices can be thought of as a “port”, and events occur at system ports. The port input
and output events are visible to the customer, and the customer very often understands system
behavior in terms of sequences of port events. Given this, we mandate that system port events are the
“primitives” of a system test case, that is, a system test case (or equivalently, a system thread) is
expressed as an interleaved sequence of port input and port output events. This fits our understanding
of a test case, in which we specify pre-conditions, inputs, outputs, and post-conditions. With this
mandate we can always recognize a level violation: if a test case (thread) ever requires an input (or an
output) that is not visible at the port boundary, the test case cannot be a system level
test case (thread). Notice that this is clear, recognizable, and enforceable. We will refine this
inChapter 14 when we discuss threads of system behavior.
Integration Testing
Craftspersons are recognized by two essential characteristics: they have a deep knowledge of the tools
of their trade, and they have a similar knowledge of the medium in which they work, so that they
understand their tools in terms of how they “work” with the medium. In Parts II and III, we focused
on the tools (techniques) available to the testing craftsperson. Our goal there was to understand testing
techniques in terms of their advantages and limitations with respect to particular types of faults. Here
we shift our emphasis to the medium, with the goal that a better understanding of the medium will
improve the testing craftsperson’s judgment.
Some of the hierarchy is obscured to reduce the confusion in the drawing. One thing should be quite
obvious: drawings of call graphs do not scale up well. Both the drawings and the adjacency matrix
provide insights to the tester. Nodes with high degree will be important to integration testing, and
paths from the main program (node 1) to the sink nodes can be used to identify contents of builds for
an incremental development.
We can dispense with the big bang approach most easily: in this view of integration, all the units are
compiled together and tested at once. The drawback to this is that when (not if!) a failure is observed,
there are few clues to help isolate the location(s) of the fault.
Top-Down Integration
Top-down integration begins with the main program (the root of the tree). Any lower level unit that is
called by the main program appears as a “stub”, where stubs are pieces of throw-away code that
emulate a called unit. If we performed top-down integration testing for the SATM system, the first
step would be to develop stubs for all the units called by the main program: Watch Card Slot, Control
Card Roller, Screen Driver, Validate Card, Validate PIN, Manage Transaction
Once all the stubs for SATM main have been provided, we test the main program as if it were a stand-
alone unit. We could apply any of the appropriate functional and structural techniques, and look for
faults. When we are convinced that the main program logic is correct, we gradually replace stubs with
the actual code. Even this can be problematic. Would we replace all the stubs at once? If we did, we
would have a “small bang” for units with a high outdegree. If we replace one stub at a time, we retest
the main program once for each replaced stub. This means that, for the SATM main program example
here, we would repeat its integration test eight times (once for each replaced stub, and once with all
the stubs).
Bottom-up Integration
Bottom-up integration is a “mirror image” to the top-down order, with the difference that stubs are
replaced by driver modules that emulate units at the next level up in the tree. In bottom-up integration,
we start with the leaves of the decomposition tree (units like ControlDoor and DispenseCash), and test
them with specially coded drivers. There is probably less throw-away code in drivers than there is in
stubs. Recall we had one stub for each child node in the decomposition tree. Most systems have a
fairly high fan-out near at the leaves, so in the bottom-up integration order, we won’t have as many
drivers. This is partially offset by the fact that the driver modules will be more complicated.
Sandwich Integration
Sandwich integration is a combination of top-down and bottom-up integration. If we think about it in
terms of the decomposition tree, we are really just doing big bang integration on a sub-tree
We can always compute the number of neighborhoods for a given call graph. There will be one
neighborhood for each interior node, plus one extra in case there are leaf nodes connected directly to
the root node. (An interior node has a non-zero indegree and a non-zero outdegree.)
sent to GetPIN as a string variable. (Notice that KeySensor performs the physical to logical
transition.) GetPIN determines whether a digit key or the cancel key was pressed, and responds
accordingly. (Notice that button presses are ignored.) The ASF terminates with either screen 2 or 4
being displayed. Rather than require system keystrokes and visible screen displays, we could use a
driver to provide these, and test the digit entry ASF via integration testing. We can see this using our
continuing example.