This action might not be possible to undo. Are you sure you want to continue?

# Chapter 6

**Testing and verification
**

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

Software Engineering

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

quality of a program.

Build-and-Fix

Write

program

Modify

program

Release

The waterfall model is a linear process in which one stage

is followed by the next.

Waterfall model

Establish

requirements

Create

design

Implement

code

Test

system

Release

An iterative process is one that allows a software

developer to cycle through different development activities.

Iterative process

Establish

requirements

Create

design

Implement

code

Test

system

Release

The iterative process allows

backtracking to correct problems.

The iterative process leads to the following evolutionary

development process.

Evolutionary Development

Establish

requirements

Architectural

design

Establish

refinement

scope

System

test

Identify

relationships

Unit and

Integration

test

Identify

classes &

objects

Implementation

Detailed

design

Release

Refinement

cycle

Specific methods

and algorithms

General structure

of the system

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

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

Testing (cont.)

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”

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

Unit 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

White Box Testing

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

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.

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

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.

Condition Coverage (cont.)

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

statement

1

}

if ((a == 2) || (c > 1)) {

statement

2

}

Multiple Condition Coverage

Multiple Condition Coverage

(cont.)

All possible combinations

of condition outcomes in

each decision can be

covered with the

following eight cases:

1. a > 1, b == 0

2. a > 1, b != 0

3. a <= 1, b == 0

4. a <= 1, b != 0

5. a == 2, c > 1

6. a == 2, c <= 1

7. a != 2, c > 1

8. a != 2, c <= 1

false false

true false

false true

true true

b == 0 a > 1

These cases come directly

from truth tables:

false false

true false

false true

true true

c > 1 a == 2

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

⇒ Covers cases 3 and 7

4. a == 1, b == 1, c == 1

⇒ Covers cases 4 and 8

Multiple Condition Coverage

(cont.)

1. a > 1, b == 0

2. a > 1, b != 0

3. a <= 1, b ==

0

4. a <= 1, b !=

0

5. a == 2, c > 1

6. a == 2, c <=

1

7. a != 2, c > 1

8. a != 2, c <=

1

Notice that we still haven’t executed all paths through

the segment:

1. a == 2, b == 0, c == 2

⇒ Executes statement

1

and executes statement

2

2. a == 2, b == 1, c == 1

⇒ Skips statement

1

and executes statement

2

3. a == 1, b == 0, c == 2

⇒ Skips statement

1

and executes statement

2

4. a == 1, b == 1, c == 1

⇒ Skips statement

1

and skips statement

2

The path “executes statement

1

and skips

statement

2

” is not executed.

Multiple Condition Coverage

(cont.)

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

Gray Box Testing

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

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.

Equivalence Partitioning (cont.)

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.

Boundary Value Analysis

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

Error Guessing

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.

Combined Strategy

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 Summary

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

Stubbed implementation of

TrafficSignal

Left blank

Left blank

Dummy return value

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

}

public int light () {

return 0;

}

public void change () {

}

}

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.

Testing TrafficSignal (cont.)

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;

}

/**

**

** @ensure result == A * B

**/

@require B >= 0

Verification

Consider the following simple Java method:

Is this method correct?

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

Egyptian Numbering System

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

Rosetta Stone

Deciphering the Rhind Papyrus

2468 490

4936 4936 245

9872 122

1974 4 1974 4 61

6318 08 6318 08 1

3159 04 3159 04 3

1579 52 1579 52 7

7897 6 7897 6 15

3948 8 30

1234 1234 981

Remainder “Doubled” Multiplier

“Halved”

Multiplicand

1210554

Egyptian Multiplication

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;

}

/**

**

** @ensure result == A * B

**/

@require B >= 0

Egyptian Multiplication (cont.)

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

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.

Program Correctness (cont.)

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

Consider the following code segment with

postcondition:

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

sum = 2 * x + 1;

{sum > 1}

Constructing Preconditions (cont.)

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:

which means that the constructed weakest

precondition is Q with all instances of V

replaced by E.

( )

,

V E

wp V E Q Q

÷

· ·

The Assignment Axiom

Consider the following assignment statement

with postcondition:

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.

a = b / 2 - 1;

{a < 10}

The Assignment Axiom (cont.)

Consider the following assignment

statement with postcondition:

Using the assignment axiom produces {x

> 3} for the constructed weakest

precondition.

x = x - 3;

{x > 0}

The Assignment Axiom (cont.)

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

statements:

Repeated applications of the Sequence Rule

can be used for blocks containing more than

2 statements.

( ) ( ) ( )

1 2 1 2

; , , , wp C C Q wp C wp C Q ·

Sequential Statements

Consider the following sequence of

statements with postcondition:

The constructed weakest precondition for the

last statement is:

{y + 3 < 10}

{y < 7}

y = 3 * x + 1;

x = y + 3;

{x < 10}

Sequential Statements (cont.)

This becomes the postcondition to the first

statement:

The constructed weakest precondition for the

last statement is:

{3 * x + 1 < 7}

{x < 2}

y = 3 * x + 1;

{y < 7}

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;

x = y + 3;

{x < 10}

Sequential Statements (cont.)

y = 3 * x + 1;

{y < 7}

x = y + 3;

{x < 10}

{x < 2}

y = 3 * x + 1;

{y < 7}

x = y + 3;

{x < 10}

Consider the following code segment with

postcondition:

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

Let’s verify that it does.

temp = x;

x = y;

y = temp;

{x == b && y == a}

Sequential Statements (cont.)

temp = x;

x = y;

y = temp;

{x == b && y == a}

temp = x;

x = y;

{x == b && temp == a}

y = temp;

{x == b && y == a}

temp = x;

{y == b && temp == a}

x = y;

{x == b && temp == a}

y = temp;

{x == b && y == a}

The construction would proceed as follows:

Sequential Statements (cont.)

{y == b && x == a}

temp = x;

{y == b && temp == a}

x = y;

{x == b && temp == a}

y = temp;

{x == b && y == a}

The constructed weakest

precondition is:

{y == b && x == a}

Comparing this with the

postcondition, we see that the code

does, indeed, swap the values of x

and y.

Consider the following code segment with

postcondition:

Let’s construct the weakest precondition

that produces the stated postcondition.

Working backward:

y = 4;

z = x + y;

{z = 7}

y = 4;

z = x + y;

{z == 7}

y = 4;

{x + y == 7}

z = x + y;

{z == 7}

{x + 4 == 7}

y = 4;

{x + y == 7}

z = x + y;

{z == 7}

{x == 3}

y = 4;

{x + y == 7}

z = x + y;

{z == 7}

The constructed

weakest

precondition is {x

== 3}.

Sequential Statements (cont.)

Suppose we have the statement:

{P}

if (B) {

S

1

}

else {

S

2

}

{Q}

Conditional Statements

The if statement can take us from P to Q

along either of two paths:

truecase: P → B → S

1

→ Q

falsecase: P → ~B → S

2

→ Q

When working backward to construct P, we

need to take into account each of these

paths.

Conditional Statements (cont.)

For truecase, backing over S

1

leads to a

precondition, call it P

1

.

For execution to reach that point, the condition B

must have been true.

Backing over it gives us P

1

∧ B as a condition of

executing the true path.

For falsecase, backing over S

2

leads to a

precondition, call it P

2

.

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 P

2

∧ ~B as a condition of

executing the false path.

Conditional Statements (cont.)

To cover both paths, we want:

P = (P

1

∧ B) ∨ (P

2

∧ ~B)

as the precondition.

The following Conditional Rule defines the

constructed weakest precondition for a

conditional statement:

Conditional Statements (cont.)

( ) ( ) ( ) ( ) ( ) ( )

1 2 1 2

, , , wp B C C Q wp C Q B wp C Q B · · v · ¬ if else

Let’s construct the weakest precondition for the

following conditional:

if (x > y) {

y = x - 1;

}

else {

y = x + 1;

}

{y > 0}

The truecase:

produces the precondition {x – 1 > 0}

which simplifies to {x > 1}.

Backing over the if condition, we get

{(x > 1) && (x > y)}.

y = x - 1;

{y > 0}

The falsecase:

produces the precondition {x + 1 > 0}

which simplifies to {x > -1}.

Backing over the negative of the if

condition, we get {(x > -1) && (x <=

y)}.

y = x + 1;

{y > 0}

The disjoin of these is

{((x > 1) && (x > y)) || ((x > -1) && (x <= y))}

which serves as a precondition of the 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;

}

else {

y = n + 1;

}

{y == 36}

The truecase:

produces the precondition {n * n ==

36} which simplifies to {n == 6 || n

== -6}.

Backing over the if condition, we get

{(n == 6 || n == -6) && (n >= 5)}

which simplifies to {n == 6}.

y = n * n;

{y == 36}

The falsecase:

produces the precondition {n + 1 ==

36} which simplifies to {n == 35}.

Backing over the negtive of the if

condition, we get {(n == 35) && (n <

5)}.

This is always false.

y = n + 1;

{y == 36}

Thus, the constructed precondition is

{(n == 6) || false}, which simplifies

to

{n == 6}.

Conditional Statements (cont.)

Show that the following code segment assigns the

maximum of x and y to max:

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)

if (x >= y) {

max = x;

}

else {

max = y;

}

Conditional Statements (cont.)

The truecase:

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

max = x;

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

if (x >= y) {

max = x;

}

else {

max = y;

}

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

Conditional Statements (cont.)

The falsecase:

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

max = y;

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

if (x >= y) {

max = x;

}

else {

max = y;

}

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

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.

Conditional Statements (cont.)

Suppose we have the statement

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.

{P}

while (G) {

S

}

{Q}

Logical Pretest Loops

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.

int i = 0;

int s = 1;

while (i < 10)

{

s = s * 2;

i = i + 1;

}

Logical Pretest Loops (cont.)

The condition that stops the loop

is called the terminating

condition and is normally more

specific than the negative of the

guard. Consider the code

segment to the right.

The negative of the guard is

i >= 10, but the terminating

condition is i == 10.

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

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 A

1

).

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 A

1

∧ G

must have been true prior to that pass over

the loop body.

A

1

∧ 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 A

2

at the top of the body.

Backing over the guard means A

2

∧ G must

have been true prior to the program

executing that forward pass over the loop.

Repeated backward executions yields

successive conditions A

1

∧ G , A

2

∧ G, A

3

∧ G ,

A

4

∧ G , A

5

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

The Reverse Execution Model

(cont.)

The following Loop Rule defines the precondition

construction process for a conditional statement:

The following recursive definition defines the loop

invariant I

k

assuming the loop has a single

terminating condition τ:

( ) ( ) ( ) ( )

,

k

wp B C Q B Q B · I · v ·¬ while

( )

( )

1

, 0

,

n

n

wp C Q if n

wp C B otherwise

t

¹ · ·

¹

I ·

'

I ·

¹

¹

Logical Pretest Loops (cont.)

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;

}

/**

**

** @ensure result == A * B

**/

@require old.B >= 0

result == A * B

x == A * B

Loop invariant:

(x == (A*B)–(m*n)) && (n >= 0)

(x == (A*B)–(m*n)) && (n >= 0)

(x == (A*B)–(m*B)) && (B >= 0)

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

(0 == (A*B)–(A*B)) && (B >= 0)

(0 == (old.A * old.B)–(old.A * old.B)) && (B >= 0) (0 == 0) && (old.B >= 0) (old.B >= 0)

@require B >= 0

Logical Pretest Loops (cont.)

public int compute(int N) {

if (N < 0) {

N = -N;

}

int m = 0;

int x = 0;

int y = 1;

while (m < N) {

x = x + y;

y = y + 2;

m = m + 1;

}

return x;

}

/**

**

** @ensure result == N * N

**/

@require true

result == N * N

x == N * N

Loop invariant:

(x == N*N–(N–m)*y–(N–m)*(N–m–1)) && (N >= m)

(x == N*N–(N–m)*y–(N–m)*(N–m–1)) && (N >= m)

How do we find an appropriate loop invariant?

(x == N*N–(N–m)–(N–m)*(N–m–1)) && (N >= m)

(0 == N*N–(N–m)–(N–m)*(N–m–1)) && (N >= m)

(0 == N*N–N–N*(N–1)) && (N >= 0) (0 == (N*N–N*N) && (N >= 0)

((N < 0) && (-N >= 0)) || (!(N < 0) && (N >= 0)) (N < 0) || (N >= 0)

true && (N >= 0) N >= 0

true

true

Logical Pretest Loops (cont.)