Top Banner
Lightweight Test Stubs and Moles for .NET Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA
41

Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Dec 22, 2015

Download

Documents

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Lightweight Test Stubs and Moles for .NET

Peli de Halleux, Nikolai TillmannResearch in Software EngineeringMicrosoft Research, Redmond, USA

Page 2: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

1999… people packing groceries for the Y2k bug

How do you replace DateTime.Now?

The Y2K bug DEMO

if (DateTime.Now == new DateTime(2000,1,1)) throw Y2KBugException();

DateTime.Now = () => new DateTime(2000,1,1);

DateTime.Now

MDateTime.NowGet = () => new DateTime(2000,1,1);

Moles

Page 3: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Delegates naming convention

Lambda Expressions and Statements

(C# 3.0 Syntax)

delegate void Action<T>(T t); // void f(int i);delegate R Func<T, R>(T t); // int f(string i);// C# 2.0Func<string, int> f = delegate(string s) {return 0; }

// C# 3.0Func<string, int> f = (s) => 0Func<string, int> f = (s) => { return 0; }Func<string, int> f = _ => 0

Page 4: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Y2K BugDEMO

Page 5: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Isolation in Unit Testing

Motivation

Page 6: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

A unit test is a small program with assertions

Tests a single (small) unit of code in isolation

Reality check: Real unit tests are not that simple!

Unit Testing

void ReadWrite() { var list = new List(); list.Add(3); Assert.AreEqual(1, list.Count);}

Page 7: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Unit Testing is not that easy

Components depend on other components

Hidden Integration Testsvoid FileExistsTest() { File.Write(“foo.txt”, “”); var result = IsFileEmpty(“foo.txt”) Assert.IsTrue(result);}

bool IsFileEmpty(string file) { var content = File.ReadAllText(file); return content.Length == 0;}

File.ReadAllText(file);

File.Write(“foo.txt”, “”);

Page 8: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Isolation is critical

Slow, complicated setup, non-deterministic tests

Solution: Replace by Simpler Environment (“mocking”)

Testable Design: Abstraction layer + Dependency Injection + Mocks for testing Simply uses virtual methods

Hard-coded Design: No abstraction layer, static methods, sealed types. Runtime rewriting needed

Page 9: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs and Moles Framework

Replace Any .NET method with A Delegate

Method can be overridden? Use Stubs Interfaces, Abstract classes, Virtual methods in non-sealed types

Method cannot be overridden? Use Moles Static methods, Sealed types, Inline Constructor calls

Page 10: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs for Unit Testing

Page 11: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Testable Design

Introduce abstraction for external components Replace them with something simpler, i.e. a Mock

bool IsFileEmpty(IFileSystem fs, string file) { var content = fs.ReadAllText(file); return content.Length == 0;}

void FileExistsTest() { IFileSystem fs = ???; fs.Write(“foo.txt”, “”); var result = IsFileEmpty(fs,“foo.txt”) Assert.IsTrue(result);}

IFileSystem fs

IFileSystem fs = ???;

Mock, Stub, Double, Fake, …

Page 12: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs – Delegate Based Stubs

Replace Any .NET method with A Delegate

var fs = new SIFileSystem() { ReadAllTextString = file => “”;};

file => “”;

interface IFileSystem { string ReadAllText(string file);}

class SIFileSystem : IFileSystem {Func<string,string> ReadAllTextString; string IFileSystem.ReadAllText(string file) { return this.ReadAllTextString(file);}} // </auto-generated>

Func<string,string> ReadAllTextString;

this.ReadAllTextString(file);

string ReadAllText(string file);

Page 13: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

IFileSystem DemoDEMO

Page 14: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles for Unit Testing

Page 15: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Hard-coded Design

Existing external components cannot be re-factored SharePoint, Asp.NET, VSTO

Need mechanism to stubnon-virtual methods Static methods, methods in sealed

types, constructors MSIL code rewriting required

Other Tools provide this functionality

Page 16: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles – Delegate Based Detours

Method redirected to user delegate, i.e. moled

Requires Code Instrumentation,e.g. via Profiler!

Pex provides [HostType(“Pex”)] NUnit, xUnit, etc… also supported

bool result = IsFileEmpty(“foo.txt”);Assert.IsTrue(result);

MFile.ReadAllTextString = file => “”;

Page 17: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles under the Hood

File.ReadAllText(string name) {

}

mscorlib.dll

File.ReadAllText(string name) { var d = GetDetour(); if (d != null) return d();

}

push ecxpush edxpush eax

.NET RuntimeJust In Time

Compiler

ExtendedReflection

Page 18: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

File.ReadAllText DemoDEMO

Page 19: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs and Moles

Lightweight Framework Type Safe Refactorable

Testable and “Hard-coded” Code Overridable methods -> Stubs Any other -> Moles

Delegate Based – use the language!

Page 20: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs, Moles and PexIsolated Parameterized Unit Testing

Page 21: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Parameterized Unit Testing

var list = new List(capacity); list.Add(item); var count = list.Count;

Assert.AreEqual(1, count);}

A Unit Test has Three essential ingredients: Data Method Sequence Assertionsvoid Add() { int item = 3; int capacity = 4;// for all item, capacity...void Add(int item, int capacity) {

void List.Add(T item) { if (this.count >= this.Capacity) this.ResizeArray(); this.items[this.count++] = item;}

if (this.count >= this.Capacity)Capacity =

0 Test Case!

Page 22: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Isolated Parameterized Unit Testing

Automated White box Analysis does not ‘understand’ the environment

Isolate Code using Stubs and Moles

if (DateTime.Now == new DateTime(2000,1,1)) throw new Y2KException();

DateTime.Now

???

Void Y2k(DateTime dt) { MDateTime.NowGet = () => dt ...}

MDateTime.NowGet = () => dt DateTime.Now ==

dt

Page 23: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs, Moles and PexIsolated Parameterized Unit Testing

DEMO

Page 24: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Future standalone download

Pex Components

ExtendedReflectionRuntime Code Instrumentation

Source Code Generation

MolesStubs

Z3Constraint

Solver

PexTest Generation

Automated White box Analysis

Page 25: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs and Moles in Details

Page 26: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs Naming Conventions

Types

Methods

Properties

Bar.IFoo -> Bar.Stubs.SIFoo

void Foo(string v) -> FooString

String Value {get;} -> ValueGet

Page 27: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles Naming Conventions

Types

Methods

Properties

Bar.Foo -> Bar.Stubs.MFoo

void Foo(string v) -> FooString

string Value {get;} -> ValueGet

Page 28: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles Type Structure

class Foo { static int StaticMethod() {…} int InstanceMethod() {…}}

class MFoo : MoleBase<Foo> { static Func<int> StaticMethod { set; } Func<int> InstanceMethod { set; }

implicit operator Foo (MFoo foo);}

Page 29: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Side Effects for free

Compiler generates closures for usvoid Test(string content) { var fs = new SIFileSystem(); bool called = false; fs.ReadAllText = file => { called = true; return content; }; ... Assert.IsTrue(called);}

bool called = false;

called = true;

called

Page 30: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Recursive Stubs

For free with Object Initializersinterface IBar { IFoo Foo {get;} } interface IFoo { string Value {get;} }

var bar = new SIBar { FooGet = () => new SIFoo { ValueGet = () => “hello” }};

IBar bar = …if(bar.Foo.Value == “hello”) ...

new SIBar().Foo.Value

Page 31: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Recursive Moles

For free with Object Initializers

class Bar { public Foo Foo {get;} }class Foo { public string Value {get;} }

MBar.Constructor = me => { new Mbar(me) => { FooGet = () => new MFoo { ValueGet = () => “hello”}}}

if(new Bar().Foo.Value == “hello”) ...new Bar()

.Foo.Value

Page 32: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Recursive Moles And Stubs

It just works!

class Bar { public Foo Foo {get;} }interface IFoo {string Value {get;} }

MBar.Constructor = (me) => { new Mbar(me) => { FooGet = () => new SIFoo { ValueGet = () => “hello”}}}

if(new Bar().Foo.Value == “hello”) ...new Bar()

.Foo.Value

Page 33: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Bind = Runtime Duck Typing

Bind all methods of an interface at onceclass Foo : IEnumerable<int> {...}

int[] values = {1,2,3};

var foo = new MFoo() .Bind(values); // bind all methods of // Ienumerable<int>

Page 34: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Trapping Environment

Set a trap to flag any call to a type

Iteratively build the mole sequence

MFoo.FallbackToNotImplemented();

Page 35: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Per Instance Moles

Dispatching moles per instanceclass Foo { public int Value {get;}}

var foo = new MFoo { ValueGet = () => 1 };var bar = new MFoo { ValueGet = () => 2 };

Assert.IsTrue(foo.Value != bar.Value);

Page 36: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles for New Objects

Attach Mole when Contructor is runclass Foo { public Foo() {} public int Bar() {…}}

MFoo.Constructor = me => { var foo = new MFoo(me) { Bar = () => 10 }; MFoo.Constructor = null; // only 1 instance};

MFoo.Constructor = _ => new MFoo(_) { Bar = () => 10 };

Page 37: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Partial Stubs

Stubs inherited from class may call base implementation

abstract class FooBase { public virtual string Value {get;}}

var foo = new SFooBase() { CallBase = true; }// call base class if no stub providedvar value = foo.Value;

CallBase = true;

Page 38: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Stubs Fallback Behavior

Defines behavior when stub not provided Default throws exceptioninterface IFoo { string Value {get;}}

StubFallbackBehavior.Current = StubFallbackBehavior.Default; var foo = new SIFoo();var value = foo.Value; // returns null

Page 39: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Moles Fallback Behavior

Defines behavior when mole not provided Default throws exceptionclass Foo { string Value {get;}}

var foo = new MFoo() { InstanceFallbackBehavior = MoleFallbackBehavior.Default }.Instance;

var value = foo.Value; //returns null

Page 40: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Pex Ready Stubs

Pex automatically detects and uses Stubs

Pex provides return valuesinterface IFoo { string Value {get;}}

[PexMethod]void Test(IFoo foo) { // pex uses SIFoo if (foo.Value == “foo”) throw ...; // pex chooses value

Page 41: Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.

Give your feedback,Shape the future