2014年11月26日水曜日

移行サンプル:Telerik JustMock によるモック化① final メソッドのモック化 - from "Prig: Open Source Alternative to Microsoft Fakes" Wiki -

まとまってきたドキュメントを日本語の記事にもしておくよシリーズ第 5 段!(元ネタ:Prigwiki より、MIGRATION: Final Mocking by Telerik JustMock。同シリーズの他記事:1 2 3 4

やっと折り返し地点ですね。今回からは 3 回に渡る JustMock の公式ドキュメントにあるサンプルを実装するシリーズとなります。まずは、「Final Mocking samples」を Prig に移行してみましょう。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
Using Jekyll with Pages - GitHub Help
Can I share a Microsoft Fakes unit test with a developer using Visual Studio Professional - Stack Overflow
shim a sealed class singleton method and with MS Fakes - Stack Overflow
Search - GitHub Anonymously Hosted DynamicMethods Assembly
API Hooking with MS Detours - CodeProject
Incorrect solution build ordering when using MSBuild.exe - The Visual Studio Blog - Site Home - MSDN Blogs
visual studio 2010 - How do I target a specific .NET project within a Solution using MSBuild from VS2010 - Stack Overflow
Advanced Usage | JustMock Documentation
.net - Is there any free mocking framework that can mock static methods and sealed classes - Stack Overflow
Modern ASP.NET Webskills by @calebjenkins





目次

準備
このサンプルでは、シリーズということもあり、1 つのソリューションに複数のプロジェクトを追加するという構成を採っています。





なお、この例は、間接的に呼び出される対象が、GAC に登録されていない Assembly に含まれており、以前の例(MolesFakes)とは異なることに注意してくださいね。

これまでは Package Manager Console で間接スタブを作成する手順を実施しても問題ありませんでしたが、今後は、PowerShell(コンソール)を使えるようになることを推奨します。Package Manager Console は、仕様上、ネストしたプロンプトをサポートしていません。従って、1 度間接対象の解析を行うために Assembly を読み込むと、2 度とその Assembly を解放できないという問題があるのです。そもそも、いつも使う機能である、オートコンプリート、コマンド履歴なども PowerShell(コンソール)のものより機能的に劣ります。間接設定の追加やテストの実行時は Package Manager Console を使い、Assembly の解析時は PowerShell(コンソール)を使うということが、効率が良いでしょう。

※注※:以下の解説で、PM> で始まるコマンドは Package Manager Console で実行しますが、PS> で始まるコマンドは PowerShell(コンソール)で実行することに注意してください。

FinalMockingMigration をビルド後、出力ディレクトリを開き、以下のコマンドを実行します。プロジェクトがいくつもありますので、対象のテストプロジェクトを Default project: として選択することをお忘れなく:
PM> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2014/09/16 6:37 FinalMockingMigration
d---- 2014/09/16 20:30 FinalMockingMigrationTest
d---- 2014/09/16 6:35 packages
d---- 2014/09/16 20:32 SealedMockingMigration
d---- 2014/09/16 20:34 SealedMockingMigrationTest
d---- 2014/09/16 20:37 StaticMockingMigration
d---- 2014/09/16 20:40 StaticMockingMigrationTest
-a--- 2014/09/16 6:31 3665 JustMockMigrationDemo.sln
PM> cd .\FinalMockingMigration\bin\Debug
PM> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/09/16 6:38 6144 FinalMockingMigration.exe
-a--- 2014/09/16 6:30 189 FinalMockingMigration.exe.config
-a--- 2014/09/16 6:38 13824 FinalMockingMigration.pdb
-a--- 2014/09/17 6:31 23168 FinalMockingMigration.vshost.exe
-a--- 2014/09/16 6:30 189 FinalMockingMigration.vshost.exe.config
-a--- 2013/06/18 21:28 490 FinalMockingMigration.vshost.exe.manifest
PM> padd -af (dir .\FinalMockingMigration.exe).FullName
PM>
view raw 05_01.ps1 hosted with ❤ by GitHub

ちなみに、padd -af は、Add-PrigAssembly -AssemblyFrom のエイリアスです。GAC に登録されていない Assembly の間接スタブ設定を追加する場合、コマンドの引数にフルパスを引き渡す必要があることに注意してください。PowerShell(スクリプト言語)では、コマンド (dir <target file>).FullName でフルファイルパスを取得することができます。FinalMockingMigrationTest に FinalMockingMigration.v4.0.30319.v1.0.0.0.prig が追加されれば成功です。

さて、次は、FinalMockingMigration の出力ディレクトリ上で PowerShell(コンソール)を実行し、Assembly を解析しましょう。Windows 8 を使っているのであれば、エクスプローラで対象のディレクトリを表示中、Alt、F、R のキーコンビネーションが便利ですよね。現在の位置を確認したら、ネストしたプロンプトを開始します:
PS> $pwd
Path
----
C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug
PS> powershell
Windows PowerShell
Copyright (C) 2013 Microsoft Corporation. All rights reserved.
PS>
view raw 05_02.ps1 hosted with ❤ by GitHub

Package Manager Console で利用が可能になっている種々のコマンド(Find-IndirectionTarget や Get-IndirectionStubSetting など)は、Prig をインストール後、モジュール $(SolutionDir)\packages\Prig.\tools\Urasandesu.Prig に配置されます。なので、それを PowerShell(コンソール)にインポートします:
PS> ipmo "C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\packages\Prig.1.0.0\tools\Urasandesu.Prig"
PS> gmo
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 3.1.0.0 Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Con...
Manifest 3.1.0.0 Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object...}
Script 0.0.0.0 Urasandesu.Prig {Add-PrigAssembly, ConvertTo-PrigAssemblyName, Find-Indire...
PS>
view raw 05_03.ps1 hosted with ❤ by GitHub

GAC に登録していない Assembly を読み込みたい場合、System.Reflection.Assembly.LoadFrom(string) にフルパスを引き渡す必要があります。取得には・・・って、ちょっと前にやりましたね (^^ゞ:
PS> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/09/16 6:38 6144 FinalMockingMigration.exe
-a--- 2014/09/16 6:30 189 FinalMockingMigration.exe.config
-a--- 2014/09/16 6:38 13824 FinalMockingMigration.pdb
-a--- 2014/09/17 6:31 23168 FinalMockingMigration.vshost.exe
-a--- 2014/09/16 6:30 189 FinalMockingMigration.vshost.exe.config
-a--- 2013/06/18 21:28 490 FinalMockingMigration.vshost.exe.manifest
PS> (dir .\FinalMockingMigration.exe).FullName
C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigration\bin\Debug\FinalMockingMigration.exe
view raw 05_04.ps1 hosted with ❤ by GitHub

Assembly を読み込み後、変数に設定します。ところで、履歴から実行したコマンドを呼び出し、ちょっと変更して再実行するという流れは PowerShell(コンソール)の真骨頂だと思います。Package Manager Console で同じことをしようものなら、重複した履歴を延々と遡る必要があったり、オートコンプリートによる意図しない消去を食らったりすることになるでしょう。ストレスがマッハになること請け合いです (-_-;):
PS> [System.Reflection.Assembly]::LoadFrom((dir .\FinalMockingMigration.exe).FullName)
GAC Version Location
--- ------- --------
False v4.0.30319 C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\FinalMockingMigrati...
PS> $asmInfo = [System.Reflection.Assembly]::LoadFrom((dir .\FinalMockingMigration.exe).FullName)
PS>
view raw 05_05.ps1 hosted with ❤ by GitHub

GetTypes で型を確認すると、上から 3 つが対象ということがわかります。それらに対し、前のサンプルと同様のフィルタを掛けてみましょう:
PS> $asmInfo.GetTypes()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False Foo System.Object
False True EchoEventHandler System.MulticastDelegate
True False FooGeneric System.Object
False False Program System.Object
PS> ($asmInfo.GetTypes())[0..2] | % { $_.GetMembers() } | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType }
Method
------
Int32 Execute(Int32, Int32)
Int32 Execute(Int32)
Int32 Echo(Int32)
System.String get_FooProp()
Void set_FooProp(System.String)
Void add_OnEchoCallback(EchoEventHandler)
Void remove_OnEchoCallback(EchoEventHandler)
Void .ctor()
Void Invoke(Boolean)
System.IAsyncResult BeginInvoke(Boolean, System.AsyncCallback, System.Object)
Void EndInvoke(System.IAsyncResult)
Void .ctor(System.Object, IntPtr)
TRet Echo[T,TRet](T)
Void .ctor()
PS>
view raw 05_06.ps1 hosted with ❤ by GitHub

結果が良さそうであれば、間接スタブ設定に変換し、クリップボードにコピーします。なお、解析が終わった Assembly を解放したい場合は、ネストしたプロンプトを終了すれば OK です。
PS> ($asmInfo.GetTypes())[0..2] | % { $_.GetMembers() } | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType } | pget | clip
PS> exit
PS>
view raw 05_07.ps1 hosted with ❤ by GitHub

Visual Studio に戻り、FinalMockingMigration.v4.0.30319.v1.0.0.0.prig に間接スタブ設定を貼り付けます。ビルドは通りましたか?それでは、ちょっと長くなりましたが、実際の移行を行っていきましょう!





Assert Final Method Setup
Fakes のサンプルでも言いましたが、Prig は Mock Object としての機能を持っていませんので、Moq のような別のモックフレームワークと一緒に使うことを推奨しています。しかし、どうも JustMock のサンプルは、単に Test Stub としての振る舞いが紹介されているだけのものが大半ですので、Prig のサンプルもそれと一貫性を持たせるようにしました:

はじめに、final メソッドの戻り値を入れ替えてみましょう:
[Test]
public void Prig_should_setup_a_call_to_a_final_method()
{
using (new IndirectionsContext())
{
// Arrange
var fooProxy = new PProxyFoo();
fooProxy.EchoInt32().Body = (@this, arg1) => 10;
var foo = (Foo)fooProxy;
// Act
var actual = foo.Echo(1);
// Assert
Assert.AreEqual(10, actual);
}
}
view raw 05_08.cs hosted with ❤ by GitHub

fooProxy.EchoInt32().Body = (@this, arg1) => 10; で、Prig は、その final メソッドを、定数 10 を返す処理に入れ替えています。大丈夫ですよね。次、行ってみましょう!





Assert Property Get
final プロパティを入れ替えるサンプルです:
[Test]
public void Prig_should_setup_a_call_to_a_final_property()
{
using (new IndirectionsContext())
{
// Arrange
var fooProxy = new PProxyFoo();
fooProxy.FooPropGet().Body = @this => "bar";
var foo = (Foo)fooProxy;
// Act
var actual = foo.FooProp;
// Assert
Assert.AreEqual("bar", actual);
}
}
view raw 05_09.cs hosted with ❤ by GitHub

fooProxy.FooPropGet().Body = @this => "bar"; で、Prig は、その final プロパティを、定数 bar を返す処理に入れ替えています。どんどん行きますよ (^O^)





Assert Property Set
final プロパティのセッターを検証するサンプルです。これは Moq を使ったほうが簡単そうですね:
[Test]
[ExpectedException(typeof(MockException))]
public void Prig_should_assert_property_set()
{
using (new IndirectionsContext())
{
// Arrange
var fooProxy = new PProxyFoo();
var fooPropSetMock = new Mock<IndirectionAction<Foo, string>>(MockBehavior.Strict);
fooPropSetMock.Setup(_ => _(fooProxy, "ping"));
fooProxy.FooPropSetString().Body = fooPropSetMock.Object;
var foo = (Foo)fooProxy;
// Act, Assert
foo.FooProp = "foo";
}
}
view raw 05_10.cs hosted with ❤ by GitHub

Telerik.JustMock.Behavior.Strict は、Moq.MockBehavior.Strict にあたります。従って、Setup で指定した条件を満たさない引数で対象のメンバーを呼び出した場合、MockException をスローするようになります。





Assert Method Overloads
各 final メソッドのオーバーロードを入れ替えてみましょう。間接スタブ設定を正しく追加していれば、特に迷うことは無いはずです:
[Test]
public void Prig_should_assert_on_method_overload()
{
using (new IndirectionsContext())
{
// Arrange
var fooProxy = new PProxyFoo();
fooProxy.ExecuteInt32().Body = (@this, arg1) => arg1;
fooProxy.ExecuteInt32Int32().Body = (@this, arg1, arg2) => arg1 + arg2;
var foo = (Foo)fooProxy;
// Act, Assert
Assert.AreEqual(1, foo.Execute(1));
Assert.AreEqual(2, foo.Execute(1, 1));
}
}
view raw 05_11.cs hosted with ❤ by GitHub

特に問題は無いですよね?次に進みましょう。





Assert Method Callbacks
イベントとしてのコールバックを置換することを説明する例です。Moles の例で紹介しましたが、実装には若干のテクニックが必要です:
[Test]
public void Prig_should_assert_on_method_callbacks()
{
using (new IndirectionsContext())
{
// Arrange
var handler = default(Foo.EchoEventHandler);
var fooProxy = new PProxyFoo();
fooProxy.AddOnEchoCallbackEchoEventHandler().Body = (@this, value) => handler += value;
fooProxy.EchoInt32().Body = (@this, arg1) => { handler(true); return arg1; };
var foo = (Foo)fooProxy;
var called = false;
foo.OnEchoCallback += echoed => called = echoed;
// Act
foo.Echo(10);
// Assert
Assert.IsTrue(called);
}
}
view raw 05_12.cs hosted with ❤ by GitHub

実装は以下の流れになります:
  1. fooProxy.AddOnEchoCallbackEchoEventHandler().Body = (@this, value) => handler += value; のように、入れ替えたいイベントの += 演算子を乗っ取り、渡されたハンドラ(value)をテストのためのデリゲート(handler)に紐付ける。
  2. 元のイベントを発行したいタイミングで、手順 1 で紐づけたデリゲート(handler)を代わりに実行する。
これで、元のイベントを発火することと同じ効果を得ることができるようになります。





Assert Generic Types and Methods
ジェネリックな型とメソッドを入れ替える例です:
[Test]
public void Prig_should_assert_on_generic_types_and_method()
{
using (new IndirectionsContext())
{
// Arrange
var expected = "ping";
var fooGenericProxy = new PProxyFooGeneric();
fooGenericProxy.EchoOfTOfTRetT<string, string>().Body = (@this, s) => s;
var fooGeneric = (FooGeneric)fooGenericProxy;
// Act
var actual = fooGeneric.Echo<string, string>(expected);
// Assert
Assert.AreEqual(expected, actual);
}
}
view raw 05_13.cs hosted with ❤ by GitHub

ね、簡単でしょう?これに関しては、以前の記事で解説したジェネリックのサポートも、合わせてご覧いただければと思います。



0 件のコメント:

コメントを投稿