名前からわかるように、Microsoft Researchの製品
-Microsoft Moles Isolation Frameworkについて説明します。 haberraiser
alek_sysの 投稿を読んだ後、初めて彼に会いました。 私はがとても好きだったので、その使用の経験を共有することにしました。
なんで?
最初に、Microsoft.Molesが意図する目的と、Microsoft.Molesで何を達成できるかを判断します。
- テストされたロジックを外部環境から完全に分離します。
- ユーザーがテスト対象クラスであるクラスの実装がない場合でも、ユニットテストを迅速かつ簡単に作成し、クラスのロジックをテストする機能が可能になります。
- テストデータセットを整理したり、関連オブジェクトの状態をシミュレートしてテスト条件を作成したりすることが容易になります。
- 時には、単体テストの実行時間が短縮され、テストの頻繁な起動が現実のものになります
- ユニットロジックの違反は、テスト用に設計されていない数百または2つのテストの低下を伴いません
- 複雑なワークフローを使用した便利なメソッドテスト
一般的な単体テストについて
上記の目標に疑問がある場合は、単体テストの主要なポイントを思い出して少し更新する価値があります。 疑いのない人-次の
セクションにスキップできます。
ユニットテストとは何ですか?
- 個々のプログラムユニットを分離し、ユニット(または複数のユニットのセット)が個別に動作していることを示します。
- ユニットのロジックを文書化する-ユニットがどのように機能するかを理解するために-テストのコードを読むだけです。
- リファクタリングの簡素化-変更後、コードがエラーなしで機能することを簡単に確認できます。
- 開発プロセスのほとんどのエラーの修正によるバグ修正時間の短縮。 回帰の可能性を最小限にします。
単体テストの要件
- テストではクラスコードの最小量をテストする必要があり、特定のコードのテスト数はコード実行パスの数に比例する必要があります。
- テストの実装は可能な限り単純で、テスト外のテスト環境の初期化を必要としないものでなければなりません(たとえば、データベース内のテストデータの準備)。
- テストは自己文書化する必要があります。外部の開発者は、このユニットに実装されたテストを読んで、ユニットのロジックを理解する必要があります。 データベースの内容やテストデータの他のソースを調べる必要なし
- テストは可能な限りシンプルにする必要があります。これらすべてのテストケースをテストする1つのテストよりも、個々のテストケースに実装された12個のシンプルなテストを理解する方がはるかに簡単です。
- テストの落下は、テスト対象外のロジックに関連付けるべきではありません。
- これらのシステムとの相互作用をテストするように設計されていない場合、テストは外部システム(データベースサーバー、Webサーバー、ファイルシステム、外部デバイスコントローラー)に依存してはなりません。
- 最小限の実行時間でテストできる最大のコードカバレッジ
単体テストを使用するときに生じる問題
- テスト環境の準備の難しさ-大規模なテストデータセットの作成、サーバーへのさまざまな接続の作成、構成ファイルの説明、外部デバイスのエミュレーターを作成する必要性など。
- クラスは他のクラスのユーザーであることが多いため、特定のクラスのテストの複雑さ、および作業のエラーの存在はテストの低下を伴います。これは単体テストのパラダイムに対応しません。
- テストでコードを100%網羅することの難しさ(原則として、これは開発者の怠と最初の2つの問題によるものです)
- 最初の2つの理由に起因するテストの維持(デバッグ、修正)の複雑さ。
- クラスの非表示メンバーにアクセスできない(リフレクションを除く)、テスト値で読み取り専用メンバーを初期化できない。
- 多くの場合、テストの失敗は、別のユニット(テスト済みのユニットではない)のロジックが違反されたという事実によるものです。 間違った場所でエラーを探すのに時間を費やす必要があります。
- テストの実行時間-ユニットテストの主な要件は頻繁に実行することです(ユニットを変更し、テストを実行しました)が、テストの実行に時間がかかりすぎるため、多くの場合、これは不可能です。 テストに直接関係しないプロセスに費やされるほとんどの時間-データベース、ファイルシステム、Webサーバーなどの読み取り/書き込み、テストに直接関係しない関連ロジックの初期化。
単体テストツールキット。
現時点では、さまざまな目的(ユニットテスト、統合テスト、機能テスト、UIテスト)の自動テストを作成および使用するために設計された多くのプログラミング言語用のフレームワーク(NUnit、JUnit、DUnit、xUnit、MSTest)が多数あります。 原則として、テストされたコードが周囲のロジックから完全に分離されていることを保証できないため、純粋な形式で完全な単体テストを作成することはできません。 開発者は、さまざまなトリックに頼り、追加のモッククラスのクラウドを作成し、リフレクションを使用する必要があります。 そして、ここでIsolation Frameworksが役立ちます。これにより、便利かつ迅速に、単体テスト用の分離された環境を整理できます。MicrosoftMolesもその1つです。
Microsoft.Molesは何ができますか?
モルが私たちに例を提供する可能性を実証しようとします(可能な限り単純にしようとしましたが、その中でモルを使用することを正当化するのに十分なほど難しくしました)。
ローカルファイルハッシュとリモートファイルハッシュが一致しない場合に、リモートストレージからダウンロードしたファイルでローカルストレージのファイルを更新するタスクが唯一のUpdateFileFromService(string fileId)メソッドを持つFileUpdaterクラスがあるとします。
リストからわかるように、UpdateFileFromServiceメソッドはFileManagerクラスとStorageServiceクラスを使用して、ローカルおよびリモートのリポジトリにアクセスします。 FileManagerクラスとStorageServiceクラスのメソッドの実装を意図的に引用することはありません。したがって、それらのインターフェイスを知るだけで済みます。
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 .
UpdateFileFromServiceメソッドの次のバージョンは
明らかです。
- fileId-空の文字列
- ローカルとリモートのファイルハッシュは同一ではありません
- ローカルとリモートのファイルハッシュは同一です
FileUpdaterクラスのこの実装には処理が含まれていないため、残りの状況は考慮しません。
最初のケースのテスト実装は、テストフレームワークの標準ツールを使用した初歩的なものであるため、Microsoft.Molesを使用してオプション2および3のテストを作成してみましょう。
ほくろおよびスタブクラスの生成
まず、クラスを含むライブラリのMoledアセンブリを生成する必要があります。クラスのロジックは、カスタムデリゲート(それ以外の場合はスタブメソッド)に置き換えます。 これは、Mole.exeを使用したコマンドラインを使用して実行できます。mole.exeは、Microsoft.Molesのインストールパッケージに含まれており、Visual Studio 2008/2010インターフェイスからも使用できます。 2番目の方法を使用します。
ご覧のとおり、Microsoft.Molesをインストールすると、参照アセンブリ用の新しいコンテキストメニュー項目
「Add Moles Assembly」が表示されました(図1)。

ClassLibrary1アセンブリでこのコマンドを実行すると、
ClassLibrary1.molesファイルの新しいグループが取得されます(図2)。そこからClassLibrary1.molesファイルはMoledアセンブリ記述子であり、生成用のパラメーターを含み、必要に応じて変更できます。 残りのファイルは各ビルドで自動的に再生成され、編集する意味はありません。

Microsoft.Molesでは、2つのタイプの代替クラスを使用できます-スタブ&モール。
- スタブは、抽象、仮想メソッド、およびメンバーインターフェイスの実装を偽装する軽量の分離フレームワークを提供します。 インターフェイスの実装である仮想メソッドまたはメンバーのスプーフィングに適しています
- Moleは強力な呼び出しリダイレクトフレームワークを使用します。このフレームワークは、コードプロファイラーAPIを使用して、使用済みクラスへの呼び出しをインターセプトし、呼び出しを偽のオブジェクトにリダイレクトします。 クラスの任意のメンバーにコールをリダイレクトできます
個人的には、両方のオプションが同じように便利であることが判明し、使用に支障はありませんでした。
生成されたアセンブリClassLibrary1.Moles.dllには、デフォルトで、アセンブリClassLibrary1.dllに含まれる各クラスのクラスのペアM%ClassName%とS%ClassName%が含まれています。
ここで:
- M%ClassName%-ほくろクラス
- S%ClassName%-スタブクラス
必要に応じて、
ClassLibrary1.molesファイルに変更を加えることで、特定のクラスに対してのみMolesまたはStubが生成されるようにし(生成時間を大幅に短縮します)、MoleまたはStubを使用していない場合は生成を無効にしたり、その他を設定したりできます生成パラメータ(インテリジェンスは、有効なパラメータとその目的のリストを教えてくれます)。 moles.exeコマンドラインを使用して生成するときにClassLibrary1.moles
ファイルを使用することもできます(moles.exeパラメーターのリストは、パラメーターなしでmoles.exeを実行することで確認できます)。
Moleクラスを使用する
MolesとStubsの使用はやや似ているため、この記事ではMicrosoft.Molesのすべての機能について完全に説明するふりをしていません。Moleの使用例のみを取り上げます。
例をより明確にするために、生成されたMoleおよびStubクラスのメンバーの命名機能にすぐに注意します。最初に元のクラスのメンバーの名前が来て、次に渡されたパラメーターの型名がそれに追加されます。 たとえば、
MFileManager.AllInstances.GetFileHashStringは
、FileManager.GetFileHash(string fileId)メソッドを
オーバーライドするプロパティです。 このスタイルは、オーバーロードされたメソッドに対して生成されたメンバーの一意性を確保する必要性に関連していると思います。
Moleを使用すると、クラスのメソッドとプロパティをさまざまな方法で置き換えることができます。
- クラスのすべてのインスタンスを置き換える
MFileManager.AllInstances.GetFileHashString = (fileManager, fileId) =>
{
// GetFileHash(string fileId) FileManager
return Guid .NewGuid().ToString();
};
* This source code was highlighted with Source Code Highlighter .
静的クラスと静的プロパティの置換は、 AllInstancesを指定しない場合にのみ同様に実行されます。
- クラスの特定のインスタンスを置き換える
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 .
または、初期化されたクラスオブジェクトのコンストラクターで転送します
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 .
- Moleクラスの必要なメンバーをクラスのすべてのインスタンスのオブジェクトとオーバーラップさせるために、クラスコンストラクターを置き換えます。
MFileUpdater.Constructor = (@ this ) =>
{
var mole = new MFileUpdater(@ this );
mole.ServiceGet = (x) => initializedServiceInstance;
};
* This source code was highlighted with Source Code Highlighter .
- クラスの元の実装をさらに参照する置換。 元のクラスメソッドを呼び出す前に着信値のチェックを行う必要がある場合は、次のメソッドを使用します
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 .
明らかに、ラムダ式では、着信値のあらゆる種類のアサートチェックを実行したり、
所定の結果が返ってくる俊敏性。
InstanceBehavior
また、代わりのデリゲートを明示的に指定していないクラスメンバーの動作を指定する可能性も注目に値します。 Moleが生成した各クラスにはInstanceBehaviorプロパティがあり、次の値を取ることができます
- MoleBehaviors.DefaultValue-クラスの非置換メンバーは空のデリゲートに置き換えられ、返された結果の型のデフォルト値を返します
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-非置換メンバーにアクセスすると、 NotImplementedExceptionがスローされます
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-非置換メンバーへの呼び出しは、置き換えられたクラスの元の実装に従って処理されます
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 -Moleクラスのインスタンスがクラスと重複した後の非置換メソッドのデフォルトの動作。 非置換メンバーが呼び出されると、 MoleNotImplementedExceptionがスローされます
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は、重複するクラスインスタンスの重複しないすべてのメンバーの現在の動作を残します
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 .
- 動作のカスタム実装-IMoleBehaviorインターフェースの独自の実装を作成する場合に可能
テスト実装例
これは、ローカルファイルとリモートファイルのハッシュが異なる場合の状況の実際のテストです。
///
/// ,
///
[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 .
2番目の状況-ハッシュが同一の場合、テストを自分で実装してみてください。
追加情報
- ほくろ型は、マルチスレッドテストでは使用できません。
- モルを使用して、一部の種類のmscorlibを置き換えることはできません。
- Microsoft.Molesを使用したテストは、 Moleによってインストルメント化する必要があります-MSTestの場合、これはHostType( "Moles")テストメソッドの属性です。 他のテストフレームワークの場合、moles.runner.exeを使用してテストを実行する必要があります(nunitの場合、 moles.runner.exe "yourTestDLLName" /runner:nunit-console.exeのようになります)。したがって、GUIを使用してテストフレームワークを操作するファンも実行する必要がありますmole.exeによるシェル
- ほくろによって計測されるテストを呼び出すためのNantタスクの設定例
< 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 .
とりあえずこれを終了します。この情報がおもしろければ継続するかもしれません。
ソース
Microsoft MolesリファレンスマニュアルNUnit、NAnt、および.NET 3.5でMS Molesを使用するMSDN-モレフレームワーク