Auto Test
Auto Test
Fall 2008
• It is not possible to prove that there are no faults in the software using
testing
• Testing should help locate errors, not just detect their presence
– a “yes/no” answer to the question “is the program correct?” is not
very helpful
• The basic difficulty in testing is finding a test set that will uncover the
faults in the program
• Coverage is a problem
Generating Test Cases Randomly
assignAbsolute(int x)
{ Consider this program segment, the test set
if (x < 0) T = {x=1} will give statement coverage,
x := -x; however not branch coverage
z := x;
}
B0
Control Flow Graph: (x < 0)
true false
B1 Test set {x=1} does not
x := -x execute this edge, hence, it
does not give branch coverage
B2
z := x
Control Flow Graphs (CFGs)
• Nodes in the control flow graph are basic blocks
– A basic block is a sequence of statements always entered at the
beginning of the block and exited at the end
• Edges in the control flow graph represent the control flow
if (x < y) { B0 (x < y)
x = 5 * y; Y N
x = x + 3;
} x = 5 * y B1 B2 y = 5
else x = x + 3
y = 5;
x = x+y;
x = x+y B3
• Select a test set T such that by executing program P for each test
case d in T, each edge of P’s control flow graph is traversed at least
once
B0
(x < 0)
true false
B1 Test set {x=1} does not
x := -x execute this edge, hence, it
does not give branch coverage
• Note that every path in the control flow graphs may not be executable
– It is possible that there are paths which will never be executed
due to dependencies between branch conditions
• In the presence of cycles in the control flow graph (for example loops)
we need to clarify what we mean by path coverage
– Given a cycle in the control flow graph we can go over the cycle
arbitrary number of times, which will create an infinite set of paths
– Redefine path coverage as: each cycle must be executed 0, 1, ...,
k times where k is a constant (k could be 1 or 2)
Condition Coverage
• In the branch coverage we make sure that we execute every branch at
least once
– For conditional branches, this means that, we execute the TRUE
branch at least once and the FALSE branch at least once
• Conditions for conditional branches can be compound boolean
expressions
– A compound boolean expression consists of a combination of
boolean terms combined with logical connectives AND, OR, and
NOT
• Condition coverage:
– Select a test set T such that by executing program P for each test
case d in T, (1) each edge of P’s control flow graph is traversed at
least once and (2) each boolean term that appears in a branch
condition takes the value TRUE at least once and the value FALSE
at least once
• Condition coverage is a refinement of branch coverage (part (1) is
same as the branch coverage)
Condition Coverage
T = {(x=1, y=1), (x=1, y=1)} will achieve
statement, branch and path coverage,
something(int x) however T will not achieve condition
{ coverage because the boolean term (y <
if (x < 0 || y < x) x) never evaluates to true. This test set
{ satisfies part (1) but does not satisfy part (2).
y := -y;
x := -x; B0 T = {(x=1, y=1), (x=1, y=0)}
} (x < 0 || y < x) will not achieve condition coverage
z := x; either. This test set satisfies part (2)
} true false but does not satisfy part (1). It does
B1 not achieve branch coverage since
y := -y; both test cases take the true branch,
x := -x; and, hence, it does not achieve
condition coverage by definition.
Control Flow Graph B2
T = {(x=1, y=2), {(x=1, y=1)}
z := x
achieves condition coverage.
Multiple Condition Coverage
• Multiple Condition Coverage requires that all possible combination of truth
assignments for the boolean terms in each branch condition should
happen at least once
• For example for the previous example we had:
x < 0 && y < x
term1 term2
• Test set {(x=1, y=2), (x=1, y=1)}, achieves condition coverage:
– test case (x=1, y=2) makes term1=true, term2=true, and the whole
expression evaluates to true (i.e., we take the true branch)
– test case (x=1, y=1) makes term1=false, term2=false, and the whole
expression evaluates to false (i.e., we take the false branch)
• However, test set {(x=1, y= 2), (x=1, y=1)} does not achieve multiple
condition coverage since we did not observe the following truth
assignments
– term1=true, term2=false
– term1=false, term2=true
Types of Testing
• Unit (Module) testing
– testing of a single module in an isolated environment
• Integration testing
– testing parts of the system by combining the modules
• System testing
– testing of the system as a whole after the integration phase
• Acceptance testing
– testing the system as a whole to find out if it satisfies the
requirements specifications
Types of Testing
• Unit (Module) testing
– testing of a single module in an isolated environment
• Integration testing
– testing parts of the system by combining the modules
• System testing
– testing of the system as a whole after the integration phase
• Acceptance testing
– testing the system as a whole to find out if it satisfies the
requirements specifications
Unit Testing
• Involves testing a single isolated module
• Note that unit testing allows us to isolate the errors to a single module
– we know that if we find an error during unit testing it is in the
module we are testing
• Modules in a program are not isolated, they interact with each other.
Possible interactions:
– calling procedures in other modules
– receiving procedure calls from other modules
– sharing variables
Module
Driver Stub
procedure Under Test procedure
call call
access to global
variables
• Driver and Stub should have the same interface as the modules they replace
• Driver and Stub should be simpler than the modules they replace
Integration Testing
• Integration testing: Integrated collection of modules tested as a group
or partial system
A B
• We assume that
the uses hierarchy is
a directed acyclic graph.
• If there are cycles merge
D them to a single module
level 1 C H
level 0 E F G I
• Modules at lower levels are tested using the previously tested higher
level modules
• Requires a module driver for each module to feed the test case input
to the interface of the module being tested
– However, stubs are not needed since we are starting with the
terminal modules and use already tested modules when testing
modules in the lower levels
Bottom-up Integration
A B
C H
E F G I
Top-down Integration
• Only modules tested in isolation are the modules which are at the
highest level
A B
C
H
E F G I
Other Approaches to Integration
• Sandwich Integration
– Compromise between bottom-up and top-down testing
– Simultaneously begin bottom-up and top-down testing and meet
at a predetermined point in the middle
• Manual testing
– Somebody uses the software on a bunch of scenarios and
records the results
– Use cases and use case scenarios in the requirements
specification would be very helpful here
– manual testing is sometimes unavoidable: usability testing
System Testing, Acceptance Testing
• Alpha testing is performed within the development organization
• Stress testing
– push system to extreme situations and see if it fails
– large number of data, high input rate, low input rate, etc.
Regression testing
• You should preserve all the test cases for a program
• When you find a bug in your program you should write a test case
that exhibits the bug
– Then using regression testing you can make sure that the old
bugs do not reappear
Test Plan
• Testing is a complicated task
– it is a good idea to have a test plan
• TestEra uses Alloy and Alloy Analyzer to generate the test cases and
to evaluate the correctness criteria
TestEra
spec
counter
example
Alloy Java Alloy I/O
input spec tester spec fail
Alloy Check
Concretization Run Code Abstraction correctness
Analyzer
module list
import integer
sig List {
elem: Integer,
next: lone List }
fact OutputOK {
MergeSortOk(Input, Output) }
Counter-Examples
• If an error is inserted in the method for merging where
(l1.elem <= l2.elem) is changed to (l1.elem >= l2.elem)
Counterexample found:
Input List: 1 -> 1 -> 3 -> 2
Output List: 3 -> 2 -> 1 -> 1
Abstraction and Concretization Translations
• An abstraction function: j2a
– translate Java instance to Alloy instance
• Alloy Analyzer
– Found some bugs in the Alloy Analyzer using TestEra framework
Korat
• Another automated testing tool
– Similar to TestEra but does not require extra Alloy specifications
– Application domain is again unit testing of complex data structures
• Korat then executes the method on each test case and uses the
method postcondition as a test oracle to check the correctness of
output
import java.util.*;
class BinaryTree {
private Node root;
private int size;
static class Node {
private Node left;
private Node right;
}
public boolean repOk() {
// this method checks the class invariant:
// checks that empty tree has size zero
// checks that the tree has no cycle
// checks that the number of nodes in the tree is
// equal to its size
}
Finitization
• Korat uses a finitization description to specify the finite bounds on the
inputs (scope)
N0 N0 N0
right right left N0 left
N0
right
N1 left
N1 N1 N1 N1 N2
right
left
N2 right left
N2 N2 N2
Each of the above trees correspond to 6 isomorphic trees. Korat only generates
one tree representing the 6 isomorphic trees.
For finBinaryTree(7) Korat generates 429 non-isomorphic trees in less than a second
Isomorphic Instances
N0 N0 N1 N1
left right left right left right left right
N1 N2 N2 N1 N2 N0 N0 N2
N2 N2
left right left right
N1 N0 N0 N1
Generating test cases
• The crucial component of Korat is the test case generation algorithms
• Consider the binary tree example with scope 3
– There are three fields: root, left, right
– Each of these fields can be assigned a node instance or null
– There is one root field and there is one left and one right field for
each node instance
– Given n node instances, the state space (the set of all possible
test cases) for the binary tree example is:
(n+1)2n + 1
– Most of these structures are not valid binary trees
• They do not satisfy the class invariant
– Most of these structures are isomorphic (they are equivalent if we
ignore the object identities)
Generating test cases
• There are two techniques Korat uses to generate the test cases
efficiently
• Two test cases are defined to be isomorphic if the parts of their object
graphs reachable from the root object are isomorphic
• The isomorphism definition partitions the state space (i.e. the input
domain) to a set of isomorphism partitions
– Korat generates only one test case for each partition class
Generating Test Cases
• Korat only generates the test cases which satisfies the input
predicate: class invariant and the precondition
N0 [1,0,2,3,0,0,0,0,] [1,0,2,3,0,0,0,0,]
left right
N1 N2
fields of the fields of the
Binary tree object Node object N1
size=3
Generating Test Cases
• Search starts with a candidate vector set to all zeros.
• For each candidate vectors, Korat sets fields in the objects according
to the values in the vector.
• Korat then invokes repOk (i.e., class invariant) to check the validity of
the current candidate.
– During the execution of repOk, Korat monitors the fields that
reopOK accesses, it stores the indices of the fields that are
accessed by the repOK (field ordering)
– For example, for the binary tree example, if the repOK accesses
root, N0.left and N0.right, then the field ordering is 0, 2, 3
• Korat generates the next candidate vector by backtracking on the
fields accessed by repOk.
– First increments the field domain index for the field that is last in
the field-ordering
– If the field index exceeds the domain size, then Korat resets that
index to zero and increments the domain index of the previous
field in the field ordering
Generating Test Cases
• Korat achieves non-isomorphic test case generation using the
ordering of field domains and the vector representation
• While generating the test cases, Korat ensures that the indices of the
objects that belong to the same class domain are listed in
nondecreasing order in the generated candidate vectors
• This means that during backtracking, Korat looks for fields
– that precede the field that is accessed last and
– that have an object from the same class domain as the field that is
accessed last
– and makes sure that the object assigned to the field that is
accessed last is higher in the ordering then those objects
Using Contracts in Testing
• Korat checks the contracts written in JML on the generated instances
• Test case generation with Korat is more efficient than the test case
generation with Alloy Analyzer.