ICTSS’11 Tutorial Parameterized Unit Testing Nikolai Tillmann Microsoft Research Outline First part: 14:00 - 15:00pm Second part: 15:30 - 17:00pm Outline Unit Testing Isolation Parameterized Unit Testing Data Generation by Dynamic Symbolic Execution Patterns Limitations and other Details About the exercises… Interactive: http://pexforfun.com Demos: Pex for Visual Studio http://research.microsoft.com/pex Requires Windows, .NET 2.0 or 4.0, ideally Visual Studio 2008 / 2010 Unit Testing Quiz: Unit testing What is a unit test? Unit Testing A unit test is a small program with assertions Test a single (small) unit of code void AddAndCount() { var list = new List(); list.Add(3); Assert.AreEqual(1, list.Count); } Let’s play a game! The Code Under Test string ReadFooValue() { string[] lines = File.ReadAllLines(@"t:\myapp.ini"); foreach (var line in lines) { int index = line.IndexOf('='); string name = line.Substring(index); if (name == "Foo") { string value = line.Substring(index + 1); return value; } t:\myapp.ini } A=B return null; } Foo=C C=D Quiz: Coverage How much block coverage do we need? 1. 50% 2. 80% 3. 100% 4. Block coverage alone is not enough Quiz: Coverage How much block coverage do we need? 1. 50% 2. 80% 3. 100% 4. Block coverage alone is not enough Quiz: Coverage [TestMethod] void ExistingFoo() { File.WriteAllLines(@"t:\myapp.ini",new string[]{“Foo=b”}); Reader.ReadFooValue(); } Do we need more tests to get 100% cov.? [TestMethod] void MissingFoo() { File.WriteAllLines(@"t:\myapp.ini",new string[0]); Reader.ReadFooValue(); } Quiz: Assertions Why write Assertions (or not)? 1. Documentation 2. Double check your program 3. Please your manager 4. Prevent future bugs 5. Validate user inputs 6. Catch errors early Quiz: Assertions Why write Assertions (or not)? 1. Documentation 2. Double check your program 3. Please your manager 4. Prevent future bugs 5. Validate user inputs 6. Catch errors early Quiz: Assertions [TestMethod] void MissingFoo() { File.WriteAllLines(@"t:\myapp.ini",new string[]{“a=b”}); string value = Reader.ReadFooValue(); Assert.IsTrue(????); } Which Assertions should you write? 1. Assert.IsTrue(value == “b”); 2. Assert.IsTrue(value == null); 3. Assert.IsTrue(String.IsNullOrEmpty(value)) 4. Assert.IsTrue(false); 5. No assertions Quiz: Assertions [TestMethod] void MissingFoo() { File.WriteAllLines(@"t:\myapp.ini",new string[]{“a=b”}); string value = Reader.ReadFooValue(); Assert.IsTrue(????); } Which Assertions should you write? 1. Assert.IsTrue(value == “b”); 2. Assert.IsTrue(value == null); 3. Assert.IsTrue(String.IsNullOrEmpty(value)) 4. Assert.IsTrue(false); 5. No assertions Quiz: Coverage + Assertions What gives you confidence in the code? 1. High coverage, no assertions 2. Low coverage, many assertions 3. High coverage, many assertions 4. Low coverage, no assertions 5. I wrote it Quiz: Coverage + Assertions What gives you confidence in the code? 1. High coverage, few assertions 2. Low coverage, many assertions 3. High coverage, many assertions 4. Low coverage, no assertions 5. I wrote it The Code Under Test string ReadFooValue() { string[] lines = File.ReadAllLines(@"t:\myapp.ini"); foreach (var line in lines) { int index = line.IndexOf('='); string name = line.Substring(index); if (name == "Foo") { string value = line.Substring(index + 1); return value; } } return null; } Quiz: Isolation string ReadFooValue() { string[] lines = File.ReadAllLines(@"t:\myapp.ini"); ... } In the example, what are the external dependencies? 1. Network Share 2. Local Disk 3. No file system, all in memory Quiz: Isolation string ReadFooValue() { string[] lines = File.ReadAllLines(@"t:\myapp.ini"); ... } In the example, what are the external dependencies? 1. Network Share 2. Local Disk 3. No file system, all in memory Quiz: Isolation What is the problem with a Local Disk? 1. Mapping already exists 2. Cannot run tests concurrently 3. Disk full 4. Access rights Quiz: Isolation What is the problem with a Local Disk? 1. Mapping already exists 2. Cannot run tests concurrently 3. Disk full 4. Access rights Quiz: Isolation How do you mitigate the Local Disk issues? 1. Always run on the same machine, same 2. 3. 4. 5. hardware, same credentials, same time, same temperature, same solar system configuration Refactoring: use Streams Refactoring: introduce IFileSystem Refactoring: pass the lines as parameter Change implementation of File.ReadAllLines Quiz: Isolation How do you mitigate the Local Disk issues? 1. Always run on the same machine, same 2. 3. 4. 5. hardware, same credentials, same time, same temperature, same solar system configuration Refactoring: use Streams Refactoring: introduce IFileSystem Refactoring: pass the lines as parameter Change implementation of File.ReadAllLines Quiz: Definition of Unit Test What is a Unit Test? A Unit Test is a program that runs fast the code under test, without environment dependencies, with assertions. What is a Unit Test Suite? A set of Unit Tests which achieves high code coverage Isolation Dependency hell var lines = File.ReadAllLines(@"t:\v.ini"); How do you change File.ReadAllLines? 1. Override static method 2. Changing the .NET Framework source code (and recompiling it) 3. Rewrite application in JavaScript 4. Code instrumentation Replace any .NET method with a delegate var lines = File.ReadAllLines(@"t:\myapp.ini"); What if we could replace File.ReadAllLines? File.ReadAllLines = delegate(string fn) MFile.ReadAllLinesString = delegate(string fn) { return new string[0]; Moles }; Motivation for Moles Why another isolation framework? Specifically designed to enable Pex Simple, well-defined semantics ▪ “Replace any .NET method” Mole Types Code Generation // System.IO public static class File { public static string[] ReadAllLines(string fn); } // System.IO.Moles public class MFile { public static Func<string, string[]> ReadAllLinesString; } // delegate R Func<T, R>(T t); Injecting Detours at Runtime // System.IO public static class File { public static string[] ReadAllLines(string fn) { if (MFile.ReadAllLinesString != null) return MFile.ReadAllLines(fn); … original code } } Automatically injected at runtime Moles Demo Start from ReadFooValue problem Create test project Add moles for mscorlib Write test using moles Run test Debug test You can pick up project from ReadFooValueIsolation\ReadFooValueIsolation.sln Quiz: Moles Usage When should you use Moles (and not)? 1. 2. 3. 4. 5. 6. 7. 8. 9. Always use Moles to solve isolation issues With Moles, one does not need to use interfaces anymore Moles only should be used for 3rd party API, use interfaces for isolation in your APIs Moles can be used in production code Moles lets you get away with untestable APIs Moles make test cases more robust With Moles, you do not need integration tests anymore Moles make tests easier to understand Moles is for poor programmers, real programmers rely on interfaces Quiz: Moles Usage When should you use Moles (and not)? 1. 2. 3. 4. 5. 6. 7. 8. 9. Always use Moles to solve isolation issues With Moles, one does not need to use interfaces anymore Moles only should be used for 3rd party API, use interfaces for isolation in your APIs Moles can be used in production code Moles lets you get away with untestable APIs Moles make test cases more robust With Moles, you do not need integration tests anymore Moles make tests easier to understand Moles is for poor programmers, real programmers rely on interfaces Exercise (Optional / homework) Step-by-step tutorial: research.microsoft.com/documentation.aspx “Unit Testing with Microsoft Moles” “Unit Testing SharePoint Foundation with Microsoft Pex and Moles” What you learned so far The Definition of Unit Testing Unit Test Isolation 1. Identify External Dependencies 2. Replace each External API with your delegate Parameterized Unit Testing The Recipe of Unit Testing Three essential ingredients: Data Method Sequence Assertions void AddAndCount() { int item = 3; var list = new List(); list.Add(item); var count = list.Count; Assert.AreEqual(1, count); } Quiz: list.Add(???) list.Add(???); Which value matters? 1. 2. 3. 4. 5. 6. 7. 0 1 int.MaxValue, int.MinValue -1 1000 it does not matter I don’t know until I read the code Parameterized Unit Testing void AddAndCount(List list, int item) { var count = list.Count; list.Add(item); Assert.AreEqual(count + 1, forlist.Count); any list, } for any item, Parameterized Unit Test = … adding 1 item Unit Test with Parameters increments Count by 1 Separation of concerns Specification of behavior Data to achieve coverage Parameterized Unit Tests are Algebraic Specifications A Parameterized Unit Test can be read as a universally quantified, conditional axiom. void ReadWrite(string name, string data) { Assume.IsTrue(name != null && data != null); Write(name, data); var readData = Read(name); Assert.AreEqual(data, readData); } forall. string name, string data: name not_equal null and data not_equal null implies equals( ReadResource(name,WriteResource(name,data)), data) Parameterized Unit Testing is going mainstream Parameterized Unit Tests (PUTs) commonly supported by various test frameworks .NET: Supported by .NET test frameworks http://www.mbunit.com/ http://www.nunit.org/ … Java: Supported by JUnit 4.X http://www.junit.org/ Generating test inputs for PUTs supported by tools .NET: Supported by Microsoft Research Pex http://research.microsoft.com/Pex/ Java: Supported by Agitar AgitarOne http://www.agitar.com/ Problem We cannot execute parameterized unit tests without data. Where does the data come from? Random data generator Real customer data Ranges Some values hand-picked by dev/tester Data Generation by Dynamic Symbolic Execution Data Generation Challenge Goal: Given a program with a set of input parameters, automatically generate a set of input values that, upon execution, will exercise as many statements as possible How would you do it? 45 Dynamic Symbolic Execution Combines many ideas Combines ideas from symbolic execution to capture behavior symbolically model checking to perform systematic bounded state space exploration testing to monitor and check concrete behavior constraint solving to find inputs Dynamic Symbolic Execution The high-level algorithm • This choice decides search order • Search order decides how quick we can achieve high code coverage! Combines concrete and symbolic execution • Incomplete constraint-solver leads to under-approximation Algorithm: Set J := ∅ (J is set of already analyzed program inputs) Loop Choose program input i ∉ J (stop if no such i can be found) Output i Loop notCterminate if number Execute P(i); record pathdoes condition (in particular, C(i) holds) Set J := J ∪ C the set { i | C(i ) } ) of execution(viewing pathsCisasinfinite End loop (in the presence of loops/recursion) 47 Dynamic Symbolic Execution Example Choose next path Code to generate inputs for: void CoverMe(int[] a) { if (a == null) return; if (a.Length > 0) if (a[0] == 1234567890) throw new Exception("bug"); } F F a.Length>0 a==null T Solve Constraints to solve Execute&Monitor Data Observed constraints null a==null a!=null && !(a.Length>0) a!=null && a.Length>0 && a[0]!=1234567890 a!=null {} a!=null && a.Length>0 {0} a!=null && a.Length>0 && a[0]==1234567890 {123…} a!=null && a.Length>0 && a[0]==1234567890 T Done: There is no path left. a[0]==123… F T http://pex4fun.com/CoverMe Dynamic Symbolic Execution Exercises ArrayIndexLength Pex knows about all implicit, exceptionthrowing control-flow branches ArrayHeap Pex models the heap Assert, Assert123 Assertions connect code coverage and correctness http://pex4fun.com/DynamicSymbolicExecutionExercises Assertions vs. Coverage What about finding faults? void AssertIsTrue(bool condition) { if (!condition) throw new AssertionException(); } Assertions induce branches Pex tries to cover branches ▪ Pex tries to fail assertions -> finds bug! Dynamic Symbolic Execution with Pex in Visual Studio Create new project in Visual Studio. Insert CoverMe method below. Right-click on CoverMe method, and select “Run Pex”. Inspect results in table. Save tests, …, have fun! public static void CoverMe(int[] a) { if (a == null) return; if (a.Length > 0) if (a[0] == 1234567890) throw new Exception("bug"); } Dynamic Symbolic Execution with Pex in Visual Studio Dynamic Symbolic Execution with Pex in Visual Studio Generated Test Inputs are persisted as C# Unit Tests Exercise (Optional / homework) Step-by-step tutorial: research.microsoft.com/documentation.aspx “Exploring Code with Microsoft Pex” Motivation: Unit Testing Hell ResourceReader How to test this code? (Actual code from .NET base class libraries) Motivation: Unit Testing Hell ResourceReader Demo ResourceReader [PexClass, TestClass] [PexAllowedException(typeof(ArgumentNullException))] [PexAllowedException(typeof(ArgumentException))] [PexAllowedException(typeof(FormatException))] [PexAllowedException(typeof(BadImageFormatException))] [PexAllowedException(typeof(IOException))] [PexAllowedException(typeof(NotSupportedException))] public partial class ResourceReaderTest { [PexMethod] public unsafe void ReadEntries(byte[] data) { PexAssume.IsTrue(data != null); fixed (byte* p = data) using (var stream = new UnmanagedMemoryStream(p, data.Length)) { var reader = new ResourceReader(stream); foreach (var entry in reader) { /* reading entries */ } } } } Dynamic Symbolic Execution Many implementations Defined by execution environment / programming language, symbolic execution precision, and constraint solving Execution environment: C, Java, x86, .NET,… Precision: linear vs. non-linear arithmetic, “gods integers” vs. bitvectors, concrete heap vs. symbolic heap., floating-point values, etc. Solvers: lp_solve, CVCLite, STP, Disolver, Z3,… Examples of DSE implementations: DART (Bell Labs), and also CUTE “concolic execution” EXE/EGT/KLEE (Stanford) “constraint-based execution” Vigilante (Microsoft) to generate worm filters BitScope (CMU/Berkeley) for malware analysis Sage (Microsoft) for security testing of X86 code Yogi (Microsoft) to verify device drivers (integrated in SLAM) Pex (Microsoft) for parameterized unit testing of .NET code CREST, jCUTE, jFuzz, … Constraint Solving vs. Search Strategies Initially, choose Arbitrary Path-constraint solving is just hard. Test Inputs Constraint System Reachability is undecidable! (Loops) Execution Path Known Paths Symbolic State Representation Representation of symbolic values and state is similar to the ones used to build verification conditions in ESC/Java, Spec#, … Terms for Primitive types (integers, floats, …), constants, expressions Struct types by tuples Instance fields of classes by mutable ”mapping of references to values" Elements of arrays, memory accessed through unsafe pointers by mutable “mapping of integers to values" Efficiency by Many reduction rules, including reduction of ground terms to constants Sharing of syntactically equal sub-terms BDDs over if-then-else terms to represent logical operations Patricia Trees to represent AC1 operators (including parallel array updates) Constraint Solving SMT-Solver (“Satisfiability Modulo Theories”) Decides logical first order formulas with respect to theories SAT solver for Boolean structure Decision procedures for relevant theories: uninterpreted functions with equalities, linear integer arithmetic, bitvector arithmetic, arrays, tuples Model generation for satisfiable formulas Models used as test inputs Limitations No decision procedure for floating point arithmetic Pex uses Z3: http://research.microsoft.com/z3 Satisfiability Modulo Theories void Foo(int x, int y, int[] a) { if (x + 2 == y) { a[x] = 3; Debug.Assert(f(a[y - 2]) == f(y – x + 1)); } } x 2 y f (read ( write(a, x,3), y 2) f ( y x 1) Uninterpreted Array Arithmetic Theory Functions Domain-specific Constraint Solving Pex uses some heuristic approaches for domain-specific problems. Strings [TACAS’09, ICST’10] “uninterpreted functions” for string operations two-phase solving: first solve integer constraints involving indices and lengths, then character constraints Floating-point arithmetic Search-based approach: minimization of fitness function … (more to come) Writing Parameterized Unit Tests Exercise Write PUT for ReadFooValue Can you come up with Parameterized Unit Tests? string ReadFooValue() { string[] lines = File.ReadAllLines(@"t:\myapp.ini"); foreach (var line in lines) { int index = line.IndexOf('='); string name = line.Substring(index); if (name == "Foo") { string value = line.Substring(index + 1); return value; } } return null; } Exercise Write PUT for ReadFooValue Create new solution with C# class library Write code for ReadFooValue Create Test Project Add moles for mscorlib Write parameterized unit tests. [PexMethod] // attribute marks pex tests public void Test(...) { ... } Or get result from ReadFooValuePUT\ReadFooValuePUT.sln Exercise Crash Test Execute the code without assertions [PexMethod] void Crash(string[] lines) { MFile.ReadAllLines = () => lines; Reader.ReadFooValue(); } Exercise Assert Observed Results Manually review outputs Pex inserts Assertions automatically [PexMethod] public string Regression(string[] lines) { MFile.ReadAllLines = () => lines; return Reader.ReadFooValue(); } Exercise Write and read property [PexMethod] void FooExist(string value1) { PexAssume.IsTrue(value1 != null); var lines = new string[]{“Foo=“ + value1}; MFile.ReadAllLines = () => lines; var value2 = Reader.ReadFooValue(); PexAssert.AreEqual(value1, value2); } Test Generation Work-Flow Code-Under-Test Project Test Project Parameterized Unit Tests Pex Generated Tests Patterns Patterns how to create objects Quiz: Complex Objects As Inputs [PexMethod] public void ArrayListTest(ArrayList al, object o) { PexAssume.IsTrue(al != null); int len = al.Count; al.Add(o); PexAssert.IsTrue(al[len] == o); } What are the test inputs? Only null An array list created by calling a constructor ... and also calling Add(…) many times … and also calling Clear() many times … Quiz: Complex Objects As Inputs [PexMethod] public void ArrayListTest(ArrayList al, object o) { PexAssume.IsTrue(al != null); int len = al.Count; al.Add(o); PexAssert.IsTrue(al[len] == o); } Other possible problems: What if… ArrayList has no public constructor ArrayList is an abstract class (or interface) Creating complex objects The Problem Problem: Test parameters may be classes Symbolic execution tracks constraints over (private) fields of objects Constraint solver determines solution, assigning concrete values to (private) fields However, in practice objects must be initialized by constructor, and can be mutated only by calling certain methods What sequence of method calls reaches a desired field assignment? There may be no such sequence In general, undecidable Object creation: Factory methods Factory methods Parameterized factories create and configure objects Explicit: Public static method annotated with [PexFactoryMethod] Implicit: Pex guesses “best” constructor/setters Result: Exploration of reachable states Reachable using factory method Reachable within configured bounds Under-approximation of possible states Factory methods ArrayList Hand-written factory method: [PexFactoryMethod(ArrayList)] public static ArrayList Create( int capacity, object[] values) { var a = new ArrayList(capacity); foreach (value in values) a.Add(value); return a; } Pex will explore both factory method and PUT! Demo: Factory methods Open project “ArrayListSample”. Explore AddTestExplicit Explore AddTestImplicit Inspect generated test cases Notice constructor implicitly used by Pex Save factory method Edit factory method Explore again Alternative Object creation via class invariants Write class invariant as boolean-valued parameterless method Refers to private fields Must be placed in implementation code Write special constructor/factory method for testing only Sets fields, and assumes invariant Result: Exploration of feasible states May include states that are not reachable Patterns for Parameterized Unit Tests Pattern 4A Assume, Arrange, Act, Assert [PexMethod] void Add(List target, T value) { PexAssume.IsNotNull(target); // assume var count = target.Count; // arrange target.Add(value); // act 1. 2. 3. } // assert quiz Assert(target != null); Assert(target.Count == count + 1); Assert(target[0] == value); Pattern 4A Assume, Arrange, Act, Assert [PexMethod] void Add(List target, T value) { PexAssume.IsNotNull(target); // assume var count = target.Count; // arrange target.Add(value); // act 1. 2. 3. } // assert quiz Assert(target != null); Assert(target.Count == count + 1); Assert(target[0] == value); Pattern Regression Tests Generated test asserts any observed value Return value, out parameters int AddTest(int a, int b) { return a + b; } When code evolves, breaking changes in observable will be discovered void AddTest01() { var result = AddTest(0, 0); Assert.AreEqual(0, result); } Pattern Roundtrip For an API f(x), f-1(f(x)) = x for all x void ToStringParseRoundtrip(int value) { string s = value.ToString(); int parsed = int.Parse(s); 1. 2. 3. } // assert quiz Assert.AreEqual(value, s); Assert.AreEqual(s, parsed); Assert.AreEqual(parsed, value); Pattern Roundtrip For an API f(x), f-1(f(x)) = x for all x void ToStringParseRoundtrip(int value) { string s = value.ToString(); int parsed = int.Parse(s); 1. 2. 3. } // assert quiz Assert.AreEqual(value, s); Assert.AreEqual(s, parsed); Assert.AreEqual(parsed, value); Pattern Normalized Roundtrip For an API f(x), f-1(f(f-1(x)) = f-1(x) for all x void ParseToString(string x) { var normalized = int.Parse(x); var intermediate = normalized.ToString(); var roundtripped = int.Parse(intermediate); 1. 2. 3. 4. } // assert quiz Assert(x == intermediate); Assert(intermediate == roundtripped); Assert(normalized == roundtripped); Assert(x == roundtripped); Pattern Normalized Roundtrip For an API f(x), f-1(f(f-1(x)) = f-1(x) for all x void ParseToString(string x) { var normalized = int.Parse(x); var intermediate = normalized.ToString(); var roundtripped = int.Parse(intermediate); 1. 2. 3. 4. } // assert quiz Assert(x == intermediate); Assert(intermediate == roundtripped); Assert(normalized == roundtripped); Assert(x == roundtripped); Pattern Reachability Indicate which portions of a PUT should be reachable. [PexAssertReachEventually] public void Constructor(object input) { new Foo(input); PexAssert.ReachEventually(); } Pattern Same Observable Behavior Given two methods f(x) and g(x), and a method b(y) that observes the result or the exception behavior of a method, assert that f(x) and g(x) have same observable behavior under b, i.e. b(f(x)) = b(g(x)) for all x. public void ConcatsBehaveTheSame( string left, string right) { PexAssert.AreBehaviorsEqual( () => StringFormatter.ConcatV1(left, right), () => StringFormatter.ConcatV2(left, right)); } Pattern Commutative Diagram Given two implementations f and g of the same function, each possible requiring a different number of steps, i.e. f(x)=f1(f2(…(fn(x)…)), and g(x)=g1(g2(… (gm(x)…)), then it should hold that f1(f2(…(fn(x))…) = g1(g2(…(gm(x)…)) for all x. string Multiply(string x, string y); int Multiply(int x, int y); void CommutativeDiagram1(int x, int y) { string z1 = Multiply(x, y).ToString(); string z2 = Multiply(x.ToString(), y.ToString()); PexAssert.AreEqual(z1, z2); } Homework Patterns Read patterns paper: research.microsoft.com/Pex/documentation.aspx “Parameterized Test Patterns for Microsoft Pex” Limitations It’s called Parameterized Unit Testing Testability Pex understand managed .NET code only Pex does not understand native code. Problem if branching over values obtained from the environment Pex may not automatically detect all such cases. File System? if (!File.Exists(f)) throw ... Hidden Complexity Pex analyzes every executed .NET instruction Some used libraries may be surprisingly expensive to analyze XML parsing repeatedly converting data between different representations void Sum(string[] A) { Don’t do this. var sum = “0”; foreach(var a in A) sum = (int.Parse(a) + int.Parse(sum)).ToString(); if(sum == “123”) throw new Exception(); } Exploration Boundaries Configurable bounds include: TimeOut MaxBranches MaxCalls MaxConditions ▪ Number of conditions that depend on test inputs MaxRuns ConstraintSolverTimeOut ConstraintSolverMemoryLimit Multi-threaded code Unlike test inputs, thread-interleavings can normally not be controlled Thus, Pex can only explore single-threaded code Related approach to explore threadschedules (but not input parameters) by controlling thread-scheduler: CHESS http://research.microsoft.com/CHESS Lack of Test Oracle Unit Testing != Test Generation Wrapping up Conclusion What you learned today The Definition of Unit Testing Unit Tests are not Integration Tests Unit Test Isolation Introduction of Abstraction Layers Parameterized Unit Testing Separation of concerns: Coverage/Specification Patterns to write Parameterized Unit Tests Data Generation by Dynamic Symbolic Execution Availability Pex and Moles are Visual Studio 2010 Power Tools (academic/commercial license available) Minimum: Windows XP, .NET 2.0 Ideally: Visual Studio 2010 Professional http://research.microsoft.com/pex/ http://pexforfun.com Thank you http://research.microsoft.com/pex http://sites.google.com/site/asergrp/