You are on page 1of 76

Chapter 6

Testing and verification


Software Engineering
 A program’s life cycle is the sequence of phases it goes
through from its conception to its ultimate demise.
 A new program is created in the development stage.
 Software maintenance is the process of modifying a
program in order to enhance it or fix problems.
 Historically, computer scientists have developed and used
various software development process models, including:
 Build-and-Fix
 Waterfall model
 Iterative process
Build-and-Fix

 The build-and-fix approach does not address the overall


quality of a program.

Write Modify
program program Release
Waterfall model

 The waterfall model is a linear process in which one stage


is followed by the next.

Establish
requirements

Create
design

Implement
code

Test
system Release
Iterative process

 An iterative process is one that allows a software


developer to cycle through different development activities.

Establish  The iterative process allows


requirements backtracking to correct problems.

Create
design

Implement
code

Test
system Release
Evolutionary Development

 The iterative process leads to the following evolutionary


development process.
Establish System
Establish Architectural
requirements design refinement test Release
scope

Unit and Identify


General structure Integration classes &
of the system test objects

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.)

 Each decision outcome is exercised by the test cases:


 a = 2, b = 5
 a = -1, b = 5
 Note that this does not exercise the condition on b.
Condition Coverage

 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

The path “executes statement1 and skips


statement ” is not executed.
Gray Box Testing
 Another form of unit testing is gray box testing.
 Gray box testing is also referred to as data-driven
testing:
 Not concerned with the internal structure of the method.
 Test data is derived solely from method specifications.
 Look for circumstances in which the method does not
behave according to its specifications.
 Let’s consider 3 approaches for gray box testing:
 Equivalence Partitioning
 Boundary Value Analysis
 Error Guessing
Equivalence Partitioning
 Partition the domain of input values into groups called
equivalence classes.
 Equivalence classes are chosen such that testing one
element in the class is equivalent to testing any other
element in the class.
 Valid equivalence classes represent valid inputs to the
method.
 Invalid equivalence classes represent all other inputs
(i.e., those that violate the precondition)
 Invalid inputs are program errors and should not be tested.
 No rules for identifying equivalence classes, but there
are some guidelines.
Equivalence Partitioning (cont.)
 If an input condition specifies a range of values, identify
a valid equivalence class for that range.
 If an input condition specifically lists the number of
values, identify one valid equivalence class for these
values.
 If an input condition specifies a set of input values and
there is reason to believe that each is handled differently
by the program, identify one valid equivalence class for
each element in the set.
 If an input condition specifies a “must be” situation,
identify a valid equivalence class for it.
 If there is any reason to believe that elements in an
equivalence class are not handled in an identical manner
by the program, split the equivalence class into smaller
equivalence classes.
Boundary Value Analysis
 Divide the input space into equivalence classes and
select a test case from each class.
 Boundary value analysis focuses on selecting data near
the edges of conditions on both input space and output
space.
 General guidelines:
 If an input or output condition specifies a range of values,
write test cases for the ends of the range.
 If an input or output condition specifies a number of values,
write test cases for the minimum and maximum number of
values.
 If the input or output of a procedure is an ordered set,
focus attention on the first and last elements of the set.
Error Guessing
 Error guessing is largely based on intuition and
experience as to frequently encountered errors.
 Examples of error guessing situations:
 Null strings
 0 numeric values
 Numeric values nearly exceeding computer storage
capability
 Collections or files that are empty or contain only one value
 Collections containing all the same value
 Collections in some ordered form
Combined Strategy
 Overall strategy for gray box testing:
1. Analyze preconditions and postconditions to make a list
of the external conditions, both input and output.
2. Divide each of these external conditions into valid
equivalence classes.
3. Use boundary value analysis to identify test cases for
these equivalence classes. A test case may cover
more that one equivalence class.
4. Use error guessing to add other classes and test
cases.
5. For each test case, identify the expected output.
6. Test the procedure and compare the expected output
with the actual output.
Testing Summary
 Now let’s try to apply some of the ideas we just discussed
about testing to the design and implementation of our
classes.
 We’ll focus on gray box testing.
 We’ll employ incremental test-driven implementation:
 Implementation proceeds in relatively small increments.
 Code a little, test a little.
 Each time you add a new bit of functionality, you test it
immediately.
 Write a test for a feature before implementing the feature.
 Let’s consider the process of testing the TrafficSignal
class.
Testing TrafficSignal
 We want to create a TrafficSignal instance and
exercise it by querying it and giving it commands.
 To do this, we need another object to act as client to the
TrafficSignal.
 We will call this class TrafficSignalTest and put it in
the same package as the TrafficSignal class.
 Since it does not need to be accessed from outside the
package, we do not need to make TrafficSignalTest
public.
 The only property of TrafficSignalTest is the
TrafficSignal to be tested.
Testing TrafficSignal (cont.)
 Stubbed implementation of 
TrafficSignal
public class TrafficSignal {
public static final int GREEN = 0;
public static final int YELLOW = 1;
public static final int RED = 2;
private int light;
public TrafficSignal () {
Left blank
}
public int light () {
return 0; Dummy return value
}
public void change () {
Left blank

}
}
Testing TrafficSignal (cont.)
 We start by defining a new class named
TrafficSignalTest:
/**
* A tester for the class TrafficSignal.
*/
class TrafficSignalTest {

private TrafficSignal signal; // the object to test

/**
* 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.

 Note that the methodology of designing the test cases


(via the Test class) along with the TrafficSignal
class promotes a test-driven incremental design.

 Further note that by defining the test cases based on the


specification before implementing the method, we can
avoid being biased by the implementation.
Verification
 Consider the following simple Java method:

/**
** @require B >= 0
** @ensure result == A * B
**/

public int compute(int A, int B) {


int x = 0;
int m = A;
int n = B;
while (n > 0) {
if ((n % 2) != 0) {
x = x + m;
}
m = 2 * m;
n = n / 2;
} Is this method correct?
return x;
}
Egyptian Numbering System

Ahmes inscribed the


“Rhind Papyrus”
(~1850 BC)

“… 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
**/

public int compute(int A, int B) {


int x = 0;
int m = A;
int n = B;
while (n > 0) {
if ((n % 2) != 0) {
x = x + m;
}
m = 2 * m;
n = n / 2;
}
return x;
}
Program Correctness

 Attempts to provide a more precise specification of


the behavior of a method.
 Such a method specification includes two parts:
• Any preliminary conditions that are required for
the method to execute properly, known as the
method’s precondition.
• The changes that result from executing the
method, known the method’s postcondition.
Program Correctness (cont.)

 Taken together, the precondition and postcondition


form a software contract. This contract states the
following:
IF the calling code ensures the precondition is
true at the time it calls the method
THEN the postcondition is guaranteed to be true at
the time the method completes execution
Program Correctness (cont.)

 Preconditions and postconditions are logical


expressions which describe states of the program
variables.
 Given a method, the postcondition describes the
expected outcome of its execution.
 The role of the precondition is to identify
requirements for using the method so as to ensure
that the postcondition is met.
► Violating a precondition should be considered a logic
error.
Program Correctness (cont.)

 To be valid, the precondition should define a state


from which the method can produce the
postcondition.
 It is reasonable to ask, what precondition will
produce the desired postcondition?
► To answer this, we will construct such a precondition
starting with the postcondition.
Constructing Preconditions
 To construct a precondition for a method, we start
with the postcondition and work backward to
successively compute the precondition for each
statement of the method.
► At each step, we seek the broadest precondition
that produces the desired postcondition.
 The weakest precondition of a statement (or
method) is the least restrictive precondition that will
guarantee the validity of the associated
postcondition for that statement (or method).
Constructing Preconditions (cont.)
 Consider the following code segment with
postcondition:
sum = 2 * x + 1;
{sum > 1}
The assertion {sum > 1} is the postcondition
for the statement sum = 2 * x + 1;
Possible preconditions are {x > 10}, {x >
50}, and {x > 1000}.
The weakest precondition is {x > 0}.
The Assignment Axiom
 The basis for our technique is the
assignment axiom, which allows us to
perform a backward substitution.
 Let V = E be a general assignment statement
and Q be its postcondition. Formally, the
assignment axiom is stated:
wp  V  E , Q   QV  E
which means that the constructed weakest
precondition is Q with all instances of V
replaced by E.
The Assignment Axiom (cont.)
 Consider the following assignment statement
with postcondition:
a = b / 2 - 1;
{a < 10}
The weakest precondition is computed by
substituting b / 2 – 1 for a into the assertion
{a < 10}, thus
b / 2 – 1 < 10
b / 2 < 11
b < 22
So {b < 22} is the constructed weakest
precondition.
The Assignment Axiom (cont.)
 Consider the following assignment
statement with postcondition:
x = x - 3;
{x > 0}
Using the assignment axiom produces {x
> 3} for the constructed weakest
precondition.
Sequential Statements
 The weakest precondition for a block of
statements can be constructed by
successively backing over each of the
statements in the block (starting with the last
statement) and using the constructed
weakest precondition of each statement as
the postcondition to its preceding statement.
 The following Sequence Rule defines the
constructed weakest precondition for a pair of
1 2  1 2 
wp  C ; C , Q   wp C , wp  C , Q 
statements:

 Repeated applications of the Sequence Rule


can be used for blocks containing more than
2 statements.
Sequential Statements (cont.)
 Consider the following sequence of
statements with postcondition:
y = 3 * x + 1;
x = y + 3;
{x < 10}

The constructed weakest precondition for the


last statement is:
{y + 3 < 10}
{y < 7}
Sequential Statements (cont.)
 This becomes the postcondition to the first
statement:

y = 3 * x + 1;
{y < 7}

The constructed weakest precondition for the


last statement is:
{3 * x + 1 < 7}
{x < 2}
Sequential Statements (cont.)
 In practice, when deriving the precondition,
we frequently write out the statements and
the postcondition leaving blank lines between
each statement. We work backwards to fill in
the blank lines with the successive
preconditions as shown below:

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}

It is supposed to swap the values of x and y.


 Let’s verify that it does.
Sequential Statements (cont.)

 The construction would proceed as follows:

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

 Suppose we have the statement:

{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

When working backward to construct P, we


need to take into account each of these
paths.
Conditional Statements (cont.)
 For truecase, backing over S1 leads to a
precondition, call it P1.
 For execution to reach that point, the condition B
must have been true.
 Backing over it gives us P1 ∧ B as a condition of
executing the true path.
 For falsecase, backing over S2 leads to a
precondition, call it P2.
 For execution to reach that point, the condition B
must have been false (i.e., ~B must have been
true).
 Backing over ~B gives us P2 ∧ ~B as a condition of
executing the false path.
Conditional Statements (cont.)
 To cover both paths, we want:
P = (P1 ∧ B) ∨ (P2 ∧ ~B)
as the precondition.
 The following Conditional Rule defines the
constructed weakest precondition for a
conditional statement:
wp  if  B  C1 else C2 , Q    wp  C1 , Q   B    wp  C2 , Q   B 
Conditional Statements (cont.)
 Let’s construct the weakest precondition for the
following conditional:
if (x > y) {
y = x - 1;
}
else {
y = x + 1;
}
{y > 0}

The truecase: The falsecase:


y = x - 1; y = x + 1;
{y > 0} {y > 0}

produces the precondition {x – 1 > 0} produces the precondition {x + 1 > 0}


which simplifies to {x > 1}. which simplifies to {x > -1}.
Backing over the The disjoin of these
if condition, is
we get Backing over the negative of the if
{((x
{(x > 1) && (x > y)}. > 1) && (x > y)) || ((x > -1) &&
condition, we(xget<={(x
y))}
> -1) && (x <=
which serves as a precondition of the
y)}. if structure.
Conditional Statements (cont.)
 Let’s construct a precondition which ensures the
correctness of the following code segment:
if (n >= 5) {
y = n * n;
Thus, the constructed precondition is }
{(n == 6) || false}, which simplifies else {
to y = n + 1;
{n == 6}. }
{y == 36}

The truecase: The falsecase:


y = n * n; y = n + 1;
{y == 36} {y == 36}
produces the precondition {n * n == produces the precondition {n + 1 ==
36} which simplifies to {n == 6 || n 36} which simplifies to {n == 35}.
== -6}.
Backing over the negtive of the if
Backing over the if condition, we get condition, we get {(n == 35) && (n <
{(n == 6 || n == -6) && (n >= 5)} 5)}.
which simplifies to {n == 6}. This is always false.
Conditional Statements (cont.)
 Show that the following code segment assigns the
maximum of x and y to max:
if (x >= y) {
max = x;
}
else {
max = y;
}

We need to establish a postcondition that reflects


the requirement that, after execution, the value
of max is the larger of x and y. This can be stated
as:
(x >= y && max == x) || (x < y && max == y)
Conditional Statements (cont.)
if (x >= y) {
max = x;
}
else {
max = y;
}
{(x >= y && max == x) || (x < y && max == y)}

The truecase:

max = x;
{(x >= y && max == x) || (x < y && max == y)}

produces the precondition {(x >= y && x == x) || (x < y && x == y)}


which simplifies to {(x >= y && true) || false} and further simplifies to
{x >= y}.
Backing over the if condition, we get {(x >= y) && (x >= y)} which
simplifies to {x >= y}.
Conditional Statements (cont.)
if (x >= y) {
max = x;
}
else {
max = y;
}
{(x >= y && max == x) || (x < y && max == y)}

The falsecase:

max = y;
{(x >= y && max == x) || (x < y && max == y)}

produces the precondition {(x >= y && y == x) || (x < y && y == y)}


which simplifies to {(y == x) || (x < y && true)} and further simplifies
to {y >= x}.
Backing over the negative of the if condition, we get {(y >= x) && (x <
y)} which simplifies to {x < y}.
Conditional Statements (cont.)
if (x >= y) {
max = x;
}
else {
max = y;
}
{(x >= y && max == x) || (x < y && max == y)}

Combining the results of each pass, we get:

{(x >= y) || (x < y)}

Since this is always true, it means that the code


segment produces the postcondition for all values of
x and y.
We write the precondition as simply true.
Logical Pretest Loops

 Suppose we have the statement


{P}
while (G) {
S
}
{Q}

where the guard G is a logical expression and S


is the body of the loop.
The while statement can take us from P to Q
along any of a number of different paths,
depending on how many times the condition G is
true, including the path that goes directly from P
to Q when the condition G is initially false.
Logical Pretest Loops (cont.)

 Executing a while loop involves repeatedly


evaluating the guard G and executing the body S
until the guard becomes false, at which time
control passes to the statement after the loop.
 The condition that stops the loop int i = 0;
is called the terminating int s = 1;
condition and is normally more while (i < 10)
specific than the negative of the {
guard. Consider the code s = s * 2;
segment to the right. i = i + 1;
}
 The negative of the guard is
i >= 10, but the terminating
condition is i == 10.
The Reverse Execution Model

 To compute a precondition, we reverse execute


the loop for several passes in an attempt to
determine what must be true before the loop can
be properly executed.
 To begin the backward execution, we choose
the condition that must be true after the loop
has terminated.
 We know that after the loop has terminated, the
postcondition Q and the terminating condition
(which we call T) must be true.
 Thus we start backing over the loop with the
condition Q ∧ T.
 Backing over the loop produces a condition
that must be satisfied in order for the program
to forward execute that pass of the loop.
The Reverse Execution Model
(cont.)
 As a result of backing over the body of the loop,
at the top of the body, the condition Q ∧ T will
have been transformed into some other condition
(which we will call A1).
 We know the guard G must also have been
true in order for execution to have reached the
top statement of the loop body, thus backing
over the guard means the condition A1 ∧ G
must have been true prior to that pass over
the loop body.
 A1 ∧ G serves as a precondition for the last
forward pass and a postcondition to the second
backward pass over the loop.
The Reverse Execution Model
(cont.)
 Executing the second backward pass results
in a condition A2 at the top of the body.
 Backing over the guard means A2 ∧ G must
have been true prior to the program
executing that forward pass over the loop.
 Repeated backward executions yields
successive conditions A1 ∧ G , A2 ∧ G, A3 ∧ G ,
A4 ∧ G , A5 ∧ G , …
 Each of these conditions must have been
true before the program could have
executed the corresponding forward pass
through the loop.
 From these conditions we determine the
general pattern.
The Reverse Execution Model
(cont.)
 The general pattern defines a condition which
we call a loop invariant.
 A loop invariant, I, is an assertion within
a loop, evaluated prior to the loop guard,
which is true every time execution reaches
that position in the loop.
 The invariant must be true every time the
while loop guard is encountered.
 It is an assertion that is a precondition to
each execution of the body and must also
be true when the guard is encountered on
the terminating pass.
 That is, it must be true when the guard is
true or the terminating condition is true.
Logical Pretest Loops (cont.)
 The following Loop Rule defines the precondition
construction process for a conditional statement:

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) {

int x = 0; (0 == (A*B)–(A*B)) && (B >= 0)


int m = A; (x == (A*B)–(A*B)) && (B >= 0)
int n = B; (x == (A*B)–(m*B)) && (B >= 0)
while (n > 0) { (x == (A*B)–(m*n)) && (n >= 0)
if ((n % 2) != 0) {

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
}

You might also like