📜 ⬆️ ⬇️

Microsoft Moles Isolation Framework, digging deeper

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:


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?
Unit Test Requirements

What problems arise when using unit tests?

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 :
  1. fileId - empty string
  2. local and remote file hashes are not identical
  3. 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.
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:

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:

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

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



At this point I’ll finish, it may be continued if this information turns out to be interesting.

Sources


Microsoft Moles Reference Manual
Using MS Moles with NUnit, NAnt and .NET 3.5
MSDN - The Moles Framework

Source: https://habr.com/ru/post/101408/


All Articles