Object Oriented
Testing Techniques
Class Testing
Class testing is the initial testing phase in the class life cycle.
A class may be reused as a component in a variety of applications over
time. It must be tested in isolation to check its validity as a unit.
The test suite for a class can be defined by first testing each of
the routines (methods) in it. The suite is then extended to include
test cases that provide interaction testing in which methods that call
other methods in the class and those that access the same class data
are tested together. Use the preconditions and postconditions in the
routines to guide you in the development of test cases for the individual
methods.
Class testing includes two forms of testing: specification-based
and program-based testing. Specification-based testing treats
the class as a black box. It is intended to determine whether the class
is performing according to its specification. For example, specification
testing for a Stack class should ensure that the Last-In-First-Out (LIFO)
policy is being enforced. Program-based testing considers the implementation
of the class. The testing is intended to check the correctness in coding.
For example, in the Stack class, the test suite must have test cases
for all the methods to check for appropriate behaviour.
Specification-Based Testing
Specification-based testing includes both class specification
and method specification.
Class Specification
Class specification includes the specifications of all the methods
in the class and a broader statement of the concept that the class is
intended to represent. For example, a Stack class would include specifications
for the method push and pop, but these specifications do not convey
how these operations behave when working together in a class. The specification
for push would describe how a value is added to the "top" of a stack
but would not say anything about deletions. The specification for pop
would not state how the deleted item had been inserted on a stack. Only
in the class-level specification is the LIFO idea represented. Method
specifications lack support for the full semantics of a class, as the
pre and post conditions of a method captures only the behavioural requirements
for that method.
The specification of a class is the exported features of class. Subclasses
will see an expanded interface, which includes both the public and protected
features. All the features of a class (exported and secret) must be
tested.
Method Specification
The medthod specification is defined by its precondition and postcondition
as well as by its signature. Based on the preconditions, test cases
are selected to produce outputs.
Program-Based Testing
The program-based testing of a class will test all its methods and
will test the class as a unit. The test plan will consider the coding
in individual methods in isolation and then consider the interaction
between methods. Each method can be tested over its domain of inputs
but the interactions between methods need to be tested before testing
is adequate. Method testing involves checking that the method performs
as specified. The class must be exercised over a representative set
of its states.
Methods in Isolation
The first level of program-based testing considers each method in isolation.
Messages sent to other objects are ignored and replaced by stubs that
return appropriate values. Test data is selected to ensure adequate
coverage of the code in the class.
Methods in Integration
This level of testing considers the interaction of one method calling
another within the same class (intraclass) and messages from one class
to another (interclass). Testing methods in isolation checks the quality
of their implementation but may not highlight any subtle problem of
sequencing. Test cases that invoke these interactions are included in
the test suite to enable the developer to check that the interaction
is handled correctly. Exercising all major states of the class is an
important adequacy criterion for this type of testing.
Integration Testing
Testing a new class also tests the integration of those classes that
participate in its definition. is-a, is-part-of, and refers-to relations
establish dependencies between classes which require testing. They also
imply an ordering to the testing of several classes. Test basic classes
first, then test classes that use the basic classes, and so on, using
a layered approach.
The current testing can be limited to domain testing when a
message is sent from the class being tested to an instance of another
class which has already been tested. The message is checked to make
sure that the actual parameters come from the appropriate domains. Integration
testing in object-oriented systems requires that classes be brought
together that were not specifically written for use in the current application.
This implies that the coordination between these classes needs to be
tested.
Hierarchical Incremental
Testing
The structure of the application class definitions can be used to support
an approach that reuses test cases from one class to another. First,
test the base classes, which have no ancestors. Reuse existing test
cases from the test suites of the parent class(es) in the test suite
of a subclass. This technique incrementally develops the test suite
for a class based on its hierarchical relationship with its ancestors
and is thus termed hierarchical incremental testing. Complications exist
when testing deferred/abstract methods where no implementation exists,
and deferred/abstract classes, where the class cannot be instantiated.
There can also complications testing secret or private features.
Hierarchical incremental testing allows the testing of a subclass to
begin with the test cases of the ancestor classes. Develop the test
suite for a subclass by noting those parts of the class that are new.
Eliminate test cases from the ancestor's test suite, which need not
be retested. By treating specification, program-based and integration
testing separately, one has three distinct sets of test cases from which
to select those that require retesting. Other test cases may be added
when new features are defined, either as additions to existing classes
or as redefinitions of existing features.
Inheritance Regression
Testing
Regression testing is a fault-based testing approach. It involves
repeating test cases that uncovered faults in a subclass or previous
software version in order to detect errors in the current software version.
When new software, built from old versions of source, contains previously
fixed errors, the code is said to have "regressed", hence
the term regression testing .
Basic regression testing checks for faults that were fixed previously,
however other errors may still be present. Developers may have access
to bug report logs from a previous iteration of the project which highlight
the kinds of errors and areas of code (e.g. complex algorithm, complex
interface, boundary conditions, rarely executed piece of code such as
the year 2000 problem) that warrant more attention.
A robust regression test incorporates knowledge about common areas
where software faults occur. A good robust set of regression tests is
designed to uncover related faults in the software in addition to testing
for the old errors, as sof tware faults may be structurally or functionally
related. Regression tests complement behavioural (functional) and structural
(code based) testing.
Often, developers do not employ robust regression techniques to create
and track tests. A case in point appears to be the new automated Victorian
Met Tram ticketing system, reported in "the Four Corners Program, ABC
TV, 20/4/98 titled "On the Right Tracks". Mr. Batchelor, shadow transport
minister reported that the new Met Tram ticketing system had some teething
problems and that it appeared that a suite of tests to track revenues
across various parts of the system were having about a 10% success rate.
It seemed that one of the business requirements to track patronage and
revenue across various parts of the system was not working. This aspect
of the system had to be fixed, and regression testing was needed to
ensure that the system tests failed if any aspect of the previous faults
still existed. The tests had to also provide good code-based coverage
including code that interacted with previously faulty code. Finally,
it had to also verify that the output data from the tests were correct.