272: Software Engineering Fall 2012: Instructor: Tevfik Bultan
272: Software Engineering Fall 2012: Instructor: Tevfik Bultan
272: Software Engineering Fall 2012: Instructor: Tevfik Bultan
Fall 2012
• 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, however
something(int x) T will not achieve condition coverage
{ because the boolean term (y < x) never
if (x < 0 || y < x) 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
• Basic idea: Write the test cases before writing the code
– Test first, code second
– These functions are not known and are just theoretical concepts
used for defining properties of oracles
Formalizing Oracles
• An oracle o O identifies which tests pass and which tests fail
o(t, p) means that the test t passes for program p based on oracle o
• Assuming that the oracles are both complete, a more powerful oracle
can catch more errors
In other words, for each mutant m M, there exists a test t such that
the oracle o signals a fault.