Upload
ishmael-hopkins
View
74
Download
0
Tags:
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
1
Parameterized Unit TestParameterized Unit Test
Parameterized Unit Testsserve as specificationscan be leveraged by (automatic) test input generatorsfit in development environment, evolve with the code
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
Parameterized Unit Test, written by handParameterized Unit Test, written by hand
Test input, generated by Pex
Test input, generated by Pex
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.
Design and specificationby example
Code coverage and regression testingconfidence in correctnesspreserving behavior
Short feedback loopunit tests exercise little codefailures are easy to debug
Documentation
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
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!
Parameterized Unit TestParameterized Unit Test
A parameterized unit test is a small program that takes some inputs and states assumptions and assertions.
9
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 !
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)
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
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
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
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
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.
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.
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 …
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;}
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!
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
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
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;…
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
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
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
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
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
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
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…
Add ImplicitNullCheck testRun PexHow many tests will be necessary?
Try it out with other instruction:Allocating new arrays,Accessing array indexes,Field dereference
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
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);}
Exploration driverExplicitImplicit, in Pex configurable through attributes, e.g.
PexExplorableFromConstructorAttribute
Result: Exploration of reachable states
Only within generally configured boundsUnder-approximation
34
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
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
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
• Revisit explicit ArrayList driver• TestEmissionFilter=PexTestEmissionFilter.All
38
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))]
DateTime.ParseMay throw FormatException
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
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
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
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
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
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
Extract SamplesOpen sample solutionShow AppendFormat codeShow AppendFormat testRun Pex
47
Problem: Without further work, parameterized mock objects might lead to spurious warnings.
48
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
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());}
TAP 2008
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
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
RandomTestRun PexWrite substitution for Random.NextRun Pex
54
(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
(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
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)
ExampleLook at test sourceRun pex from console
How to use pex.exeReport has additional information
Look atOnePathInvariantTest
58
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
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
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.
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
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
64
http://research.microsoft.com/Pex