As you understand from the title, it will be about the product from Microsoft Research -
Microsoft Moles Isolation Framework . I met him for the first time after reading the
post alak_sys
habrauser . I liked the mole so much that I decided to share my experience of using it.
What for?
To begin with, let's try to decide for what purposes Microsoft.Moles is intended and what we can achieve with it:
- Complete isolation of the tested logic from the external environment.
- The ability to quickly and easily create unit tests, whereby the testing of class logic becomes possible even if there is no implementation of the classes whose user is the class under test.
- It becomes easy to organize test cases or model the state of related objects to create test conditions.
- At times, the execution time of unit tests is reduced, frequent test runs become real
- Violation of the logic of a unit does not entail the fall of a hundred or two tests not intended for its testing.
- Convenient testing of methods with complex workflow
About unit testing in general
If you have the above goals raise doubts, it is worth a little to refresh the main points of the testing unit. If you have any doubts, you can skip to the next
section .
What is unit testing for?
- Isolate the individual units of the program and show that individually (or a combination of several units) are operational.
- Documenting the logic of units - in order to understand how a unit functions - it is enough to read the code of its tests.
- Simplification of refactoring - you can easily check that the code works without errors after making changes.
- Reducing the time of bugfix due to the correction of most errors in the development process. Minimize the possibility of regression.
Unit Test Requirements
- The test should test the minimum amount of class code, and the number of tests for this code should be proportional to the number of code execution paths.
- Test implementation should be as simple as possible and not require initialization of the test environment outside of the test (for example, preparing test data in the database).
- The test must be self-documenting - an outside developer must understand the logic of the unit after reading the tests implemented for this unit. Without the need to examine the contents of the database or other sources of test data
- The test should be as simple as possible - it is much easier to understand a dozen simple tests implemented for each individual test case than in a single test that tests all these test cases.
- A test crash should not be related to logic for which it was not intended to be tested.
- Passing the test should not depend on external systems (database server, web server, file system, external device controllers), if it is not designed to test interaction with these systems.
- You need maximum code coverage with tests with minimum time to complete them.
What problems arise when using unit tests?
- The challenges of preparing a test environment are the creation of large test data sets, the creation of various connections to servers, the description of configuration files, the need to create external device emulators, and so on.
- The complexity of testing a particular class, since a class is often a user of other classes, and the presence of errors in their work entails a drop in the test, which is not in line with the unit testing paradigm - the test should fall if the tested logic is broken.
- The difficulty of ensuring 100% code coverage of tests (as a rule, this is due to the developer’s laziness and the first two problems)
- The complexity of the maintenance (debugging, fix) tests that comes from the first two reasons.
- The impossibility of accessing (except for reflection) to the hidden members of classes, the impossibility of initializing readonly members with test values.
- Often the drop in the test is due to the fact that the logic of another unit (and not the one being tested) was violated. We have to spend time looking for errors in the wrong places.
- Test time - the main requirement for unit tests is their frequent execution (made a change in the unit, drove the tests), but it is often impossible to do this because the tests run too long, for large projects this time can be measured in hours. Moreover, most of the time is taken by processes not directly related to testing - reading / writing of the database, file system, web servers, etc., initialization of the associated logic, which is not directly related to the test.
Toolkit for unit testing.
At the moment there are a large number of frameworks (NUnit, JUnit, DUnit, xUnit, MSTest) for a variety of programming languages ​​designed to create and use automated tests for various purposes (Unit Tests, Integration Tests, Functional Tests, UI Tests). As a rule, they do not allow creating full-fledged unit tests in a pure form, since they cannot ensure complete isolation of the code under test from the surrounding logic. Developers have to resort to various tricks, create a cloud of additional mock classes, use reflection, and so on. And here comes the help of Isolation Frameworks, which allow you to quickly and conveniently organize an isolated environment for performing unit tests, and Microsoft Moles is one of them.
')
What can Microsoft.Moles?
I will try to demonstrate the capabilities that Moles provides us with an example (I tried to make it as simple as possible, but difficult enough to use Moles in it).
Suppose we have a FileUpdater class, with the only method UpdateFileFromService (string fileId), whose task is to update the file in the local storage with the file downloaded from the remote storage if the local and remote file hashes do not match.
As you can see from the listing, the UpdateFileFromService method uses the FileManager and StorageService classes to access local and remote storage. I intentionally do not provide an implementation of the methods of the FileManager and StorageService classes, so all we need to know is their interface.
public class FileUpdater
{
private StorageService storageService;
public StorageService Service
{
get
{
if (storageService == null )
{
storageService = new StorageService();
}
return storageService;
}
}
public void UpdateFileFromService( string fileId)
{
if ( string .IsNullOrEmpty(fileId))
{
throw new Exception( "fileId is empty" );
}
var fileManager = new FileManager();
string localFileHash = fileManager.GetFileHash(fileId);
string remoteFileHash = Service.GetFileHash(fileId);
if (localFileHash != remoteFileHash)
{
FileStream file = Service.DownloadFile(fileId);
fileManager.SaveFile(fileId, remoteFileHash, file);
}
}
}
public class FileManager
{
public string GetFileHash( string fileId)
{
throw new NotImplementedException();
}
public void SaveFile( string fileId, string remoteFileHash, FileStream file)
{
throw new NotImplementedException();
}
}
public class StorageService
{
public string GetFileHash( string fileId)
{
throw new NotImplementedException();
}
public FileStream DownloadFile( string fileId)
{
throw new NotImplementedException();
}
}
* This source code was highlighted with Source Code Highlighter .
The following embodiments of the
UpdateFileFromService method are
obvious :
- fileId - empty string
- local and remote file hashes are not identical
- local and remote file hashes are identical
We will not consider the remaining situations, since this implementation of the FileUpdater class does not contain their processing.
Let's try to create tests for options 2 and 3 using Microsoft.Moles, since the implementation of the test for the first case is elementary using standard tools of any test framework.
Generation of Mole and Stub classes
First of all, it is necessary to generate a Moled assembly for libraries in which there are classes, the logic of which we will replace with custom delegates (otherwise, Stub-methods). This can be done using the command line using mole.exe, which comes in the installation package with Microsoft.Moles, as well as through the Visual Studio 2008/2010 interface. We use the second method.
As you can see, after installing Microsoft.Moles, a new item of the context menu
“Add Moles Assembly” appeared for the reference assemblies (Fig. 1).

After executing this command on the ClassLibrary1 assembly, we get a new group of
ClassLibrary1.moles files (Fig.2), from which the ClassLibrary1.moles file is the Moled assembly handle, which contains its generation parameters and which can be changed if necessary. The rest of the files are automatically regenerated with each build, and there is no point in editing them.

Microsoft.Moles allows you to use two types of deputy classes - Stubs & Moles.
- Stub provides a lightweight isolation framework that is designed to fake implementations of abstract, virtual methods and interface members. Suitable for substituting virtual methods or members that are implementations of interfaces
- Mole uses a powerful call forwarding framework that uses the code profiler APIs to intercept calls to the classes used, which redirects calls to the fake object. Allows you to redirect a call to any member of the class.
For me personally, both options were equally convenient, and did not cause any difficulties in use.
The generated assembly ClassLibrary1.Moles.dll by default contains pairs of classes M% ClassName% and S% ClassName% for each of the classes contained in the assembly ClassLibrary1.dll.
Where:
- M% ClassName% - Mole class
- S% ClassName% - Stub class
If you wish, by making changes to the
ClassLibrary1.moles file, you can ensure that Moles or Stubs are generated only for certain classes (which greatly reduces generation time), or you can turn off generation of Moles or Stubs altogether if you do not use it, and also set other generation parameters (Intellisence will prompt a list of valid parameters and their purpose). You can also use the ClassLibrary1.moles
file when generating with the command line moles.exe (you can familiarize yourself with the list of moles.exe parameters by running moles.exe without parameters).
Using Mole Classes
Since the use of Moles and Stubs is somewhat similar, and the article does not pretend to provide a complete description of all the functionality of Microsoft.Moles - we’ll dwell only on the examples of using Mole.
To make the examples clearer, I would immediately note the particular naming of members of the generated Mole and Stub classes — first the name of the member of the original class comes, and then the names of the types of the parameters passed are added to it. For example,
MFileManager.AllInstances.GetFileHashString is a property to
override the FileManager.GetFileHash method
(string fileId) . I think this style is associated with the need to ensure the uniqueness of the members generated for the overloaded methods.
With Mole, we get the ability to replace any methods and properties of classes in various ways:
- Replacing all instances of a class
MFileManager.AllInstances.GetFileHashString = (fileManager, fileId) =>
{
// GetFileHash(string fileId) FileManager
return Guid .NewGuid().ToString();
};
* This source code was highlighted with Source Code Highlighter .
Substitution of static classes and static properties is performed in the same way, only without specifying AllInstances .
- Substitution of a specific class instance
var storageServiceMole = new MStorageService();
storageServiceMole.GetFileHashString = (fileId) =>
{
// GetFileHash(string fileId) storageService StorageService
return testRemoteHash;
};
var storageService = storageServiceMole.Instanc
* This source code was highlighted with Source Code Highlighter .
or so, with the transfer of the initialized class object in the constructor
var storageService = new StorageService();
var storageServiceMole = new MStorageService(storageService);
storageServiceMole.GetFileHashString = (fileId) =>
{
// GetFileHash(string fileId) storageService StorageService
return testRemoteHash;
};
* This source code was highlighted with Source Code Highlighter .
- Replacing the class constructor in order to override the necessary members of the Mole class with objects for all instances of the class.
MFileUpdater.Constructor = (@ this ) =>
{
var mole = new MFileUpdater(@ this );
mole.ServiceGet = (x) => initializedServiceInstance;
};
* This source code was highlighted with Source Code Highlighter .
- Substitution with further reference to the original implementation of the class. If you need to perform some checks on incoming values ​​before calling the original class method, use the following method
var fileUpdaterMole = new MFileUpdater() { InstanceBehavior = MoleBehaviors.Fallthrough };
fileUpdaterMole.UpdateFileFromServiceString = (y) =>
{
fileUpdaterMole.UpdateFileFromServiceString = null ; //
fileUpdaterMole.Instance.UpdateFileFromService(y); // FileUpdater.UpdateFileFromService
};
fileUpdaterMole.Instance.UpdateFileFromService( Guid .NewGuid().ToString());
* This source code was highlighted with Source Code Highlighter .
It is obvious that in lambda expressions you can perform various checks of Assert incoming values ​​or simulate some de
Activity with the return of a predetermined result.
InstanceBehavior
It is also worth noting the possibility of specifying the behavior of members of the class to whom we do not explicitly specify a replacement delegate. Each Mole generated class has an InstanceBehavior property, which can have the following values
- MoleBehaviors.DefaultValue - the unsubstituted members of the class will be replaced with an empty delegate and return the default value of the return type
var storageService = new StorageService();
var storageServiceMole = new MStorageService(storageService) { InstanceBehavior = MoleBehaviors.DefaultValue };
// null;
var result = storageService.GetFileHash( Guid .NewGuid().ToString());
* This source code was highlighted with Source Code Highlighter .
- MoleBehaviors.NotImplemented - when accessing an unsubstituted member, a NotImplementedException exception will occur
var storageService = new StorageService();
var storageServiceMole = new MStorageService(storageService) { InstanceBehavior = MoleBehaviors.NotImplemented };
// NotImplementedException
storageService.GetFileHash( "328576BA-7345-4847-84AC-170EF03FFA7A" );
* This source code was highlighted with Source Code Highlighter .
- MoleBehaviors.Fallthrough - calls to unsubstituted members will be processed according to their original implementation in the class being replaced.
var storageService = new StorageService();
var storageServiceMole = new MStorageService() {InstanceBehavior = MoleBehaviors.Fallthrough};
// StorageService.GetFileHash(string fileId)
storageService.GetFileHash( "328576BA-7345-4847-84AC-170EF03FFA7A" );
* This source code was highlighted with Source Code Highlighter .
- MoleBehaviors.Current - default behavior of unsubstituted methods after overlaying the instance of the Mole class with a class. When calling an unsubstituted member, a MoleNotImplementedException exception will be thrown .
var storageService = new StorageService();
var storageServiceMole = new MStorageService() {InstanceBehavior = MoleBehaviors.Current};
// MoleNotImplementedException
storageService.GetFileHash( "328576BA-7345-4847-84AC-170EF03FFA7A" );
* This source code was highlighted with Source Code Highlighter .
- MoleBehaviors.CurrentProxy leaves the current behavior for all non-overlapping members of the class instance that is being blocked
var storageService = new StorageService();
var storageServiceMole1 = new MStorageService(storageService) { InstanceBehavior = MoleBehaviors.NotImplemented };
storageServiceMole1.GetFileHashString = (fileId) =>
{
return Guid .NewGuid().ToString();
};
var storageServiceMole2 = new MStorageService(storageService) { InstanceBehavior = MoleBehaviors.CurrentProxy };
storageServiceMole2.DownloadFileString = (fileId) =>
{
return testFileStream;
};
// storageServiceMole1.GetFileHashString
storageService.GetFileHash( "328576BA-7345-4847-84AC-170EF03FFA7A" );
// storageServiceMole2.GetFileHashString
storageService.DownloadFile( "328576BA-7345-4847-84AC-170EF03FFA7A" );
* This source code was highlighted with Source Code Highlighter .
- Custom behavior implementation - possible if you create your own IMoleBehavior interface implementation
Test implementation example
This is actually a test of the situation when the hashes of the local and remote files are different.
///
/// ,
///
[TestMethod]
[HostType( "Moles" )]
public void TestUpdateFileNonMatchedHash()
{
var callOrder = 0; //
var testFileId = Guid .NewGuid().ToString();
var testLocalHash = Guid .NewGuid().ToString();
var testRemoteHash = Guid .NewGuid().ToString();
var testFileStream = new FileStream ( @"c:\testfile.txt" , FileMode .OpenOrCreate);
var storageServiceMole = new MStorageService() { InstanceBehavior = MoleBehaviors.Fallthrough };
// GetFileHash StorageService
storageServiceMole.GetFileHashString = (fileId) =>
{
Assert.AreEqual(1, callOrder++); //
Assert.AreEqual(testFileId, fileId);
Assert.AreNotEqual(testLocalHash, testRemoteHash);
return testRemoteHash;
};
storageServiceMole.DownloadFileString = (fileId) =>
{
Assert.AreEqual(2, callOrder++);
Assert.AreEqual(testFileId, fileId);
return testFileStream;
};
// FileManager.
//MFileManager.AllInstances FileManager UpdateFile
// ,
// FileManager
MFileManager.AllInstances.GetFileHashString = (fileManager, fileId) =>
{
Assert.AreEqual(0, callOrder++);
Assert.AreEqual(testFileId, fileId);
return Guid .NewGuid().ToString();
};
MFileManager.AllInstances.SaveFileStringStringFileStream = (fileManager, fileId, fileHash, fileStream) =>
{
Assert.AreEqual(3, callOrder++);
Assert.AreEqual(testFileId, fileId);
Assert.AreEqual(testRemoteHash, fileHash);
Assert.AreSame(testFileStream, fileStream);
};
var fileUpdaterMole = new MFileUpdater
{
InstanceBehavior = MoleBehaviors.Fallthrough,
// getter FileUpdater.Service , moled StorageService
ServiceGet = () => storageServiceMole.Instance
};
var fileUpdater = fileUpdaterMole.Instance;
fileUpdater.UpdateFileFromService(testFileId);
}
* This source code was highlighted with Source Code Highlighter .
For the second situation - when the hashes are identical, try implementing the test yourself.
additional information
- Mole types cannot be used in multi-threaded tests.
- Moles cannot be used to replace certain types of mscorlib.
- Tests using Microsoft.Moles must be instrumented by Moles — for MSTest, this is an attribute of the HostType test method (“Moles”) . For other test frameworks, you need to run tests using moles.runner.exe (for nunit, it will look like moles.runner.exe "yourTestDLLName" /runner:nunit-console.exe ) Accordingly, those who like to work with the test framework via the GUI will also have to run shell via mole.exe
- Nant task configuration example for invoking tests that moles instructs
< target name ="runTests" >
< exec basedir ="${build.dir.unittests}" program ="${microsoft.moles.dir}moles.runner.exe" >
< arg value ="/Runner:${nunit.console.dir}nunit-console.exe" ></ arg >
< arg value ="${build.dir.unittests}My.UnitTests.dll" ></ arg >
</ exec >
</ target >
* This source code was highlighted with Source Code Highlighter .
At this point I’ll finish, it may be continued if this information turns out to be interesting.
Sources
Microsoft Moles Reference ManualUsing MS Moles with NUnit, NAnt and .NET 3.5MSDN - The Moles Framework