Rigorous Software Development CSCIGA 3033 011 Instructor Thomas

  • Slides: 36
Download presentation
Rigorous Software Development CSCI-GA 3033 -011 Instructor: Thomas Wies Spring 2012 Lecture 7

Rigorous Software Development CSCI-GA 3033 -011 Instructor: Thomas Wies Spring 2012 Lecture 7

Programming Project: Minesweeper

Programming Project: Minesweeper

Project Goal • Implement a minesweeper game together with a perfect minesweeper solver –

Project Goal • Implement a minesweeper game together with a perfect minesweeper solver – perfect solver: never guess the next move unless the current board configuration does not allow to deduce whether some unrevealed cell contains a mine or not. – in particular: a perfect solver can decide whether a given board configuration can be solved without guessing.

Project Outline • Phase 1: Alloy Model • Phase 2: Java Implementation

Project Outline • Phase 1: Alloy Model • Phase 2: Java Implementation

Project Phase 1: Alloy model • Objectives: – develop abstract minesweeper model – simulate

Project Phase 1: Alloy model • Objectives: – develop abstract minesweeper model – simulate board configurations and plays of the game – specify key invariants of board configurations – identify pre and post-conditions of operations on boards – implement simple minesweeper solver • Deadline: Tuesday, April 10, 2012

Project Phase 2: Java Implementation • Objectives: – Transfer Alloy model to JML specifications

Project Phase 2: Java Implementation • Objectives: – Transfer Alloy model to JML specifications – Implement • simple parser for board configurations • game • solver – Check JML specifications using JML tools • Deadline: Tuesday, May 1, 2012 • Competition: – submitted solvers will compete against each other

Alloy Model • Abstract Minesweeper: game is played on an undirected graph rather than

Alloy Model • Abstract Minesweeper: game is played on an undirected graph rather than a grid-like board – nodes represent cells of the board – edges indicate which cells are neighbors 2

Invariants of Board Configurations • Marked cells are not revealed. • The number of

Invariants of Board Configurations • Marked cells are not revealed. • The number of marked cells is not larger than the number of mines. • If x is a revealed cell that has no neighboring mines, then all its neighbors are also revealed. • . . .

Solving Minesweeper Games • Solving minesweeper games is closely related to the Minesweeper Consistency

Solving Minesweeper Games • Solving minesweeper games is closely related to the Minesweeper Consistency Problem: – given a board configuration – does there exist an assignment of mines to unrevealed cells that is consistent with the conf. ?

Minesweeper Consistency: Example Is this configuration consistent? No!

Minesweeper Consistency: Example Is this configuration consistent? No!

Minesweeper Consistency: Example and this one? Yes!

Minesweeper Consistency: Example and this one? Yes!

Minesweeper Consistency • The Minesweeper Consistency Problem is NP-complete. • There is a simple

Minesweeper Consistency • The Minesweeper Consistency Problem is NP-complete. • There is a simple brute-force algorithm to solve minesweeper games. • If you want a solver that works fast in practice, you will have to do something more clever.

Programming Project • Phase 1: Alloy Model – Deadline: Tuesday, April 10, 2012 •

Programming Project • Phase 1: Alloy Model – Deadline: Tuesday, April 10, 2012 • Phase 2: Java Implementation – Deadline: Tuesday, May 1, 2012 More detailed information will be available on the course webpage.

Today’s Topic: Automated Test Case Generation

Today’s Topic: Automated Test Case Generation

How to Test Effectively? public class Factorial { /*@ requires n >= 0; @

How to Test Effectively? public class Factorial { /*@ requires n >= 0; @ ensures result > 0; @*/ public static int factorial (int n) { int result = n; while (--n > 0) result *= n; return result; } public static void main (String[] param) { int n = Integer. parse. Int(param[0]); int fact_n = factorial(n); System. out. println("n: " + n + ", n!: " + fact_n); } } Writing a main method for each test case does not scale.

How to Test Effectively? Faulty implementation of enqueue on binary heap: public void enqueue(Comparable

How to Test Effectively? Faulty implementation of enqueue on binary heap: public void enqueue(Comparable o) { if (num. Elems >= elems. length) grow(); int pos = num. Elems++; int parent = pos / 2; while (pos > 0 && elems[parent]. compare. To(o) > 0) { elems[pos] = elems[parent]; pos = parent; parent = pos / 2; } elems[pos] = o; } Writing all test cases manually does not scale.

Automated Testing • Unit Testing: write code to automatically test your code. • A

Automated Testing • Unit Testing: write code to automatically test your code. • A unit test is a test suite for a unit (class/module) of a program and consists of – setup code to initialize the tested class; – tear down code to clean up after testing; – test cases that call methods of the tested class with appropriate inputs and then check the results. • Once test suites are written, they are easy to run repeatedly (regression testing).

Unit Testing in Java: JUnit • A popular framework for unit testing in Java

Unit Testing in Java: JUnit • A popular framework for unit testing in Java – Frameworks are libraries with gaps – Programmer writes classes following particular conventions to fill in the gaps – Result is the complete product • JUnit automates – the execution and analysis of unit tests; – generation of tests cases from parameterized test oracles and user-provided test data.

JUnit Example import static org. junit. Assert. *; import org. junit. *; . .

JUnit Example import static org. junit. Assert. *; import org. junit. *; . . . public class Priority. Queue. Test { private Priority. Queue pq; @Before public void set. Up () { pq = new Heap(); } @After public void tear. Down () { pa = null; } @Test public void enqueue. Test () { Integer value = new Integer(5); pq. enqueue(value); assert. Equals(pq. remove. First, value); }. . . }

Drawbacks of JUnit • Low degree of automation – Programmer still needs to write

Drawbacks of JUnit • Low degree of automation – Programmer still needs to write all the test cases • Redundant specification – Duplication between checks in test oracles and formal specification (e. g. provided as JML annotations)

JMLUnit: Unit Testing for JMLUnit is a unit testing framework for JML built on

JMLUnit: Unit Testing for JMLUnit is a unit testing framework for JML built on top of JUnit User: • writes specifications • supplies test data of each type JMLUnit automatically: • constructs test cases from test data • assembles test cases into test suites • executes test suites • decides success or failure • reports results

Test Cases and Suites • A test case (o, x) consists of: – a

Test Cases and Suites • A test case (o, x) consists of: – a non-null receiver object o – a sequence x of argument objects • A test suite for method m is a set of test cases with: – receiver of m’s receiver type – arguments of m’s argument types

Test Suites are Cross Products • For method enqueue: { (pq, v) | pq

Test Suites are Cross Products • For method enqueue: { (pq, v) | pq ∈ Priority. Queue. Test. Data, v ∈ Integer. Test. Data } • Default is to use all data for all methods – Filtered automatically by preconditions – Users can filter manually if desired • Factory method allows user control of adding test cases to test suite.

Errors and Meaningless Test Cases When testing method m: receiver. m(arg 1, . .

Errors and Meaningless Test Cases When testing method m: receiver. m(arg 1, . . . ) check m’s precondition {. . . x. f(. . . ); } entry precondition violation internal precondition violation check f’s precondition {. . . } check f’s postcondition check m’s postcondition other violation Entry precondition violation ) test case rejected Internal or other violation ) error reported

Supplying Test Data • Programmer supplies data in form of strategies • A strategy

Supplying Test Data • Programmer supplies data in form of strategies • A strategy for type T: – has method that returns iterator yielding T • Strategies allow reuse of test data • JMLUnit provides framework of built-in strategies – Strategies for built-in types – Allow for easy extension, composition, filtering, etc.

Strategies for Test Data • Standard strategies: – Immutable: iterate over array of values;

Strategies for Test Data • Standard strategies: – Immutable: iterate over array of values; – Cloneable: iterate over array, clone each; – Other: create objects each time. • Cloning and creating from scratch can prevent unwanted interference between tests. • JMLUnit tries to guess appropriate strategy.

Example Strategies import org. jmlspecs. jmlunit. strategies. *; import junit. framework. *; public abstract

Example Strategies import org. jmlspecs. jmlunit. strategies. *; import junit. framework. *; public abstract class Heap_JML_Test. Data extends Test. Case { public Int. Iterator v. Comp. Iter(String method. Name, int arg. Num) { return v. Comparable. Strategy. Comparable. Iterator(); } private Strategy. Type v. Comparable. Strategy = new Immutable. Object. Abstract. Strategy() { protected Object[] add. Data() { return new Integer[] {10, -22, 55, 3000}; } }; . . .

Example Strategies. . . public Indefinite. Iterator v. Heap. Iter (String method. Name, int

Example Strategies. . . public Indefinite. Iterator v. Heap. Iter (String method. Name, int arg. Num) { return v. Point. Strategy. iterator(); } private Strategy. Type v. Heap. Strategy = new New. Object. Abstract. Strategy() { protected Object make(int n) { switch (n) { case 0: return new Heap(); case 1: return new Heap(new Integer {1, 2, 3}); default : break ; } throw new No. Such. Element. Exception(); } }; }

Using JMLUnit • JML-compile the class to be tested jmlc Factorial. java • generate

Using JMLUnit • JML-compile the class to be tested jmlc Factorial. java • generate the test suite and test data templates jmlunit Factorial. java • supply the test data $EDITOR Factorial_JML_Test. Data. java • compile the test suite export CLASSPATH=. : $JMLDIR/bin/jmlruntime. jar: $JMLDIR/specs javac Factorial_JML_Test*. java • execute the test suite jmlrac Factorial_JML_Test

Drawbacks of JMLUnit • High memory consumption: – JUnit test suite with all test

Drawbacks of JMLUnit • High memory consumption: – JUnit test suite with all test cases is constructed in memory before it is executed. • Limited degree of automation: – only test data for primitive types is generated automatically • Limited degree of granularity: – fine-grained filtering of test data for individual methods is difficult

Alternatives to JMLUnit • JMLUnit. NG – similar feature set as JMLUnit – lazy

Alternatives to JMLUnit • JMLUnit. NG – similar feature set as JMLUnit – lazy generation of test suites and lazy result aggregation (better memory footprint) – improved filtering of test data – not based on JUnit • Korat, Test. Era, UDITA – automated generation of test data for complex data types

Automated Test Case Generation with Korat • Provides test case generation for complex data

Automated Test Case Generation with Korat • Provides test case generation for complex data types. • Supports checking of JML specifications. • User provides for each complex data type – a Java predicate capturing the representation invariant of the data type; – a finitization of the data type. • Korat generates test cases for all instances that satisfy both the finitization constraints and the representation predicate.

Example: Binary Trees import java. util. *; class Binary. Tree { private Node root;

Example: Binary Trees import java. util. *; class Binary. Tree { private Node root; private int size; static class Node { private Node left; private Node right; }. . . }

Representation Predicate for Binary. Tree public boolean rep. OK() { if (root == null)

Representation Predicate for Binary. Tree public boolean rep. OK() { if (root == null) return size == 0; Set visited = new Hash. Set(); visited. add(root); Linked. List work. List = new Linked. List(); work. List. add(root); while (!work. List. is. Empty()) { Node current = (Node) work. List. remove. First(); if (current. left != null) { if (!visited. add(current. left)) return false; worklist. add(current. left); } if (current. right!= null) {. . . } } return visited. size () == size; }

Finitization for Binary. Tree public static Finitization fin. Binary. Tree (int NUM_Node) { IFinitization

Finitization for Binary. Tree public static Finitization fin. Binary. Tree (int NUM_Node) { IFinitization f = new Finitization(Binary. Tree. class); IObj. Set nodes = f. create. Obj. Set(Node. class, NUM_Node, true); // #Node = NUM_Node f. set(“root”, nodes); // root in null + Node IInt. Set sizes = f. create. Int. Set(Num_Node); f. set(“size”, sizes); // size = NUM_Node f. set(“Node. left”, nodes); // Node. left in null + Node f. set(“Node. right”, nodes); // Node. right in null + Node return f; }

Finitization for Binary. Tree Instances generated for fin. Binary. Tree(3) right left left right

Finitization for Binary. Tree Instances generated for fin. Binary. Tree(3) right left left right