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
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.
Testing TrafficSignal (cont.) 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 () { } }
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.
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.
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
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).
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.
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}
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} 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.)
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.)
65.
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.)
66.
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.)
67.
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.)
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.
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 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.