breaks. In addition, he would probably test for
processing of both financial and non-financial
records. But it is the combination of these two
factors, along with data that would cause the defect
to result in incorrect output, which is relevant.
1.2 Bounded Exhaustive Testing
In general, it is not possible to test a program over
all possible inputs. In Bounded Exhaustive Testing,
a bounded subcase of the application is formulated,
and all possible behaviors of the subcase are tested.
It is argued that many of the faulty behaviors that
can occur for the general case will also occur for the
bounded subcase. Our experience indicates that, in
particular, the combinations associated with elusive
bugs will occur in both the full-sized application and
the bounded subproblem
Similar ideas have been used in the past. For
example, when a program has loops for which the
numbers of iterations that are carried out depends on
input data, it is common to use bounded tests that
will cause 0,1 and possibly one or two larger
numbers of iterations. In [Howden, W.E., 2005], an
approach to bounded exhaustive testing is described
which uses real input to bound the problem and
symbolic input to summarize the complete behavior
of a program within the bounded domain. Model
checking is also a kind of exhaustive approach, in
which all states in a bounded version of the problem
are examined. However, in model checking the
focus is on analysis rather than testing.
Recent BET research has been carried out in the
context of class-based unit testing and involves
straight testing rather than testing combined with
symbolic evaluation or analysis. In addition, it has
resulted in new research on methods for defining and
generating bounded input domains. One of the first
of these, the Iowa JML/JUnit project, described in
[Cheon, Y., Leavens, G., 2002], has a method for
defining and generating BET tests. BET was also
the focus of the Korat project research, described in
[Boyapati, C., Khurshid, S., Marinov, D., 2002.].
1.3 Overview of Paper
This paper is organized as follows. Section 2 is a
review of JUnit. Section 3 describes the BETUnit
approach and Section 4 describes an example
scenario for BetUnit usage. Section 5 describes
other work, and in particular, the Iowa JML/JUnit
and Korat projects. Section 6 contains conclusions
and future work.
2 JUNIT – REVIEW
JUnit consists of a collection of classes and a test
automation strategy. The TestRunner class runs
tests. It is given a file containing a class definition
for a subclass of the TestCase class which is
expected to contain a suite() method. suite() will
return an object of type TestSuite, which contains a
collection of test cases. Each of these test cases is an
instance of a subclass of TestCase containing a test
method. TestRunner executes each test object in the
suite. It does this by calling its run() method.. It
passes a TestResult instance as an argument to run(),
which is used to record test results. run() runs the
setUp(), runTest() and tearDown () methods in the
TestCase subclass instance. runTest() runs the test
method in that instance. There are several
approaches to implementing runTest(). One is to
have it run the method whose name is stored in a
special class variable in the instance.
The user of JUnit has two options: default and
custom. In the default use, the default definition for
the suite() method is used when TestRunner is given
an instance of a subclass S of TestCase. S must also
contain all of the test methods, whose names must
have the prefix "test". The default suite() method
will use reflection to find the test methods. It will
then build instances of S for each of the test
methods. For each test x, the name of the test
method can be stored in the special class variable
that is used by the runTest() method to identify the
test method to run. These TestCase instances will be
added to the TestSuite instance that suite() it creates,
which is what it returns to TestRunner.
In the custom approach, the tester creates a new
subclass of TestCase with a custom suite() method
definition. Typically, suite() will create an instance
TestSuite, and then add instances of subclasses of
TestCase. Each of these subclasses will have a
defined test method that will be run when the
TestRunner executes the suite. This method can be
identified by the class variable x used to store the
name of the method to be run. It can be set by using
the TestCase constructor which takes a string
parameter that will be stored in x. It is possible to
create a composite of TestSuite and TestCase
subclass instances. The composite forms a tree, with
the TestCase instances at the leaves. The run()
method for a TestSuite instance will call the run()
methods of its TestSuite children. At the leaves, the
run() methods for the TestCase instances will be
called.
JUnit uses an assertion approach to oracles. A
special exception class called Assertion is defined.
TEST FRAMEWORKS FOR ELUSIVE BUG TESTING
251