Professional Documents
Culture Documents
Write Modify
program program Release
Waterfall model
Establish
requirements
Create
design
Implement
code
Test
system Release
Iterative process
Create
design
Implement
code
Test
system Release
Evolutionary Development
Refinement
cycle
Implementation Identify
relationships
Specific methods
and algorithms
Detailed
design
Testing
The development of a class involves design, specification,
implementation, and testing.
Testing is a fundamental part of building a software
system.
Testing is an activity with the goal of determining whether
or not an implementation functions as intended:
Testing attempts to determine if the implementation is correct.
Testing (cont.)
For any interesting problem, we cannot exhaustively test
all cases.
Testing is effective at showing errors in your
implementation, but really doesn’t show that the
implementation is correct.
Testing consists of two phases:
Test activities are determined and test data selected.
Test design
The test is conducted and test results compared with
expected results.
Testing (cont.)
We will consider two forms of testing:
Functional (or System) Testing
Testing an entire system to make sure that it conforms to the
customer’s requirements
Black box testing
Unit and Integration Testing
Unit testing involves testing individual system components to
verify that they perform correctly
Integration testing involves the interaction between individual
system components to make sure that they interact correctly
White box testing and Gray box testing
Functional Testing
Systems testing
Black box testing
Test design generally begins with the analysis of:
functional specifications of the system
system requirements
ways in which the system will be used
“use cases”
Unit Testing
The other form of testing is unit testing.
Recall that we test a class we are implementing to
increase our confidence that it behaves as expected.
Testing is part of the job of implementing the class.
Programmers often spend more time debugging than
writing code. As such, the best way to improve
productivity is to spend more effort on design.
Once the design is implemented, the best way to reduce
debugging time is to adopt an aggressive testing
regimen.
Incremental test-driven implementation is an effective
means of reducing the time required to track down and
correct bugs.
White Box Testing
One form of unit testing is white box testing.
White box testing derives its test cases based on the
implementation.
Test cases are defined by considering as many of the
paths through the program as possible.
The ultimate white box test is the execution of every
possible path through the program.
In general, it is not feasible to execute every path so
we will consider some less complete testing criteria:
Decision coverage
Condition coverage
Multiple condition coverage
Decision Coverage
For each decision, you should add test cases, one for
the true case and one for the false case.
Unfortunately, this approach does not guarantee a
complete set of test cases:
If a program has no decisions then this approach would
produce no test cases.
If a program has complex conditions then this approach
may not be adequate. Consider the following if statement:
if ((a < 0) && (b < 10)) {
…
}
Decision Coverage (cont.)
For each decision, you should add test cases such that
each condition in the decision takes on all possible
outcomes at least once.
If a program has complex conditions then this
approach still may not be adequate. Consider the
following if-else statement:
if ((a < 0) && (b < 10)) {
…
}
else {
…
}
Condition Coverage (cont.)
Each condition in the decision is exercised by the test
cases:
a = -1, b = 11
a = 5, b = 5
Note that this does not cause the else clause to
execute.
One way to avoid the problems with decision coverage
and condition coverage is to combine these methods:
Each condition in a decision takes on all possible
outcomes at least once and each decision takes on all
possible outcomes at least once.
But this still does not guarantee coverage of all
possible outcomes of all possible combinations of
conditions.
Multiple Condition Coverage
Multiple condition coverage is a more general criteria
which covers the problems with decision and condition
coverage.
Define enough test cases so that every possible
combination of condition outcomes and all points of
entry in a program are invoked at least once.
Consider the following code segment:
if ((a > 1) && (b == 0)) {
statement1
}
if ((a == 2) || (c > 1)) {
statement2
}
Multiple Condition Coverage
(cont.)
All possible combinations These cases come directly
of condition outcomes in from truth tables:
each decision can be
covered with the a > 1 b == 0
following eight cases: true true
1. a > 1, b == 0 true false
2. a > 1, b != 0 false true
3. a <= 1, b == 0 false false
4. a <= 1, b != 0
a == 2 c > 1
5. a == 2, c > 1
true true
6. a == 2, c <= 1
true false
7. a != 2, c > 1 false true
8. a != 2, c <= 1 false false
Multiple Condition Coverage
(cont.)
These eight cases can be covered by the following four
cases:
1. a == 2, b == 0, c == 2
⇒ Covers cases 1 and 5
2. a == 2, b == 1, c == 1
⇒ Covers cases 2 and 6
3. a == 1, b == 0, c == 2 1. a > 1, b == 0
⇒ Covers cases 3 and 7 2. a > 1, b != 0
4. a == 1, b == 1, c == 1 3. a <= 1, b ==
0
⇒ Covers cases 4 and 8
4. a <= 1, b !=
0
5. a == 2, c > 1
6. a == 2, c <=
1
Multiple Condition Coverage
(cont.)
Notice that we still haven’t executed all paths through
the segment:
1. a == 2, b == 0, c == 2
⇒ Executes statement1 and executes statement2
2. a == 2, b == 1, c == 1
⇒ Skips statement1 and executes statement2
3. a == 1, b == 0, c == 2
⇒ Skips statement1 and executes statement2
4. a == 1, b == 1, c == 1
⇒ Skips statement1 and skips statement2
}
}
Testing TrafficSignal (cont.)
We start by defining a new class named
TrafficSignalTest:
/**
* A tester for the class TrafficSignal.
*/
class TrafficSignalTest {
/**
* Create a TrafficSignalTest.
*/
public TrafficSignalTest () {
signal = new TrafficSignal();
}
/**
* Run the test.
*/
public void runTest () {
}
Testing TrafficSignal (cont.)
We’ll finish this class by completing runTest() later,
but first let’s create a driver class Test:
/**
* A simple test system for the class TrafficSignal.
*/
class Test {
/**
* Run the test.
*/
public static void main (String[] argv) {
TrafficSignalTest test;
test = new TrafficSignalTest();
test.runTest();
}
}
Testing TrafficSignal (cont.)
We could eliminate the variable and assignment, and
write the main() method as a single statement:
/**
* A simple test system for the class TrafficSignal.
*/
class Test {
/**
* Run the test.
*/
public static void main (String[] argv) {
(new TrafficSignalTest()).runTest();
}
}
Testing TrafficSignal (cont.)
Let’s begin writing runTest() by testing the initial state.
Although we could write all of our tests inline in
runTest(), the testing task is much more manageable if
we put each test in a separate method.
/**
* Run the test.
*/
public void runTest () {
testInitialState();
}
/**
* Test the TrafficSignal's initial state.
*/
private void testInitialState () {
System.out.println("testInitialState:");
System.out.println("Initial light: " + signal.light());
}
Testing TrafficSignal (cont.)
Testing TrafficSignal (cont.)
Now let’s add a method to test the state changes.
/**
* Run the test.
*/
public void runTest () {
testInitialState();
testChange();
}
/**
* Test the method change.
*/
private void testChange () {
System.out.println("testChange:");
System.out.println("Starting light: " + signal.light());
signal.change();
System.out.println("After 1 change: " + signal.light());
signal.change();
System.out.println("After 2 changes: " + signal.light());
signal.change();
System.out.println("After 3 changes: " + signal.light());
}
Testing TrafficSignal (cont.)
The TrafficSignal class is fairly trivial so there are
not a lot of cases to consider.
/**
** @require B >= 0
** @ensure result == A * B
**/
“… a thorough study
of all things, insight
into all that exists,
knowledge of all
obscure secrets …”
Egyptian mathematics
Deciphering the Rhind Papyrus
Rosetta Stone
Contained a passage
written in Greek,
demonic, and
hieroglyphics
Thomas Young, a British
physicist, and Jean
Francois Champollion, a
French Egyptologist,
collaborated to decipher
the hieroglyphic and
demotic texts by
comparing them with
the known Greek text
Egyptian Multiplication
“Halved”
“Doubled” Multiplier Remainder
Multiplicand
981 123 4 123 4
490 246 8
245 493 6 493 6
122 987 2
61 1974 4 1974 4
30 3948 8
15 7897 6 7897 6
7 1579 52 1579 52
3 3159 04 3159 04
1 6318 08 6318 08
12 105 54
Egyptian Multiplication (cont.)
/**
** @require B >= 0
** @ensure result == A * B
**/
y = 3 * x + 1;
{y < 7}
y = 3 * x + 1;
y = 3 * x + 1;
{x < 2}
x = y + 3; {y < 7}
y = 3 * x + 1;
{x < 10} x = y + 3;
{y < 7}
{x < 10}
x = y + 3;
{x < 10}
Sequential Statements (cont.)
Consider the following code segment with
postcondition:
temp = x;
x = y;
y = temp;
{x == b && y == a}
temp = x;
Thetemp = x;
constructed weakest
x = y;precondition is: = x;
temp
x ={yy;=={yb == {y == b && x == a}
&& bx &&
== temp
a} == a}
y = temp;{x == b && temp temp
== = x;
a}
Comparingxthis = y;
with the
{x == postcondition,
b y&&= ytemp;
=={xa}== {y that
we bsee == b &&code
the temp == a}
&& temp == a}
does, indeed,
{x == b y&&swap
y ==the
x = values
a} y; of x
and y. = temp;
{x == b && temp == a}
{x == b && y == a}
y = temp;
{x == b && y == a}
Sequential Statements (cont.)
Consider the following code segment with
postcondition:
y = 4;
z = x + y;
{z = 7}
Let’s construct the weakest precondition
that produces the stated postcondition.
Working backward:
y = 4;
The constructed y = 4; {x ==
+ 43}== 7}
weakest y7}= 4;
precondition is {x {x + y ==
== 3}. z = x + y;z = x + y;{x + y == 7}
{z == 7} {z == 7} z = x + y;
{z == 7}
Conditional Statements
{P}
if (B) {
S1
}
else {
S2
}
{Q}
Conditional Statements (cont.)
The if statement can take us from P to Q
along either of two paths:
truecase: P → B → S1 → Q
falsecase: P → ~B → S2 → Q
The truecase:
max = x;
{(x >= y && max == x) || (x < y && max == y)}
The falsecase:
max = y;
{(x >= y && max == x) || (x < y && max == y)}
wp while B C , Q k B Q B
The following recursive definition defines the loop
invariant Ik assuming the loop has a single
terminating condition τ:
wp C , Q if n 0
n
wp C , n 1 B otherwise
Logical Pretest Loops (cont.)
/**
** @require old.B
B >= 0>= 0
** @ensure result == A * B
**/ (0
(old.B
(0 ==
== (old.A
0)
>= &&
0) (old.B
* old.B)–(old.A
>= 0) * old.B)) && (B >= 0)
public int compute(int A, int B) {
x = x + m;
} Loop invariant:
(x == (A*B)–(m*n)) && (n >= 0)
m = 2 * m;
n = n / 2;
return x; x == A * B
}
result == A * B
Logical Pretest Loops (cont.)
/**
** @require true
** @ensure result == N * N
**/ true
public int compute(int N) {
if (N < 0) {
true
((N<<0)
(N 0)||
&&(N
(-N
>=>=
0)0)) || (!(N < 0) && (N >= 0))
N = -N;
}
true
N
(0 >=
== 0
&& (N >= 0)&& (N &&
(N*N–N*N)
N*N–N–N*(N–1)) >= (N
0) >= 0)
int m = 0;
(0 == N*N–(N–m)–(N–m)*(N–m–1)) && (N >= m)
int x = 0;
(x == N*N–(N–m)–(N–m)*(N–m–1)) && (N >= m)
int y = 1;
(x == N*N–(N–m)*y–(N–m)*(N–m–1)) && (N >= m)
while (m < N) {
x = x + y;
Loop invariant:
y = y + 2;
(x == N*N–(N–m)*y–(N–m)*(N–m–1)) && (N >= m)
m = m + 1;
}
x == N * N
return x;
result == N * N
How do we find an appropriate loop invaria
}