マイクロソフトのほくろ

Molesは、MS Researchの軽量ツールであり、インターフェイスと仮想メソッド、およびシールドクラス、非仮想メソッド、静的メソッドのスタブを自動的に生成できます(!) 。 最初のタイプのスタブはスタブと呼ばれ、2番目のタイプはモルです。 このこと自体を使用して非同期操作をテストしましたが、これについては前に説明しましたが、すべてを順番に説明しましょう。



スタブ





そのような例を見てみましょう。 単体テストの価値、依存関係の反転などの原則、他の非常に有用な原則とパターン(他のすべてのSOLID原則、おそらくFIRST )を理解するとします。 そして、私たちがテストやボブおじさんのファンであるということではなく、単に高い接続性が悪いことを知っているからです。 したがって、インターフェイスを割り当て、それらをタスクを実行するためにこれらのインターフェイスを必要とするクラスコンストラクターまたはメソッドに挿入することにより、合理的に依存関係を減らします。





したがって、コードの抽象化のために、 IFooインターフェイスを割り当て、このインターフェイスを使用するクラスを作成したと仮定しましょう。



namespace PlayingWithMoles

{

// , -

public interface IFoo

{

string SomeMethod();

}

// - , IFoo

public class FooConsumer

{

// ""

public FooConsumer(IFoo foo)

{

this .foo = foo;

}

// , - IFoo

public void DoStuff()

{

var someResults = foo.SomeMethod();

Console .WriteLine( "Doing stuff. IFoo.SomeMethod results: {0}" , someResults);

}

private readonly IFoo foo;

}

}




* This source code was highlighted with Source Code Highlighter .








さて、私たちの明るい頭脳はそれほど明るくない考えで訪れ、そして同様に素晴らしいDoStuffメソッドで素晴らしいFooConsumerクラスの単体テストを書くかどうかを考えてみましょう (そのようなコードをテストする意味がほぼゼロであることは明らかですが、あなたはアイデアを理解しました) 。 そのため、 FooConsumerクラスのインスタンスを作成するには、コードに含まれている可能性があるIFooインターフェイスの実装を見つける必要がありますが、 単体テストに適しているという事実ではありません。 3番目などに依存する別のクラス。 もちろん、このような兄弟のささいなことをやめることはできません。テストを実施すれば、問題は確実に終わります。 したがって、メソッドは1つしかなく、問題を引き起こさないため、手作業でインターフェイスを実装します。その結果、次の種類のテストを取得します。



[TestClass()]

public class FooConsumerTest

{

class FooTester : IFoo

{

public string SomeMethod()

{

return "Test string" ;

}

}

[TestMethod]

public void DoStuffTest()

{

IFoo foo = new FooTester();

FooConsumer target = new FooConsumer(foo);

target.DoStuff();

}

}




* This source code was highlighted with Source Code Highlighter .








これで、このコードを実行できるようになり、出力ウィンドウで「 Doing stuff 」を参照してください IFoo.SomeMethodの結果:Hello、Custom stub 。 はい、それは難しくありませんでしたが、実際のアプリケーションの問題は詳細にあり、実際にはこれよりもはるかに複雑なインターフェイスに直面していること、そして各インターフェイスの実装は最も興味深いとは言えません仕事の世界で。



だから、モルのライブラリ。 このツールを使用してスタブを作成する方法は次のとおりです。 テストを含むプロジェクトでは、テストするビジネスロジックを含むアセンブリを右クリックし、「アセンブリにモルを追加」を選択します。その後、ファイル「MyAssemblyName.moles」がプロジェクトに追加され、このプロジェクトのコンパイル後に接続されたアセンブリのリストには、アセンブリMyAssemblyName.Molesが表示されます。これには、生成されたすべてのスタブが含まれます。 このツールは、ビジネスロジックを使用してビルドの成功ビルドを自動的に追跡し、スタブを自動的に再生成します。 MyAssemblyName.molesファイルは、特定の形式の単純なxmlファイルであり、スタブが生成されるアセンブリの名前とその生成用のパラメーターを設定します(たとえば、スタブを生成するタイプ、スタブのタイプ(スタブまたはほくろ)など。







バージョン0.94から、molesファイルのスキームが変更されました。 これより前に、スタブの自動コンパイルをキャンセルし、構成ファイルのセクションの1つでCompilation = falseを設定すると、スタブを含むアセンブリが生成されず、代わりに生成されたファイルが現在のプロジェクトに直接追加されました。 バージョン0.94から、この可能性はなくなりましたが、生成されたコードがどのように見えるかを見ることができ、プロジェクトのサブフォルダーを調べます。 したがって、たとえば、このツールの現在のバージョンは、生成されたファイルを次の場所に保存します。MyTestProject\ obj \ Debug \ Moles \ sl \ mgsl。



このツールによって生成されたコードの単純化されたバージョンを見てみましょう。



namespace PlayingWithMoles.Moles

{

public class SIFoo

: Microsoft.Moles.Framework.Stubs.StubBase

, IFoo

{

string IFoo.SomeMethod()

{

var sh = this .SomeMethod;

if (sh != null )

return sh.Invoke();

else

{

// Behavior-,

}

}

// Sets the stub of IFoo.SomeMethod

public Func< string > SomeMethod;

}

}




* This source code was highlighted with Source Code Highlighter .








そのため、生成されたクラスは元のインターフェイスを明示的に実装し、対応するメソッドのシグネチャに対応する型のデリゲートを含みます。 このメソッドはパラメーターを受け入れず、 stringを返すため、スタブにはFunc <string>型のデリゲート(つまり、 文字列を返し、パラメーターを受け入れないデリゲート)が含まれます。 さらに、 SomeMethodメソッドでは、このデリゲートは単純に呼び出されます(このデリゲートがnullの 場合デフォルトStubNotImplementedExceptionがスローされますが、この動作は変更できます)。 スタブクラス名はS InterfaceOrClassNameであり、OriginalNamespace .Moles名前空間にあることに注意してください。



元のテストを変更し、生成されたスタブを使用してみましょう。

[TestMethod]

public void DoStuffTestWithStubs()

{

var fooStub = new PlayingWithMoles.Moles.SIFoo();

fooStub.SomeMethod = () => "Hello, Stub!" ;

var target = new FooConsumer(fooStub);

target.DoStuff();

}




* This source code was highlighted with Source Code Highlighter .








実行すると、予想どおり、次の行が表示されます。 IFoo.SomeMethodの結果:こんにちは、Moles Stub!



スタブは、非密閉クラスのインターフェースおよび仮想(密閉されていない)メソッドに対してのみ生成されることを再度思い出します。 また、魔法はありません。これは、継承クラスまたはインターフェイスを実装するクラスが生成されるためです。また、呼び出しのスケジューリングは古き良き仮想呼び出しによるものです。 はい、このことは非常に興味深いものであり、少し時間を節約できますが、モグラとは異なり、驚くべきことは何もありません。



ほくろ





モルはスタブの2番目のタイプで、スタブと同じ目的のために設計されていますが、静的メソッドまたはインスタンスメソッド、および非仮想メソッドで動作できます。 FooConsumerクラスがIFooインターフェイスに関連付けられているのではなく、仮想メソッドを含まない特定のFooクラスに関連付けられていると仮定しましょう。



namespace PlayingWithMoles

{

// Foo,

// , IFoo

public class Foo

{

public string SomeMethod()

{

return "Hello, from non-virtual method." ;

}

}

// - , Foo

public class FooConsumer

{

// Foo

public FooConsumer(Foo foo)

{

this .foo = foo;

}

// , - Foo

public void DoStuff()

{

var someResults = foo.SomeMethod();

Console .WriteLine( "Doing stuff. Foo.SomeMethod results: {0}" ,

someResults);

}



private readonly Foo foo;

}

}




* This source code was highlighted with Source Code Highlighter .








このコードをリファクタリングしてIFooインターフェースを選択する機会があれば、それは価値があることは明らかですが、時には実用的ではないかもしれませんし、まったく不可能な場合もあります。 誰かのコードを取得して、徐々に必要な依存関係の束を解き放つことがよくありますが、今はテストが必要です。 また、このコードを変更することは不可能である場合も珍しくありません。それは、あなたがそれを制御していないからです。 結局のところ、サードパーティのライブラリまたは外部リソースへのアクセスである可能性がありますが、その可用性テストはあなたの意図ではありません。



これは、このツールで生成できる2番目のタイプのプラグ、つまりほくろが役立つ場所です。 モルは同様の方法で生成され、同じネストされた名前空間(OriginalNamespace .Moles )に配置されますが、プレフィックスSの代わりにプレフィックスMを含みますが、仮想メソッドおよびインターフェースの代わりに、非仮想静的メソッドの動作を指定できます。 この動作を実装するために、MolesライブラリーはCLRプロファイラーを使用します。特に、コールバックICorProfilerCallback :: JITCompilationStartedの関数を処理します。ここでは、元のメソッドの代わりにデリゲートが「プッシュ」します。 その結果、元のメソッドを呼び出すと、提供されたコードフラグメントが呼び出されます。



したがって、この場合、クラスPlayingWithMolesが生成されます。 Moles.M Foo、そして新しいバージョンのFooConsumerクラスの古くからある(そして最も重要なことに役立つ) DoStuffメソッドをテストする方法を見てみましょう。



[TestMethod]

[HostType( "Moles" )]

public void DoStuffTestWithMoles()

{

var fooMole = new PlayingWithMoles.Moles.MFoo();

fooMole.SomeMethod = () => "Hello, Mole!" ;

var target = new FooConsumer(fooMole);

target.DoStuff();

}




* This source code was highlighted with Source Code Highlighter .








[HostType(“ Moles”)]属性に注意してください上記のように、コードの「計装」とCLRプロファイラーを使用するため、すべてのモグラの魔法が機能しなくなります。 このテストは、スタブが使用された以前のテストよりも複雑ではなく、起動時に期待どおりの結果が得られます。 IFoo.SomeMethodの結果:Hello、Mole!



次に、より興味深い例を見てみましょう。 以前のいくつかの 投稿では、タンバリンと非同期操作の周りで踊るさまざまな方法を検討し、例として、Webページコンテンツの非同期取得を使用しました。 しかし、時々インターネットにアクセスせずにコンピューターで作業する必要があり、毎回例を書き換えたくなかったため、このツールを使用するというアイデアが生まれました。 さらに、単体テストの適切なルールは、外部リソース(ファイル、ネットワーク接続、データベースなど)から切り離すことです。そうしないと、これらのテストはモジュール化されず、統合されます。 統合テストが悪い、いや、良いと言っているわけではありませんが、単体テストではコードに集中できますが、統合テストではこれらの外部リソースの存在と通常の操作が必要です。



Webページにアクセスし、応答の長さを出力するWebRequestorクラスがあるとします。



public class WebRequester

{

public void RequestWebPage( string url)

{

var request = WebRequest.Create(url);

var response = request.GetResponse();

Console .WriteLine( "Sync version. URL: {0}, Response content length: {1}" ,

url, response.ContentLength);

}

}




* This source code was highlighted with Source Code Highlighter .








テストの記述を開始する前に、 WebRequestクラスのモルを生成する必要があります。 このためには、テストプロジェクトのシステムアセンブリを右クリックして(このクラスがその中にあるため)、[モルアセンブリの追加]メニュー項目を選択し、テストでプロジェクトを再コンパイルすると、System.Behaviors.dllアセンブリがプロジェクトに表示されます、元のSystem.dllアセンブリに必要なすべてのスタブとモールが含まれています。



それでは、コードをテストする方法を見てみましょう。



[TestMethod]

[HostType( "Moles" )]

public void RequestWebPageTest()

{

var mole = new System.Net.Moles.MHttpWebResponse();

mole.ContentLengthGet = () => 5;

// URL-.

// , :

//System.Net.Moles.MHttpWebRequest.AllInstances.GetResponse = (r) =>

// {

// if (r.RequestUri == new Uri("http://rsdn.ru"))

// return mole;

// return r.GetResponse();

// };

//

System.Net.Moles.MHttpWebRequest.AllInstances.GetResponse = (r) => mole;

WebRequester target = new WebRequester();

target.RequestWebPage( "http://rsdn.ru" );

}




* This source code was highlighted with Source Code Highlighter .








このテストでは、最初にSystem.Net .Moles名前空間にあるHttpWebResponseクラスのスタブをM HttpWebRespoonseという名前で作成し、 ContentLengthプロパティの "body"として、 5を返すラムダを取得します。 ここで、 GetResponseデリゲートを設定して、 HttpWebRequestクラスのすべてのインスタンスのGetResponseメソッドを「変更」します。 その後、 WebRequesterクラスの RequestWebPageメソッドを安全に呼び出して、Internet: Syncバージョンにアクセスしなくても通常の結果を取得できます URL: rsdn.ru 、応答コンテンツの長さ:5。



それでは、Webページのコンテンツを取得する非同期バージョンであるWebRequesterクラスの RequestWebPageAsyncメソッドに移りましょう



public void RequestWebPageAsync( string url)

{

var waiter = new ManualResetEvent( false );

var request = WebRequest.Create(url);

request.BeginGetResponse(

ar=>

{

var response = request.EndGetResponse(ar);

Console .WriteLine( "Async version. URL: {0}, Response content length: {1}" ,

url, response.ContentLength);

waiter.Set();

}, null );



waiter.WaitOne();

}




* This source code was highlighted with Source Code Highlighter .








テストの目的で、非同期操作が完了する前にメソッドが制御を完了しないようにします。 RequestWebPageAsyncメソッドはrequest.EndGetResponseメソッドが呼び出されるまで実行を継続できるため、このコードでは、 BeginGetResponseメソッドによって返されるAsyncResult.AsyncWaitHandleを使用できません。 この結果、このような「非同期」の意味はありませんが、これは重要ではありません。ここでは、非同期操作をテストするためのメカニズムのみを示し、それ以上のことはしたくないからです。 しかし、その考えは明確だと思います。



それで、今度はテストメソッド:



[TestMethod]

[HostType( "Moles" )]

public void RequestWebPageAsyncTest()

{

var mole = new System.Net.Moles.MHttpWebResponse();

mole.ContentLengthGet = () => 5;

// ,

//

Action action = () => Thread.Sleep(500);



// , ,

//

// - (-,

// ).

ThreadPool.SetMinThreads(3, 3);

System.Net.Moles.MHttpWebRequest.AllInstances.BeginGetResponseAsyncCallbackObject =

(r, a, iar) => action.BeginInvoke(a, iar);

System.Net.Moles.MHttpWebRequest.AllInstances.EndGetResponseIAsyncResult =

(r, iar) => { action.EndInvoke(iar); return mole; };

WebRequester target = new WebRequester();

target.RequestWebPageAsync(http: //rsdn.ru);

}




* This source code was highlighted with Source Code Highlighter .








テストメソッドでは、 M HttpWebResponseスタブを再度作成します。このインスタンスは、 ContentLengthプロパティにアクセスすると5を返し、 Thread.Sleep(500)を呼び出すデリゲートを使用して、長時間実行される操作をシミュレートします。 このテストを実行すると、 非同期バージョンが得られます URL: rsdn.ru 、応答コンテンツの長さ:5.証明するために必要なもの!



結論の代わりに





Molesライブラリーは、インターフェイスの単純なスタブを生成するのに役立つ非常に興味深いツールであり、外部リソース、静的または非仮想メソッドからコードを解き、別の方法では単に不可能なスタブを作成できるようにします。 ここでは、このツールのすべての機能からはほど遠いことを示しましたが、これは特定のタスクに適用できる(または適用できない)ことを理解するのに十分なはずです。



プログラミングスタッフ経由。



All Articles