Monday, March 29, 2010

Moles!

As I write more of them, I'm finding that these "unit test" things aren't so bad, especially if you write them properly. That means getting rid of your external dependencies - code that's not yours, code that is yours but isn't being tested, networks, file systems, and all that other junk.

If you're working in the ivory tower of Objectorientedland, this is always dead simple: the dependencies of every class you use are abstracted away as interfaces, and concrete implementations of those interfaces are controlled by either the caller, who provides them to the class' constructor, or a dependency-injection framework, which I admittedly haven't messed around with. Your test should provide suitable test doubles of those dependencies.

There are lots of ways of thinking about test doubles - the two most common that I've seen are stubs and mocks. Stubs are little dummy objects with methods that basically do the simplest thing possible to make your test run. For example, if I have an object with a method that takes a DataSet and returns a scalar value, my stub implementation of that method will be a one-liner that just returns the value I'm expecting. Another kind of test double is a mock. A mock performs the same actions as a stub when it comes to the code that's calling it, but it also has "expectations:" you set up a mock to expect method calls with certain parameters in a certain order, because that's what you're expecting your code-under-test to do. If the mock's expectations aren't met, the test fails.

Back to Objectorientedland - parachuting down from the ivory tower and making our way to the swamp of spaghetti code, we see that things aren't always that simple. Sometimes corners are cut, sometimes refactoring never happens, sometimes it's just simpler to use that static filesystem accessor method, and sometimes creating a zillion classes and interfaces to accomplish a simple task (aka "enterprise design") is just overkill.

Enter Moles.

Moles are part of Microsoft Research's new test tool Pex. I'm not going to go into Pex just yet - I'm really excited about it but I haven't explored it fully enough. Both Moles and Pex help you to write better and more complete test cases for your code. Pex does it by "exploring" your code, literally finding ways to take all the branches and break it if possible. Moles gives you a way to easily stub out methods, even methods you shouldn't technically be able to stub out, without writing a ton of code.

I'm going to be writing a bunch of posts about Moles and Pex, but just to get started, I'm going to toss out a perfect use case for Moles: getting around the static File methods. In a properly built OO application, the file system will be abstracted away through a set of classes, abstracted away to interfaces, that can be swapped out so your tests can effectively "fake it." However, we all know that File.Open is way too tasty, and sometimes it's really all that's called for. The problem with File.Open is that you have inexorably chained your code to the filesystem - have fun trying to make a test suite that doesn't refuse to run on any machine but yours without 20 minutes of setup. The whole point of unit tests is that you could be able to click a couple buttons and watch the green checkmarks roll in within seconds.

With Moles, you can literally rewrite File.Open and any other static methods you like, right in your test case, to make them do whatever you want. Check out the following code and the test method for it:

public int SumNumbersFromAFile(string filename)
{
    string fileText = File.ReadAllText(filename);
    string[] numbers = fileText.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
    return numbers.Select((str) => Int32.Parse(str)).Sum();
}

[TestMethod()]
[HostType("Moles")]
public void SumNumbersFromAFileTest()
{
    Class1 target = new Class1();
    string input = "filename";
    string textFromFile = "1, 2, 3, 4";
    MFile.ReadAllTextString = path =>
        {
            Assert.AreEqual(input, path);
            return textFromFile;
        };
    int actual = target.SumNumbersFromAFile(input);
    Assert.AreEqual(10, actual);
}


SumNumbersFromAFile is the method under test. As you can see, it reads the text from a file, expecting to find a list of numbers separated by commas, and sums them. It's a pretty contrived example, and one without a lot of error handling or checking of assumptions, but it does the job for illustrating Moles as I'm doing here.

The test case looks like a pretty standard test case - setup of input and expected results, method call, and an assertion - with the exception of that "MFile" sitting there. MFile is actually the Mole of the static file class. As a mole of a static class, it has a static property for each method. The type of each property is a delegate type that matches the signature of the method being moled. By providing an implementation for the delegate, you are overriding the behavior of the method. When my method under test calls "File.ReadAllTextFromString("filename");", it will call my delegate implementation. The assert in there is a poor man's mock-like expectation: it checks to see if the method was called with the expected parameter. Then it returns my nicely prepared string to execute on.

How does this sorcery work? The key is the [HostType("Moles")] attribute at the top. This causes the test to run in a host that can overwrite the compiled IL as it executes. The debugger behaves just a little bit funny when you debug inside a Moles-hosted test - it looks like it's jumping around a bit but it actually is working properly - but if you step through SumNumbersFromAFile you can see it call out to my Mole delegate to get my pre-prepared string, bypassing the file system entirely.

You can use Moles to bypass most anything, and also included is a simple little stubbing framework for doing regular ol' stubs of interfaces, abstract classes and classes with virtual methods. For stuff where a regular stub won't cut it, you use a mole.

In the coming weeks I'll be posting some more about how to actually get Pex and Moles into your test project, neat examples of how to use Moles and stubs, and how to do things like mole constructor methods, create mole instances (for classes that can't be stubbed because they're sealed or have no virtual methods), and create nested stubs.

No comments: