750 likes | 1.05k Views
Automatic Unit Tests Generation Tools. SEPL Seminar Benny Pasternak December 2007. Agenda. Brief Introduction Automatic Generation Tools JCrasher Symstra GenUTest Summary. Definition. A unit is the smallest testable part of an application
E N D
Automatic Unit Tests Generation Tools SEPL Seminar Benny Pasternak December 2007
Agenda • Brief Introduction • Automatic Generation Tools • JCrasher • Symstra • GenUTest • Summary
Definition • A unit is the smallest testable part of an application • In the object oriented paradigm it is a class • A unit test consists of a fixed sequence of method invocations with fixed arguments • Unit test explores a particular aspect of the behavior of the Class Under Test (CUT)
Yet Another Calculator Example public class Calculator { public int add(int a, int b) { return a + b; } public int subtract(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) { return a / b; } }
Small JUnit Test public class CalculatorTest { @Test public void add3and5Equals8() { Calculator calc = new Calculator(); int result = calc.add(4, 6); assertEquals(result, 10); } @Test(expected= java.lang.ArithmeticException.class) public void divideByZeroThrowsException() { Calculator calc = new Calculator(); int result = calc.divide(3,0); } @Test public void subtract5from6Equals2() { Calculator calc = new Calculator(); int result = calc.subtract(6, 5); assertEquals(result, 2); } }
Effective Unit Tests • Effective unit test should be: • Comprehensive – unit test should exercise all class methods and achieve high code coverage rate • Test in Isolation – CUT dependent objects (e.g., Logger, Serializer) should not be tested
StackInt push(int) int pop() int top() bool empty() reverse() Comprehensiveness Unit Test test2() test1() . . testn()
StackInt Unit Test push(int) int pop() int top() bool empty() reverse() test1() test2() . . testn() Isolation MockLogger Logger MockLinkedList LinkedList MockSerializer Serializer
Benefits • Enables the immediate detection of bugs introduced into a unit whenever code changes occur Unit tests provide a safety net of regression tests and validation tests which encourage developers to refactor existing code. • Elimination of uncertainty in units Simplifies integration • Provides a sort of living documentation of the system
However… • Writing effective unit tests is a hard and tedious task… • Lots of unit tests need to be written (unit tests code may be larger than project code) Provide tools to assist developers
Tool Classification • Frameworks – JUnit, NUnit • Mock Frameworks – EasyMock, jMock • Generation – Automatic generation of unit tests • Selection – Selecting a small set of unit tests from a large one • Prioritization – Deciding what the “best order” to run the tests
Unit Test Generation • Test Generation – generate a sequence of CUT method-calls • Test Assertion – Provide an assertion to determine if the test result is correct
Test Generation Techniques • Random Execution • random sequences of method calls with random values • Symbolic Execution • method sequences with symbolic arguments • builds constraints on arguments • produces actual values by solving constraints • Capture & Replay • capture real sequences seen in actual program runs or test runs
Test Assertion Generation Techniques • Manual (Design by Contract) • Uncaught Exceptions • Classifies a test as potentially faulty if it throws an unexpected exception • Operation Model • Infer an operational model from manual tests • Properties: objects invariants, method pre/post conditions • Properties violation potentially faulty • Capture & Replay • Compare test results/state changes to the ones captured in actual program runs and classify deviations as possible errors.
Generation Tool Map Selection Generation Prioritization Operational Model Uncaught Exceptions JCrasher Jartege Random Execution Eclat JTest Rostra Symstra Symbolic Execution PathFinder Symclat Test Factoring Capture & Replay CR Tool SCRAPE GenuTest Substra
Tools Presented Today • JCrasher (Random & Uncaught Exceptions) • Symstra (Symbolic & User provided specifications) • GenUTest (Capture & Replay)
JCrasher – An Automatic Robustness Tester for Java (2003) Christoph Csallner Yannis Smaragdakis Available at http://www-tatic.cc.gatech.edu/grads/c/csallnch/jcrasher/
Goal • Robustness quality goal – “A class should not crash with an unexpected runtime exception, regardless of the parameters provided.” • Do not assume anything about domain • Robustness goal applies to all classes • Function to determine class under test robustness: expected exception type pass unexpected exception type fail
Parameter Space • Huge parameter space. • Example: m(int,int) has 2^64 param combinations • Covering all parameters combination is impossible • May not need all combinations to cover all control paths that throw an exception • Pick a random sample • Control flow analysis on byte code could derive parameter equivalence class
Type Inference Rules • Search class under test for inference rules • Transitively search referenced types • Inference Rules • Method Y.m(P1,P2,.., Pn) returns X: X Y, P1, P2, … , Pn • Sub-type Y {extend | implements } X: X Y • Constants: int -1, 0 ,1 • Add each discovered inference rule to mapping: X inference rules returning X
Exception Filtering • JCrasher runtime catches all exceptions • Example generated test case: Public void test1() throws Throwable { try { /* test case */ } catch (Exception e) { dispatchException(e); // JCrasher runtime } • Uses heuristics to decide whether the exception is a • Bug of the class pass exception on to JUnit • Expected exception suppress exception
Symstra: Framework for Generating Unit Tests using Symbolic Execution (2005) Tao Xie Darko Wolfram David Marinov Schulte Notkin
Binary Search Tree Example public class BST implements Set { Node root; int size; static class Node { int value; Node left; Node right; } public void insert (int value) { … } public void remove (int value) { … } public bool contains (int value) { … } public int size () { … } }
Other Test Generation Approaches • Straight forward – generate all possible sequences of calls to methods under test • Cleary this approach generates too many and redundant sequences BST t1 = new Bst(); BST t2 = new Bst(); t1.size(); t2.size(); t2.size();
Other Test Generation Approaches • Concrete-state exploration approach • Assume a given set of method calls arguments • Explore new receiver-object states with method calls (BFS manner)
1st Iteration remove(1) remove(2) remove(3) insert(3) insert(2) insert(1) 2 1 3 Exploring Concrete States • Method arguments: insert(1), insert(2), insert(3), remove(1), remove(2), remove(3) new BST()
Exploring Concrete States • Method arguments: insert(1), insert(2), insert(3), remove(1), remove(2), remove(3) new BST() 2nd Iteration remove(1) remove(1) remove(2) remove(3) insert(3) insert(2) insert(1) remove(2) 2 1 3 remove(3) insert(2) insert(3) 1 1 2 3
Generating Tests from Exploration • Collect method sequences along the shortest path new BST() 2nd Iteration remove(1) remove(1) remove(2) remove(3) insert(3) insert(2) insert(1) remove(2) 2 1 3 remove(3) insert(2) insert(3) BST t = new BST(); t.insert(1); t.insert(3); 1 1 2 3
Exploring Concrete States Issues • Not solved state explosion problem • Need at least N different insert arguments to reach a BST with size N • experiments shows memory runs out when N = 7 • Requires given set of relevant arguments • in our case insert(1), insert(2), remove(1), …
Concrete States Symbolic States new BST() new BST() remove(1) remove(1) remove(2) remove(3) insert(x1) insert(3) insert(2) insert(1) remove(2) 2 1 3 x1 remove(3) insert(2) insert(3) insert(x2) X1<x2 1 1 x1 2 3 x2
Symbolic Execution • Execute a method on symbolic input values • Inputs: insert(SymbolicInt x) • Explore paths of the method • Build a path condition for each path • Conjunct conditionals or their negations • Produce symbolic states (<heap, path condition>) • For example X1<x2 x1 x2
Exploring Symbolic States public void insert(SymbolicInt x) { if (root == null) { root = new Node(x); } else { Node t = root; while (true) { if (t.value < x) { // explore rigtht subtree } else if (t.value > x) { // explore left subtree } else return; } } } size++; } new BST() S1 insert(x1) S2 x1 insert(x2) S4 S5 S3 X1= x2 X1<x2 X1>x2 x1 x1 x1 x2 x2
Generating Tests from Exploration • Collect method sequences along the shortest path • Generate concrete arguments by using a constraint solver new BST() S1 insert(x1) BST t = new BST(); t.insert(x1); t.insert(x2); S2 x1 insert(x2) S4 S3 X1<x2 X1>x2 x1 x1 X1>x2 x2 x2 BST t = new BST(); t.insert(-1000000); t.insert(-999999);
More Issues • Symstra uses specifications (pre, post, invariants) written in JML. These are transformed to run-time assertions • Limitations: • can not precisely handle array indexes • currently supports primitive arguments • can generate non-primitive arguments as sequence of method calls. These eventually boil down to methods with primitive arguments
GenUTest: A Unit Test and Mock Aspect Generation Tool Benny Pasternak Shmuel Tyszberowicz Amiram Yehudai
Motivation • Writing effective unit tests is a hard and tedious process • At maintenance phase, writing tests from scratch is not considered cost effective • Corollary: Maintenance remains a difficult process • Goal: Automatically generate unit tests for projects in maintenance phase
Example • Developers are asked to create effective unit tests for an existing software project • StackInt is an implementation of integers’ stack, with the operations: • Push • Pop • Top • Empty • Reverse • Goal is to test StackInt effectively
new() stack :IntStack lst :LinkedList new() push(2) addFirst(2) push(3) addFirst(3) reverse() pop() removeFirst() 2 2 Obtaining Test Cases From Existing Tests • System/Module test that exercises IntStack as follows: • Test can be used to obtain test cases for unit tests …
GenUTest • Captures and records execution of IntStack during module/system tests in order to obtain test cases • Recorded events are used to generate unit tests for IntStack • Unit tests assist developers in the testing process
Example – Generated Unit Test new() Unit Test Code stack :IntStack lst :LinkedList new() 1 @Test public void testpop1() 2 { 3 // test execution statements 4 IntStack IntStack_2 = new IntStack(); // #1 5 IntStack_2.push(2); // #2 6 IntStack_2.push(3); // #3 7 IntStack_2.reverse(); // #4 8 int intRetVal6 = IntStack_2.pop(); // #5 9 10 // test assertion statements 11 assertEquals(intRetVal6,2); 11 } push(2) addFirst(2) push(3) addFirst(3) reverse() … pop() removeFirst() 2 2
StackInt Unit Test push(int) int pop() int top() bool empty() reverse() test1() test2() . . testn() Example Logger LinkedList Serializer
Around Advice print(“Before”); execute join point print(“After”); Pointcut 1 Pointcut 2 print(“Before”); print(“After”); Aspect Oriented Programming • Join points • object instantiation • method-calls • field setter/getter class StackInt { void reverse() { LinkedList newlst = new LinkedList(); int size = lst.size(); for (int i = 0; i < size; i++) { int elem = lst.get(i); newlst.addFirst(elem); } lst = newlst; } int pop() { int elem = lst.removeFirst(); return elem; } }
AOP – Quick Summary • Join points – well defined execution points in the control flow of the program (object instantiation, method-calls, field member access) • Pointcut – expression that specifies a set of join points • Advices – code specified to execute before, after, or aroundpointcuts • Aspects – The equivalent to class. Holds pointcut declarations and advices
Mock Advice mock object behavior code Pointcut 1 StackInt Unit Test push(int) int pop() int top() bool empty() reverse() test1() test2() . . testn() Pointcut 2 Mock Advice mock object behavior code Example Logger LinkedList Serializer