64
1

Parameterized Unit Testing

Embed Size (px)

DESCRIPTION

Parameterized Unit Testing with Pex : Introduction Tao Xie Adapted from TAP 08 tutorial slides by Nikolai Tillmann, Peli de Halleux, Wolfram Schulte Microsoft Research , Redmond http://research.microsoft.com/Pex. Parameterized Unit Testing. Parameterized Unit Tests serve as specifications - PowerPoint PPT Presentation

Citation preview

Page 1: Parameterized Unit Testing

1

Page 2: Parameterized Unit Testing

Parameterized Unit TestParameterized Unit Test

Parameterized Unit Testsserve as specificationscan be leveraged by (automatic) test input generatorsfit in development environment, evolve with the code

Page 3: Parameterized Unit Testing

Traditional approach:• Writing many tests that cover the code,• making sure it does not crash.

3

Possible test case, written by HandPossible test case, written by Hand

Page 4: Parameterized Unit Testing

Parameterized Unit Test, written by handParameterized Unit Test, written by hand

Test input, generated by Pex

Test input, generated by Pex

Page 5: Parameterized Unit Testing

5

A unit test is a small program with assertions.

[TestMethod] public void Add(){ HashSet set = new HashSet(); set.Add(3); set.Add(14); Assert.AreEqual(set.Count, 2);}

Many developers write such unit tests by hand. This involvesdetermining a meaningful sequence of method calls,selecting exemplary argument values (the test inputs),stating assertions.

Page 6: Parameterized Unit Testing

Design and specificationby example

Code coverage and regression testingconfidence in correctnesspreserving behavior

Short feedback loopunit tests exercise little codefailures are easy to debug

Documentation

Page 7: Parameterized Unit Testing

Quality of unit testsDo they test what is important?

Amount of unit testsHow many tests to write?

New code with old testsEven if unit test

Hidden integration test

Page 8: Parameterized Unit Testing

Coverage: Are all parts of the program exercised?

statementsbasic blocksexplicit/implicit branches…

Assertions: Does the program do the right thing?

test oracle

Experience:Just high coverage or large number of assertions is no good quality indicator.Only both together are!

Page 9: Parameterized Unit Testing

Parameterized Unit TestParameterized Unit Test

A parameterized unit test is a small program that takes some inputs and states assumptions and assertions.

9

Page 10: Parameterized Unit Testing

PUTs separate two concerns:1)The specification of external behavior

(i.e., assertions)2) The selection of internal test inputs

(i.e., coverage)

In many cases, Pex can construct a small test suite with high coverage !

Page 11: Parameterized Unit Testing

A PUT can be read as a universally quantified, conditional axiom.

int name, int data.name ≠ null ⋀ data ≠ null ⇒ equals(

ReadResource(name,WriteResource(name,

data)),data)

Page 12: Parameterized Unit Testing

Pex is a test input generatorPex starts from parameterized unit testsGenerated tests are emitted as traditional unit tests

Pex analyzes execution pathsAnalysis at the level of the .NET instructions (MSIL)Incremental analysis that discovers feasible execution pathsThorem prover/constraint solver Z3 determines satisfying assignments for constraint systems representing execution paths

Page 13: Parameterized Unit Testing

Hand-written

13

// FooTest.cs[TestClass, PexClass]partial class FooTest{ [PexMethod] void Test(Foo foo) {…}}

// FooTest.Test.cspartial class FooTest{ [TestMethod] void Test_1() { this.Test(new Foo(1)); }

[TestMethod] void Test_1() { this.Test(new Foo(2)); } …}

Pex

• User writes parameterized tests• Lives inside a test class • Generated unit tests

• Pex not required for re-execution• xUnit unit tests

xUnit AttributesxUnit Attributes

Parameterized Unit Test

Parameterized Unit Test

Partial ClassPartial Class

Generated

http://msdn.microsoft.com/en-us/library/wa80x488(VS.80).aspx

Page 14: Parameterized Unit Testing

Static analysisVerify properties for all possible executionsConservative ("over approximation")Spurious warnings ("false positives")

Dynamic symbolic execution (Pex’ test input generation technique)

Verify properties for many execution pathsFinds most errors within configured boundsNo spurious warnings (well, almost, see later)

Dynamic analysisTestingMay miss errors ("under approximation")No spurious warnings

Page 15: Parameterized Unit Testing

API contracts (Eiffel, Spec#, JML, …)At the level of individual actionsGoal: guarantee robustnessProblem: abstraction is hard, e.g. how to describe the protocol

Parameterized unit testsScenarios specify functional correctnessConfidence in robustness by automated test coverage

Unit testsScenarios, spanning multiple actionsGoal: functional correctnessProblem: missing implementation coverage

Page 16: Parameterized Unit Testing

Starting from parameterized unit tests as specification, we can state the testing problem as follows.

Given a sequential program P with statements S, compute a set of program inputs I such that for all reachable statements s in S there exists an input i in I such that P(i) executes s.

Page 17: Parameterized Unit Testing

Remarks:By sequential we mean that the program is single-threaded.We consider failing an assertion, or violating an implicit contract of the execution engine (e.g. NullReferenceException when null is dereferenced as special statements.Since reachability is not decidable in general, we aim for a good approximation in practice, e.g. high coverage of the statements/branches/… of the program.

Page 18: Parameterized Unit Testing

Exploration of all feasible execution paths:Start execution from initial state with symbolic values as inputOperations yieldterms over symbolic valuesAt conditional branch, fork execution for each feasible evaluation of the conditionFor each path, we get an accumulated path condition

For each path, check if path condition is feasible(using automated constraint solver / theorem prover)

ptrue false

C’=C⋀⌝pC’=C⋀p

C

if (p) then … else …

Page 19: Parameterized Unit Testing

int Max(int a, int b, int c, int d) { return Max(Max(a, b), Max(c, d));}

int Max(int x, int y) { if (x > y) return x; else return y;}

Page 20: Parameterized Unit Testing

Exploration of all feasible execution paths:Start execution from initial state with symbolic values as inputOperations yieldterms over symbolic valuesAt conditional branch, fork execution for each feasible evaluation of the conditionFor each path, we get an accumulated path condition

For each path, check if path condition is feasible(using automated constraint solver / theorem prover)

ptrue false

C’=C⋀⌝pC’=C⋀p

C

if (p) then … else …

Constraint solver cannot Constraint solver cannot reason about certain reason about certain

operationsoperations(e.g., floating point (e.g., floating point

arithmetic, interactions with arithmetic, interactions with the environment)the environment)

Constraint solver cannot Constraint solver cannot reason about certain reason about certain

operationsoperations(e.g., floating point (e.g., floating point

arithmetic, interactions with arithmetic, interactions with the environment)the environment)

Execution of programsExecution of programsthat interact with that interact with

stateful environment stateful environment cannot be forked!cannot be forked!

Execution of programsExecution of programsthat interact with that interact with

stateful environment stateful environment cannot be forked!cannot be forked!

Page 21: Parameterized Unit Testing

Dynamic symbolic execution combines static and dynamic analysis:Execute program multiple timeswith different inputs

build abstract representation of execution path on the sideplug in concrete results of operations which cannot reasoned about symbolically

Use constraint solver to obtain new inputssolve constraint system that represents an execution path not seen before

Page 22: Parameterized Unit Testing

TestTestInputsInputsTestTest

InputsInputs

Constraint Constraint SystemSystem

Constraint Constraint SystemSystem Execution Execution

PathPathExecution Execution

PathPath

KnownKnownPathsPathsKnownKnownPathsPaths

Run Test and Monitor

RecordPath Condition

Choose an Uncovered Path

Solve

Result: small test suite, Result: small test suite, high code coveragehigh code coverage

Initially, choose Arbitrary

Finds only real bugsFinds only real bugsNo false warningsNo false warnings

Page 23: Parameterized Unit Testing

TestTestInputsInputsTestTest

InputsInputs

Constraint Constraint SystemSystem

Constraint Constraint SystemSystem Execution Execution

PathPathExecution Execution

PathPath

KnownKnownPathsPathsKnownKnownPathsPaths

Run Test and Monitor

RecordPath Condition

Choose an Uncovered Path

Solve

Result: small test suite, Result: small test suite, high code coveragehigh code coverage

Initially, choose Arbitrary

Finds only real bugsFinds only real bugsNo false warningsNo false warnings

a[0] = 0;a[1] = 0;a[2] = 0;a[3] = 0;…

a[0] = 0;a[1] = 0;a[2] = 0;a[3] = 0;…

Page 24: Parameterized Unit Testing

TestTestInputsInputsTestTest

InputsInputs

Constraint Constraint SystemSystem

Constraint Constraint SystemSystem Execution Execution

PathPathExecution Execution

PathPath

KnownKnownPathsPathsKnownKnownPathsPaths

Run Test and Monitor

RecordPath Condition

Choose an Uncovered Path

Solve

Result: small test suite, Result: small test suite, high code coveragehigh code coverage

Initially, choose Arbitrary

Finds only real bugsFinds only real bugsNo false warningsNo false warnings

Path Condition:… ⋀ magicNum != 0x95673948

Page 25: Parameterized Unit Testing

TestTestInputsInputsTestTest

InputsInputs

Constraint Constraint SystemSystem

Constraint Constraint SystemSystem Execution Execution

PathPathExecution Execution

PathPath

KnownKnownPathsPathsKnownKnownPathsPaths

Run Test and Monitor

RecordPath Condition

Choose an Uncovered Path

Solve

Result: small test suite, Result: small test suite, high code coveragehigh code coverage

Initially, choose Arbitrary

Finds only real bugsFinds only real bugsNo false warningsNo false warnings

… ⋀ magicNum != 0x95673948… ⋀ magicNum == 0x95673948

Page 26: Parameterized Unit Testing

TestTestInputsInputsTestTest

InputsInputs

Constraint Constraint SystemSystem

Constraint Constraint SystemSystem Execution Execution

PathPathExecution Execution

PathPath

KnownKnownPathsPathsKnownKnownPathsPaths

Run Test and Monitor

RecordPath Condition

Choose an Uncovered Path

Solve

Result: small test suite, Result: small test suite, high code coveragehigh code coverage

Finds only real bugsFinds only real bugsNo false warningsNo false warnings

a[0] = 206;a[1] = 202;a[2] = 239;a[3] = 190;

a[0] = 206;a[1] = 202;a[2] = 239;a[3] = 190;

Initially, choose Arbitrary

Page 27: Parameterized Unit Testing

TestTestInputsInputsTestTest

InputsInputs

Constraint Constraint SystemSystem

Constraint Constraint SystemSystem Execution Execution

PathPathExecution Execution

PathPath

KnownKnownPathsPathsKnownKnownPathsPaths

Run Test and Monitor

RecordPath Condition

Choose an Uncovered Path

Solve

Result: small test suite, Result: small test suite, high code coveragehigh code coverage

Initially, choose Arbitrary

Finds only real bugsFinds only real bugsNo false warningsNo false warnings

Page 28: Parameterized Unit Testing

Dynamic symbolic execution will systematically explore the conditions in the code which the constraint solver understands.And happily ignore everything else, e.g.

Calls to native codeDifficult constraints (e.g. precise semantics of floating point arithmetic)

Result: Under-approximation, which is appropriate for testing

Calls to external world

Unmanaged x86 code

Unsafe managed .NET code (with pointers)

Safe managed .NET code

Most programs are not self-containedIn fact, large parts of the .NET Base Class Library are not written in .NET

Page 29: Parameterized Unit Testing

void Complicated(int x, int y) { int Obfuscate (int y) { if (x == Obfuscate(y)) return (100+y)*567 % 2347; error(); } else return;}

Dynamic symbolic execution, starting from Complicated, runs the code twice:

1. Call Complicated() with arbitrary values, e.g. -312 for x, 513 for y Record branch condition “x != (100+y * 567) % 2347” error is not hitCompute values such that “x == (100+y * 567) % 2347” (using constraint solver)

2. Call Complicated() with computed values for x, y (e.g. x=(100+513 * 567) % 2347, y=513)error is hit; coverage goal is reached 29

Page 30: Parameterized Unit Testing

30

Pex treats all possible exceptional control flow changes like explicit branchesDeterministic exceptions through constraint solving:

NullReferenceExceptionIndexOutOfRangeExceptionOverflowExceptionDivisionByZeroException

Non-deterministic exceptions through exception injection (if enabled)

OutOfMemoryException, StackOverflowExceptionThreadAbortException…

Page 31: Parameterized Unit Testing

Add ImplicitNullCheck testRun PexHow many tests will be necessary?

Try it out with other instruction:Allocating new arrays,Accessing array indexes,Field dereference

Page 32: Parameterized Unit Testing

Problem:• Constraint solver determines

test inputs = initial state of test• Most classes hide their state (private fields)• State is initialized by constructor, and can

be mutated only by calling methods• What sequence of method calls reaches a

given target state?• There may be no such sequence• In general, undecidable

Two approaches:• (Guided) exploration of constructor/mutator

methods• Testing with class invariants

Page 33: Parameterized Unit Testing

Specification:

[PexMethod]public void ArrayListTest(ArrayList al, object o){ PexAssume.IsTrue(al != null); int len = al.Count; al.Add(o); PexAssert.IsTrue(al[len] == o);}

Page 34: Parameterized Unit Testing

Exploration driverExplicitImplicit, in Pex configurable through attributes, e.g.

PexExplorableFromConstructorAttribute

Result: Exploration of reachable states

Only within generally configured boundsUnder-approximation

34

Page 35: Parameterized Unit Testing

Write class invariant as boolean-valued parameterless method

Refers to private fieldsMust be placed in implementation code

Write special constructor for testing only

May be marked as "debug only"Constructor sets fields, assumes invariant

Result: Exploration of feasible statesMay include states that are not reachable

35

Page 36: Parameterized Unit Testing

public class ArrayList { private Object[] _items; private int _size, _version, _capacity; private bool Invariant() { return this._items != null && this._size >= 0 && this._items.Length >= this._size; }#if DEBUG public ArrayList(object[] items, int size, int capacity) { this._items = items; this._size = size; this._capacity = capacity; if (!this.Invariant()) throw new InvalidOperationException(); }#endif}

36

Page 37: Parameterized Unit Testing

void PexAssume.IsTrue(bool c) { if (!c) throw new PexAssumptionViolationException();}

void PexAssert.IsTrue(bool c) { if (!c) throw new PexAssertionViolationException();}

Executions with assumption violation exceptions are ignored, not reported as errors or test casesBoth assumption violation and assertion violation exceptions are ‚uncatchable‘

Special code instrumentation prevents catchingto avoid tainting of coverage data 37

Page 38: Parameterized Unit Testing

• Revisit explicit ArrayList driver• TestEmissionFilter=PexTestEmissionFilter.All

38

Page 39: Parameterized Unit Testing

If the test does not throw an exception, it succeeds.If the test throws an exception,

(assumption violations are filtered out),assertion violations are failures,for all other exception, it depends on further annotations.

AnnotationsShort form of common try-catch-assert test code[PexAllowedException(typeof(T))][PexExpectedException(typeof(T))]

Page 40: Parameterized Unit Testing

DateTime.ParseMay throw FormatException

Page 41: Parameterized Unit Testing

Loops and recursion give rise to potentially infinite number of execution pathsIn Pex: Configurable exploration bounds

TimeOutMaxBranchesMaxCallsMaxConditions

Number of conditions that depend on test inputs

MaxRunsConstraintSolverTimeOutConstraintSolverMemoryLimit

Page 42: Parameterized Unit Testing

Code we don’t want to testWe usually don't want to re-test code that we use and trust, e.g. already tested libraries

Code not accessible to white-box test generation

No symbolic information about uninstrumented codeNo symbolic information about code outside of known execution machine (.Net for Pex)

42

Page 43: Parameterized Unit Testing

Unit test: while it is debatable what a ‘unit’ is, a ‘unit’ should be small.Integration test: exercises large portions of a system.

Observation: Integration tests are often “sold” as unit tests

White-box test generation does not scale well to integration test scenarios.Possible solution: Introduce abstraction layers, and mock components not under test

Page 44: Parameterized Unit Testing

AppendFormat(null, “{0} {1}!”, “Hello”, “World”); “Hello World!”

.Net Implementation:

public StringBuilder AppendFormat( IFormatProvider provider, char[] chars, params object[] args) {

if (chars == null || args == null) throw new ArgumentNullException(…); int pos = 0; int len = chars.Length; char ch = '\x0'; ICustomFormatter cf = null; if (provider != null) cf = (ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter)); …

44

Page 45: Parameterized Unit Testing

Introduce a mock class which implements the interface. Write assertions over expected inputs, provide concrete outputs

public class MFormatProvider : IFormatProvider {

public object GetFormat(Type formatType) { Assert.IsTrue(formatType != null); return new MCustomFormatter(); }}

Problems:Costly to write detailed behavior by exampleHow many and which mock objects do we need to write?

45

Page 46: Parameterized Unit Testing

Introduce a mock class which implements the interface. Let an oracle provide the behavior of the mock methods.

public class MFormatProvider : IFormatProvider {

public object GetFormat(Type formatType) { … object o = call.ChooseResult<object>();

return o; }}

Result: Relevant result values can be generated by white-box test input generation tool, just as other test inputs can be generated! 46

Page 47: Parameterized Unit Testing

Extract SamplesOpen sample solutionShow AppendFormat codeShow AppendFormat testRun Pex

47

Page 48: Parameterized Unit Testing

Problem: Without further work, parameterized mock objects might lead to spurious warnings.

48

Page 49: Parameterized Unit Testing

Write assertions over arguments,And assumptions on results.

public class MFormatProvider : IFormatProvider {

public object GetFormat(Type formatType) {

Assert(formatType != null);

object o = call.ChooseResult<object>();

PexAssume.IsTrue(o is ICustomFormatter);

return o;

}

}

(Note: Assertions and assumptions are “reversed” when compared to parameterized unit tests.)

49

Page 50: Parameterized Unit Testing

50

API-level interface contracts (e.g. written in Spec#) can be leveraged to restrict behavior of mock objects.

Consider the following Spec# contract:

interface IFormatProvider {

object GetFormat(Type formatType)

requires formatType != null;

ensures result != null && formatType.IsAssignableFrom(result.GetType());}

Page 51: Parameterized Unit Testing

TAP 2008

Page 52: Parameterized Unit Testing

Go back to Max/4 exerciseAdd reference to Microsoft.Pex.GraphsRegister package:[assembly: Microsoft.Pex.Graphs.PexGraphsPackage]

Use package:[Microsoft.Pex.Graphs.SearchGraph]

Run PexView graphs in report

Page 53: Parameterized Unit Testing

Motivation:Ideally, all environment interactions go through mockable abstraction layersHowever, this is sometimes not achieveable in practice

Examples:Program may (indirectly) use System.Environment.Exit, which terminates Pex' processConsole.Read blocks process

Solution:Rewriting program (at binary level) so that it will use 'friendly substitutions' for certain methods while exploring the program's behavior53

Page 54: Parameterized Unit Testing

RandomTestRun PexWrite substitution for Random.NextRun Pex

54

Page 55: Parameterized Unit Testing

(With Christoph Csallner and Yannis Smaragdakis)

Plug-in (using Pex’ extensibility hooks)Idea

Discover likely program invariantsBy monitoring and abstracting conditions evaluated during actual program runs

ImplementationInstead of collecting "global" path condition,trace locally derived conditions (per class, per method)Summarize conditions over all invocations

Page 56: Parameterized Unit Testing

(With Christoph Csallner and Yannis Smaragdakis)

Plug-in (using Pex’ extensibility hooks)Idea

Discover likely program invariantsBy monitoring and abstracting conditions evaluated during actual program runs

ImplementationInstead of collecting "global" path condition,trace locally derived conditions (per class, per method)Summarize conditions over all invocations

Page 57: Parameterized Unit Testing

For each method invocation during the test run:1. Local path condition and final state discovery

• Build terms representing values derived from method parameters

2. Class invariant derivation• Basically, conjunction of all local path conditions of that

class's methods

3. Pre- and post-condition computation• Simplify local path conditions assuming derived class

invariants• Pre-condition is disjunction of path conditions• Post-condition is conjunction of path-specific post-

conditions• Path-specific post-condition is implication of path condition to

final state assignment

(… and some heuristics to abstract loops)

Page 58: Parameterized Unit Testing

ExampleLook at test sourceRun pex from console

How to use pex.exeReport has additional information

Look atOnePathInvariantTest

58

Page 59: Parameterized Unit Testing

Problem: How to introduce abstraction layer to environment, and make system unit-testable in isolation?A solution (“dependency injection”, “inversion of control” pattern):

Introduce interface that describes all environment interactionsMake implementation of interface configurable

59

Page 60: Parameterized Unit Testing

Most credit card companies use a “check digit” encoding scheme

Check digit: Linear combination of digits modulo 10

Additionally, each credit card company has a distinct prefix and number length

Card Type Prefix Length

Mastercard 51-55 16

Visa 4 13, 16

Amex 34, 37 15

Diners Club 300-305, 36, 38 14

Page 61: Parameterized Unit Testing

Specification of “Luhn Algorithm” that performs check of linear combination of digits modulo 10,in plain English:

1. Double the value of alternate digits of the primary account number beginning with the second digit from the right (the first right--hand digit is the check digit.)

2. Add the individual digits comprising the products obtained in Step 1 to each of the unaffected digits in the original number.

3. The total obtained in Step 2 must be a number ending in zero (30, 40, 50, etc.) for the account number to be validated.

Page 62: Parameterized Unit Testing

Program model checkersJPF, Kiasan/KUnit (Java), XRT (.NET)

Combining random testing and constraint solving

DART (C), CUTE (C), EXE (C), jCUTE (Java),SAGE (X86)

Combining static analysis and constraint solving

Check ‘n’ Crash / DSD-Crasher (Java)

Commercial toolsAgitarOne, …

62

Page 63: Parameterized Unit Testing

Parameterized Unit Testing:Separation of concerns

Specification vs. Coverage

Pex is a Dynamic Symbolic Execution platform for .NET

Generates test inputs automatically Can digest real-world codeYou learned how to use Pex for Parameterized Unit Testing

AvailabilityReleased within Microsoft since 1/1/2007.Send emailhttp://research.microsoft.com/Pex/

More information (internals, case study): Talk on Friday 63

Page 64: Parameterized Unit Testing

64

http://research.microsoft.com/Pex