• Save
Testing Fundamentals
Upcoming SlideShare
Loading in...5
×
 

Testing Fundamentals

on

  • 11,385 views

This PPT contains software Testing Fundamentals

This PPT contains software Testing Fundamentals

Statistics

Views

Total Views
11,385
Views on SlideShare
11,341
Embed Views
44

Actions

Likes
14
Downloads
0
Comments
4

4 Embeds 44

http://www.slideshare.net 23
http://mtweblife.blogspot.com 15
http://mtweblife.blogspot.in 5
https://4learning.edisonohio.edu 1

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

14 of 4 Post a comment

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • downloading is disenabled by the author. But we can not view it also . Slideshare team , why not remove this from your list?
    Are you sure you want to
    Your message goes here
    Processing…
  • how to download
    Are you sure you want to
    Your message goes here
    Processing…
  • sdg
    Are you sure you want to
    Your message goes here
    Processing…
  • how to download
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Testing Fundamentals Testing Fundamentals Presentation Transcript

  • Learn Software Testing 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:
        • a > 1, b == 0
        • a > 1, b != 0
        • a <= 1, b == 0
        • a <= 1, b != 0
        • a == 2, c > 1
        • a == 2, c <= 1
        • a != 2, c > 1
        • a != 2, c <= 1
    • These cases come directly from truth tables:
    false false true false false true true true b == 0 a > 1 false false true false false true true true c > 1 a == 2
    • These eight cases can be covered by the following four cases:
        • a == 2, b == 0, c == 2
          • Covers cases 1 and 5
        • a == 2, b == 1, c == 1
          • Covers cases 2 and 6
        • a == 1, b == 0, c == 2
          • Covers cases 3 and 7
        • a == 1, b == 1, c == 1
          • Covers cases 4 and 8
    Multiple Condition Coverage (cont.)
    • a > 1, b == 0
    • a > 1, b != 0
    • a <= 1, b == 0
    • a <= 1, b != 0
    • a == 2, c > 1
    • a == 2, c <= 1
    • a != 2, c > 1
    • a != 2, c <= 1
    • Notice that we still haven’t executed all paths through the segment:
        • a == 2, b == 0, c == 2
          • Executes statement 1 and executes statement 2
        • a == 2, b == 1, c == 1
          • Skips statement 1 and executes statement 2
        • a == 1, b == 0, c == 2
          • Skips statement 1 and executes statement 2
        • 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:
        • Analyze preconditions and postconditions to make a list of the external conditions, both input and output.
        • Divide each of these external conditions into valid equivalence classes.
        • Use boundary value analysis to identify test cases for these equivalence classes. A test case may cover more that one equivalence class.
        • Use error guessing to add other classes and test cases.
        • For each test case, identify the expected output.
        • 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
    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 () { } }
    • 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(&quot;testInitialState:&quot;);
      • System.out.println(&quot;Initial light: &quot; + 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(&quot;testChange:&quot;);
      • System.out.println(&quot;Starting light: &quot; + signal.light());
      • signal.change();
      • System.out.println(&quot;After 1 change: &quot; + signal.light());
      • signal.change();
      • System.out.println(&quot;After 2 changes: &quot; + signal.light());
      • signal.change();
      • System.out.println(&quot;After 3 changes: &quot; + 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
  • 1210554 Egyptian Multiplication 2468 490 4936 4936 245 9872 122 19744 19744 61 631808 631808 1 315904 315904 3 157952 157952 7 78976 78976 15 39488 30 1234 1234 981 Remainder “ Doubled” Multiplier “ Halved” Multiplicand
    • 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 .
    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.
    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.)
    • The construction would proceed as follows:
    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.)
    • 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.)
    • 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  :
    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.)