Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.
Download
Report
Transcript Peli de Halleux, Nikolai Tillmann Research in Software Engineering Microsoft Research, Redmond, USA.
Peli de Halleux, Nikolai Tillmann
Research in Software Engineering
Microsoft Research, Redmond, USA
1999… people packing groceries for the Y2k
bug
DateTime.Now == new DateTime(2000,1,1))
if (DateTime.Now
throw Y2KBugException();
How do you replace DateTime.Now?
DateTime.Now
MDateTime.NowGet
= () => new DateTime(2000,1,1);
Moles
Delegates naming convention
delegate void Action<T>(T t); // void f(int i);
delegate R Func<T, R>(T t); // int f(string i);
Lambda Expressions and Statements
// C# 2.0
Func<string, int> f = delegate(string s) {return 0; }
// C# 3.0
Func<string, int> f = (s) => 0
Func<string, int> f = (s) => { return 0; }
Func<string, int> f = _ => 0
DEMO
Motivation
A unit test is a small program with assertions
Tests a single (small) unit of code in isolation
void ReadWrite() {
var list = new List();
list.Add(3);
Assert.AreEqual(1, list.Count);
}
Reality check:
Real unit tests are not that simple!
Components depend on other components
bool IsFileEmpty(string file) {
var content = File.ReadAllText(file);
File.ReadAllText(file);
return content.Length == 0;
}
Hidden Integration Tests
void FileExistsTest() {
File.Write(“foo.txt”,
File.Write(“foo.txt”, “”);
“”);
var result = IsFileEmpty(“foo.txt”)
Assert.IsTrue(result);
}
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
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
Introduce abstraction for external components
Replace them with something simpler, i.e. a Mock
IFileSystem fs,
fs string file) {
bool IsFileEmpty(IFileSystem
var content = fs.ReadAllText(file);
Mock, Stub, Double, Fake,
return content.Length…== 0;
}
void FileExistsTest() {
IFileSystem fs = ???;
fs.Write(“foo.txt”, “”);
var result = IsFileEmpty(fs,“foo.txt”)
Assert.IsTrue(result);
}
Replace Any .NET method with A Delegate
interface IFileSystem {
stringReadAllText(string
ReadAllText(stringfile);
file);
string
}
class SIFileSystem : IFileSystem {
Func<string,string> ReadAllTextString;
string IFileSystem.ReadAllText(string file) {
return this.ReadAllTextString(file);
}} // </auto-generated>
var fs = new SIFileSystem() {
file =>
=> “”;
“”;
ReadAllTextString = file
};
DEMO
Existing external components
cannot be re-factored
SharePoint, Asp.NET, VSTO
Need mechanism to stub
non-virtual methods
Static methods, methods in sealed types,
constructors
MSIL code rewriting required
Other Tools provide this functionality
Method redirected to user delegate, i.e. moled
MFile.ReadAllTextString = file => “”;
bool result = IsFileEmpty(“foo.txt”);
Assert.IsTrue(result);
Requires Code Instrumentation,
e.g. via Profiler!
Pex provides [HostType(“Pex”)]
NUnit, xUnit, etc… also supported
mscorlib.dll
File.ReadAllText(string name) {
}
.NET Runtime
Just In Time Compiler
File.ReadAllText(string name) {
var d = GetDetour();
if (d != null) return d();
}
ExtendedReflection
push ecx
push edx
push eax
DEMO
Lightweight Framework
Type Safe
Refactorable
Testable and “Hard-coded” Code
Overridable methods -> Stubs
Any other -> Moles
Delegate Based – use the language!
A Unit Test has Three essential ingredients:
Data
Method Sequence
Assertions
// for
void
Add()
all item,
{
capacity...
void
intAdd(int
item = 3;
item,
intint
capacity
capacity)
= 4;{
var list = new List(capacity);
list.Add(item);
void List.Add(T item) {
var countif= (this.count
list.Count; >= this.Capacity)
this.ResizeArray();
Capacity = 0
Assert.AreEqual(1, count);
this.items[this.count++] = item;
Test Case!
}
}
Automated White box Analysis does not
‘understand’ the environment
???
if (DateTime.Now
DateTime.Now == new DateTime(2000,1,1))
throw new Y2KException();
Isolate Code using Stubs and Moles
Void Y2k(DateTime dt) {
MDateTime.NowGet = () => dt
...
DateTime.Now == dt
}
DEMO
Z3
Constraint Solver
Future
standalone download
Pex
Test Generation
Automated White box Analysis
Stubs
ExtendedReflection
Runtime Code Instrumentation
Source Code Generation
Moles
Types
Bar.IFoo -> Bar.Stubs.SIFoo
Methods
void Foo(string v) -> FooString
Properties
String Value {get;} -> ValueGet
Types
Bar.Foo -> Bar.Stubs.MFoo
Methods
void Foo(string v) -> FooString
Properties
string Value {get;} -> ValueGet
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);
}
Compiler generates closures for us
void Test(string content) {
var fs = new SIFileSystem();
bool called = false;
fs.ReadAllText = file => {
called
called == true;
true;
return content;
};
...
Assert.IsTrue(called);
called
}
For free with Object Initializers
interface IBar { IFoo Foo {get;} }
interface IFoo { string Value {get;} }
IBar bar = new
…
SIBar()
if(bar.Foo.Value
.Foo .Value == “hello”) ...
var bar = new SIBar {
FooGet = () => new SIFoo {
ValueGet = () => “hello”
}
};
For free with Object Initializers
class Bar { public Foo Foo {get;} }
class Foo { public string Value {get;} }
if(new
new Bar()
Bar().Foo.Value
.Foo .Value == “hello”) ...
MBar.Constructor = me => {
new Mbar(me) => {
FooGet = () => new MFoo {
ValueGet = () => “hello”
}}}
It just works!
class Bar { public Foo Foo {get;} }
interface IFoo {string Value {get;} }
if(new
new Bar()
Bar().Foo.Value
.Foo .Value == “hello”) ...
MBar.Constructor = (me) => {
new Mbar(me) => {
FooGet = () => new SIFoo {
ValueGet = () => “hello”
}}}
Bind all methods of an interface at once
class Foo : IEnumerable<int> {...}
int[] values = {1,2,3};
var foo = new MFoo()
.Bind(values); // bind all methods of
// Ienumerable<int>
Set a trap to flag any call to a type
MFoo.FallbackToNotImplemented();
Iteratively build the mole sequence
Dispatching moles per instance
class Foo {
public int Value {get;}
}
var foo = new MFoo { ValueGet = () => 1 };
var bar = new MFoo { ValueGet = () => 2 };
Assert.IsTrue(foo.Value != bar.Value);
Attach Mole when Contructor is run
class 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 };
Stubs inherited from class may call base
implementation
abstract class FooBase {
public virtual string Value {get;}
}
var foo = new SFooBase() { CallBase
CallBase == true;
true; }
// call base class if no stub provided
var value = foo.Value;
Defines behavior when stub not provided
Default throws exception
interface IFoo {
string Value {get;}
}
StubFallbackBehavior.Current =
StubFallbackBehavior.Default;
var foo = new SIFoo();
var value = foo.Value; // returns null
Defines behavior when mole not provided
Default throws exception
class Foo {
string Value {get;}
}
var foo = new MFoo() {
InstanceFallbackBehavior =
MoleFallbackBehavior.Default }.Instance;
var value = foo.Value; //returns null
Pex automatically detects and uses Stubs
Pex provides return values
interface IFoo {
string Value {get;}
}
[PexMethod]
void Test(IFoo foo) { // pex uses SIFoo
if (foo.Value == “foo”)
throw ...; // pex chooses value