2014年12月8日月曜日

非公開メソッドの入れ替え - from "Prig: Open Source Alternative to Microsoft Fakes" Wiki -

ソフトウェアテストあどべんとかれんだー2014 8日目!&まとまってきたドキュメントを日本語の記事にもしておくよシリーズ第 9 段!(元ネタ:Prigwiki より、FEATURES: Non Public Method Replacement。同シリーズの他記事:1 2 3 4 5 6 7 8

はじめましての方ははじめまして!ソフトウェアテストあどべんとかれんだー2014 8日目を担当させていただきます、@urasandesu こと杉浦と申します。

前日は、@hayabusa333 さんの、Ruby - Gauntltによるセキュリティテスト #SWTestAdvent - Qiita でしたね。セキュリティテスト自動化フレームワーク、そんなものもあるのだと興味深く拝読させていただきました。2014 年は、Heartbleed や、Apache Struts の脆弱性ShellshockPOODLE と、脆弱性の話題に事欠かない年になってしまいましたが、今後セキュリティに関するテストはますます重要になっていくんでしょうね。

さて、ソフトウェアテストに関連することということで、私からは打って変わって実装寄りのお話を。以前から私が作成しています Prig という、.NET 自動ユニットテスト向け迂廻路生成ライブラリについて、紹介させていただこうと思います。迂廻路生成?と思われるかもしれませんが、簡単に言うと、通常は行えない static メソッドや private メソッドの上書きをできるようにするというものです(.NET だと Microsoft Fakes、Java だと JMockit とかが有名どころでしょうか)。

題材は「既存ライブラリの非公開メソッドが絡む自動ユニットテスト」。言語は C# です。よく言われる通り、非公開なメソッドそのものをテストすることはよくないこととされていますが、テストで非公開なメソッドに対して何かしたくなることは、しばしばあるんじゃないでしょうか?例えば、テスト対象のメソッド内で使われる private な setter を持つプロパティに意味のある値を与えておきたいWeb にアクセスしにいってしまう private メソッドをモックに入れ替えたい、などなど。まあ、今まさに開発中のコンポーネントであれば、いくらでも対処方法はあるのですが、すでに稼働しているシステムだったり、外部から買い入れたコンポーネントだったりすると、途端に難易度が跳ね上がるのが困りもの。

ところで、C# の特徴的な機能の 1 つに、今から 7 年ほど前に出た C# 3.0 で追加された、拡張メソッドという機能があります。皆さんは拡張メソッドは好きですか?乱用するべきではないですが、その機能が、本質的に、そのライブラリがそのレイヤーでサポートしてほしいものであれば、設計上自然な API を実現できることがあるかと、私は思います。ただし、そのライブラリが、そのような拡張に対してオープンであるかどうかは、場合によるでしょう。特に、そのシグネチャに、非公開な属性の 1 つである internal なクラスが現れるようなメソッドが関係する場合は要注意。今回は、Prig によって、どのようにこの問題を解決するかを解説したいと思います。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
How to mock ConfigurationManager.AppSettings with moq - Stack Overflow
TDD, Unit testing and Microsoft Fakes with Sitecore Solutions
Basic mocking techniques - Stack Overflow
Hybrid Framework - http://our.umbraco.org
Unit testing Umbraco 7 | just this guy
How to Write 3v1L, Untestable Code
Generic Methods Implementation in Microsoft Fakes - CodeProject
Paulo Morgado - Mastering Expression Trees With .NET Reflector
Expression Tree Visualizer for VS 2010 - Home
mocking - Using Microsoft Fakes Framework with VSTO Application-Level Add-in an XML based Ribbon - Stack Overflow





目次

非公開メソッドの入れ替え
既存ライブラリに、以下のような DTO 群があるとしましょう:
class ULTableStatus
{
internal bool IsOpened = false;
internal int RowsCount = 0;
}
public class ULColumn
{
public ULColumn(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class ULColumns : IEnumerable
{
ULTableStatus m_status;
List<ULColumn> m_columns = new List<ULColumn>();
internal ULColumns(ULTableStatus status)
{
m_status = status;
}
public void Add(ULColumn column)
{
ValidateState(m_status);
m_columns.Add(column);
}
public void Remove(ULColumn column)
{
ValidateState(m_status);
m_columns.Remove(column);
}
public IEnumerator GetEnumerator()
{
return m_columns.GetEnumerator();
}
static void ValidateState(ULTableStatus status)
{
if (!status.IsOpened)
throw new InvalidOperationException("The column can not be modified because owner table has not been opened.");
if (0 < status.RowsCount)
throw new ArgumentException("The column can not be modified because some rows already exist.");
}
}
public class ULTable
{
ULTableStatus m_status = new ULTableStatus();
public ULTable(string tableName)
{
TableName = tableName;
Columns = new ULColumns(m_status);
}
public string TableName { get; private set; }
public ULColumns Columns { get; private set; }
public void Open(string connectionString)
{
// ここで DB に接続し、このクラスへスキーマ情報を設定する。
...(snip)...
// 「準備完了」を表すフラグを立てる。
m_status.IsOpened = true;
}
}
view raw 09_01.cs hosted with ❤ by GitHub

「DB への接続」という副作用と、「テーブル自体のデータ」という状態を 1 つのクラスで管理しており、嫌な臭いを感じます。ただ、このライブラリを作ったニンゲンが、これ以上のリファクタリングをするモチベーションを持つことはないかもしれません。なぜならば、このライブラリだけ見れば internal なクラスが緩衝材としてあり、InternalsVisibleToAttribute を使えば、制限なくそのクラスにアクセスができるため、テストをするのに特に問題を感じないでしょうから。

さて、このライブラリはテーブルスキーマの自動生成ツールも提供しており、特定の列を自動生成してくれます。そのような列は、以下のような規約で命名されるとのことです:
  • <table name> + _ID ・・・ プライマリキー
  • DELETED ・・・ 論理削除フラグ
  • CREATED ・・・ 作成日時
  • MODIFIED ・・・ 更新日時
なるほどなるほど。そうすると「自動生成された列だけを取得する」や「手動で生成された列だけを取得する」などのようなことがやりたくなりますね。残念ながら、既存のライブラリは、そのような機能を提供していないとのこと。なので、今回は自分で作成することにしました。こんなシチュエーションでは拡張メソッドがピッタリでしょう。テストを書いてみます:
[TestFixture]
public class ULTableMixinTest
{
[Test]
public void GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated()
{
// Arrange
var expected = new[] { new ULColumn("USER_ID"), new ULColumn("DELETED"), new ULColumn("CREATED"), new ULColumn("MODIFIED") };
var users = new ULTable("USER");
users.Columns.Add(expected[0]);
users.Columns.Add(new ULColumn("PASSWORD"));
users.Columns.Add(new ULColumn("USER_NAME"));
users.Columns.Add(expected[1]);
users.Columns.Add(expected[2]);
users.Columns.Add(expected[3]);
// Act
var actual = users.GetAutoGeneratedColumns();
// Assert
CollectionAssert.AreEqual(expected, actual);
}
}
view raw 09_02.cs hosted with ❤ by GitHub

テーブル USER には、列 USER_ID、PASSWORD、USER_NAME、DELETED、CREATED、MODIFIED があるとします。そのテーブルに対し、拡張メソッド GetAutoGeneratedColumns を実行すると、自動生成された列が取得できるという寸法です。このままではビルドすら通りませんので、とりあえず以下のような最低限の実装を用意しました:
public static class ULTableMixin
{
public static IEnumerable<ULColumn> GetAutoGeneratedColumns(this ULTable @this)
{
throw new NotImplementedException();
}
}
view raw 09_03.cs hosted with ❤ by GitHub

ほい、実行っと。NotImplementedException がスローされるでしょうから、とりあえずなコードを追記し・・・て・・・あれ?
PM> & "C:\Program Files (x86)\NUnit 2.6.3\bin\nunit-console.exe" MyLibraryTest.dll /domain=None /framework=v4.0
NUnit-Console version 2.6.3.13283
Copyright (C) 2002-2012 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.
Runtime Environment -
OS Version: Microsoft Windows NT 6.2.9200.0
CLR Version: 2.0.50727.8009 ( Net 3.5 )
ProcessModel: Default DomainUsage: None
Execution Runtime: v4.0
.F
Tests run: 1, Errors: 1, Failures: 0, Inconclusive: 0, Time: 0.284553726293366 seconds
Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0
Errors and Failures:
1) Test Error : MyLibraryTest.Mixins.UntestableLibrary.ULTableMixinTest.GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated
System.InvalidOperationException : The column can not be modified because owner table has not been opened.
at UntestableLibrary.ULColumns.ValidateState(ULTableStatus status)
at UntestableLibrary.ULColumns.Add(ULColumn column)
at MyLibraryTest.Mixins.UntestableLibrary.ULTableMixinTest.GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated()
PM>
view raw 09_04.ps1 hosted with ❤ by GitHub

ヴァー!変なとこで引っかかっとる!! ('A`)

実は、スタックトレースにも出力されているように、ULColumns は、メソッド ValidateState を使うことによって、列が変更可能かどうかを検証しています。テストケースにおいては、GetAutoGeneratedColumns を検証したいのですが、その前、users.Columns.Add(expected[0]); で例外がスローされていたわけですね。これはいけません・・・。

このような状況で、Prig を使うことで、不必要な検証を一時的に外すことができます。Prig をインストールし、その Assembly のスタブ設定を追加します:
PM> Add-PrigAssembly -AssemblyFrom <ULColumns の Assembly へのフルパス。例えば、"C:\Users\User\NonPublicUntestable\UntestableLibrary\bin\Debug\UntestableLibrary.dll">
view raw 09_05.ps1 hosted with ❤ by GitHub

以下のコマンドを実行し、ULColumns の設定をクリップボードにコピーします:
PM> Add-Type -Path <ULColumns の Assembly へのフルパス。例えば、"C:\Users\User\NonPublicUntestable\UntestableLibrary\bin\Debug\UntestableLibrary.dll">
PM> Find-IndirectionTarget ([<ULColumns のフルネーム。例えば、UntestableLibrary.ULColumns>]) ValidateState | Get-IndirectionStubSetting | Clip
view raw 09_06.ps1 hosted with ❤ by GitHub

そうしましたら、追加されたスタブ設定ファイル(例:UntestableLibrary.v4.0.30319.v1.0.0.0.prig)にそれを貼り付け、ソリューションをビルドします:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="prig" type="Urasandesu.Prig.Framework.PilotStubberConfiguration.PrigSection, Urasandesu.Prig.Framework" />
</configSections>
<prig>
<stubs>
<add name="ValidateStateULTableStatus" alias="ValidateStateULTableStatus">
<RuntimeMethodInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema" z:Id="1" z:FactoryType="MemberInfoSerializationHolder" z:Type="System.Reflection.MemberInfoSerializationHolder" z:Assembly="0" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="http://schemas.datacontract.org/2004/07/System.Reflection">
<Name z:Id="2" z:Type="System.String" z:Assembly="0" xmlns="">ValidateState</Name>
<AssemblyName z:Id="3" z:Type="System.String" z:Assembly="0" xmlns="">UntestableLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</AssemblyName>
<ClassName z:Id="4" z:Type="System.String" z:Assembly="0" xmlns="">UntestableLibrary.ULColumns</ClassName>
<Signature z:Id="5" z:Type="System.String" z:Assembly="0" xmlns="">Void ValidateState(UntestableLibrary.ULTableStatus)</Signature>
<Signature2 z:Id="6" z:Type="System.String" z:Assembly="0" xmlns="">System.Void ValidateState(UntestableLibrary.ULTableStatus)</Signature2>
<MemberType z:Id="7" z:Type="System.Int32" z:Assembly="0" xmlns="">8</MemberType>
<GenericArguments i:nil="true" xmlns="" />
</RuntimeMethodInfo>
</add>
</stubs>
</prig>
</configuration>
view raw 09_07.ps1 hosted with ❤ by GitHub

上の準備が終わったら、以下のようにテストを書き直すことができるようになります:
[Test]
public void GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated()
{
using (new IndirectionsContext())
{
// Arrange
// Prig を使い、副作用に依存する検証を抑制するため、メソッド本体を入れ替えます。
PULColumns.ValidateStateULTableStatus().Body = args => null;
var expected = new[] { new ULColumn("USER_ID"), new ULColumn("DELETED"), new ULColumn("CREATED"), new ULColumn("MODIFIED") };
var users = new ULTable("USER");
users.Columns.Add(expected[0]);
users.Columns.Add(new ULColumn("PASSWORD"));
users.Columns.Add(new ULColumn("USER_NAME"));
users.Columns.Add(expected[1]);
users.Columns.Add(expected[2]);
users.Columns.Add(expected[3]);
// Act
var actual = users.GetAutoGeneratedColumns();
// Assert
CollectionAssert.AreEqual(expected, actual);
}
}
view raw 09_08.cs hosted with ❤ by GitHub

こんどはどうでしょう?
PM> prig run -process "C:\Program Files (x86)\NUnit 2.6.3\bin\nunit-console.exe" -arguments "MyLibraryTest.dll /domain=None /framework=v4.0"
NUnit-Console version 2.6.3.13283
Copyright (C) 2002-2012 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.
Runtime Environment -
OS Version: Microsoft Windows NT 6.2.9200.0
CLR Version: 2.0.50727.8009 ( Net 3.5 )
ProcessModel: Default DomainUsage: None
Execution Runtime: v4.0
.F
Tests run: 1, Errors: 1, Failures: 0, Inconclusive: 0, Time: 1.07656140775011 seconds
Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0
Errors and Failures:
1) Test Error : MyLibraryTest.Mixins.UntestableLibrary.ULTableMixinTest.GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated
System.NotImplementedException : The method or operation is not implemented.
at MyLibrary.Mixins.UntestableLibrary.ULTableMixin.GetAutoGeneratedColumns(ULTable this)
at MyLibraryTest.Mixins.UntestableLibrary.ULTableMixinTest.GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated()
PM>
view raw 09_09.ps1 hosted with ❤ by GitHub

よし!今度は NotImplementedException がスローされるという、意図した結果になりました。後は、テストを通すコードを書き、全てのテストが通ったらリファクタリングをし、新たなテストを追加する、という黄金の回転を回すだけ。良い感じじゃないですか?





付録
ところで、かの人が元のライブラリをどのように設計していれば、もっと簡単にテストができていたと思いますか?そもそも、状態を副作用から分離したライブラリとして再設計すべきだとは思いますが、既存のライブラリで、インターフェイスを変更するような再設計は難しいでしょうね。せめてなにかできることがあるとすれば、非 public なインターフェイスを public にするような変更ぐらいでしょう。

なお、次の点には注意してください:単にインターフェイスを公開する、例えば、ULTableStatus の全てのフィールドと、ULColumns のコンストラクタ .ctor(ULTableStatus) を公開するようなことをしてしまえば、簡単にデータの不整合が起きるようになってしまい、ライブラリが安全ではなくなってしまいます。ライブラリの安全性が保たれる範囲のインターフェイスだけを公開するべきでしょう。このケースでは、以下のような変更が考えられます:
...(snip)...
public class ULColumns : IEnumerable
{
List<ULColumn> m_columns = new List<ULColumn>();
IValidation<ULColumns> m_val;
public ULColumns(IValidation<ULColumns> val)
{
if (val == null)
throw new ArgumentNullException("val");
m_val = val;
}
public void Add(ULColumn column)
{
m_val.Validate(this);
m_columns.Add(column);
}
public void Remove(ULColumn column)
{
m_val.Validate(this);
m_columns.Remove(column);
}
...(snip)...
internal static IValidation<ULColumns> GetDefaultValidation(ULTableStatus status)
{
return new ColumnsVariabilityValidator(status);
}
class ColumnsVariabilityValidator : IValidation<ULColumns>
{
readonly ULTableStatus m_status;
public ColumnsVariabilityValidator(ULTableStatus status)
{
m_status = status;
}
public void Validate(ULColumns t)
{
if (!m_status.IsOpened)
throw new InvalidOperationException("The column can not be modified because owner table has not been opened.");
if (0 < m_status.RowsCount)
throw new ArgumentException("The column can not be modified because some rows already exist.");
}
}
}
public class ULTable
{
ULTableStatus m_status = new ULTableStatus();
public ULTable(string tableName)
{
TableName = tableName;
}
...(snip)...
ULColumns m_columns;
public virtual ULColumns Columns
{
get
{
if (m_columns == null)
m_columns = new ULColumns(ULColumns.GetDefaultValidation(m_status));
return m_columns;
}
}
...(snip)...
}
public interface IValidation<T>
{
void Validate(T obj);
}
view raw 09_10.cs hosted with ❤ by GitHub

ULColumns のコンストラクタは公開しましたが、ULTableStatus を直接指定することはせず、検証のためのメソッドだけを持ったインターフェイス IValidation を代わりに指定するようにしました。Prig を使って入れ替えたかったメソッド ValidateState が持つ機能を、外出ししたことになります。対象のメソッドは状態にアクセスはしますが、それを書き換えることはしません。従って、その部分を公開するだけであれば、ライブラリのデータの整合性は保ち続けられることになります。それから、ULTable の ULColumns を生成するプロパティを virtual 化します。

これらの再設計により、Prig を使わなければキーとなるメソッドに到達することすら難しかったテストは、以下のようにできます。Moq のような通常のモックフレームワークで、簡単にテストができるようになるのです:
[Test]
public void GetAutoGeneratedColumns_should_return_columns_that_are_auto_generated()
{
// Arrange
// Moq を使い、副作用に依存する検証を抑制するため、メソッド本体を入れ替えます。
var usersMock = new Mock<ULTable>("USER");
usersMock.Setup(_ => _.Columns).Returns(new ULColumns(new Mock<IValidation<ULColumns>>().Object));
var expected = new[] { new ULColumn("USER_ID"), new ULColumn("DELETED"), new ULColumn("CREATED"), new ULColumn("MODIFIED") };
var users = usersMock.Object;
users.Columns.Add(expected[0]);
users.Columns.Add(new ULColumn("PASSWORD"));
users.Columns.Add(new ULColumn("USER_NAME"));
users.Columns.Add(expected[1]);
users.Columns.Add(expected[2]);
users.Columns.Add(expected[3]);
// Act
var actual = users.GetAutoGeneratedColumns();
// Assert
CollectionAssert.AreEqual(expected, actual);
}
view raw 09_11.cs hosted with ❤ by GitHub





終わりに
ソフトウェアテストあどべんとかれんだー2014 8日目、C# と自作ライブラリを題材に、自動ユニットテストにおける非公開メソッドの入れ替えを解説してみました。つい先月、V1.0.0 をリリースしたばかりということもあり、まだまだ問題もあるかと思いますが、もし興味を持っていただき、使っていただければ嬉しいです!問題などあれば、是非 @urasandesu 宛てにお気軽に mention 下さいませ (((o(*゚▽゚*)o)))

さて、私のまとまってきたドキュメントを日本語の記事にもしておくよシリーズ(1 2 3 4 5 6 7 8)はこれで終わりですが、ソフトウェアテストあどべんとかれんだー2014 はまだまだ続きますのでお見逃しなく!

明日は、@PoohSunny さん。よろしくどうぞ!!

2014年12月6日土曜日

移行サンプル:Typemock Isolator による MessageBox を使うテストのモック化 - from "Prig: Open Source Alternative to Microsoft Fakes" Wiki -

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

このシリーズも残り僅か。今回もよろしくお願いします。Typemock は、Isolator の Quick Start で、MessageBox をモックに入れ替えるサンプルを紹介しています。これも Prig(と Moq)に移行することが可能です。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
Testing code that rely on Microsoft Azure Management Libraries using Microsoft Fakes
c# - The type is defined in an assembly that is not referenced, how to find the cause - Stack Overflow
.net - Mircosoft fakes - shims without ShimsContext - Stack Overflow
Unit Test for ShimDataTableCollection Count
c# - How to know if a MemberInfo is an explicit implementation of a property - Stack Overflow
#5816 (any_range requires copyable elements) – Boost C++ Libraries
#10360 (Since 1.56, any_range use static cast of reference instead of implicit conversion) – Boost C++ Libraries
#10493 (Since 1.56, any_range with non-reference references can cause UB) – Boost C++ Libraries
hunting bugs with git bisect and submodules - Least Significant Bit
AdventCalendar - git bisect で問題箇所を特定する - Qiita
便利!電動歯ブラシ | Boost.勉強会 #16 大阪





目次

準備
まずは、間接設定を作成する必要があります。Package Manager Console を開き、Default project: をテストプロジェクトに変更してください。その後、以下のコマンドを実行します:
PM> dir
Directory: C:\users\akira\documents\visual studio 2013\Projects\IsolatorMigrationDemo
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2014/10/10 9:31 IsolatorMigrationDemo
d---- 2014/10/10 9:37 IsolatorMigrationDemoTest
d---- 2014/10/10 9:37 packages
-a--- 2014/10/10 9:32 1561 IsolatorMigrationDemo.sln
PM> cd .\IsolatorMigrationDemo\bin\Debug
PM> dir
Directory: C:\users\akira\documents\visual studio 2013\Projects\IsolatorMigrationDemo\IsolatorMigrationDemo\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/10/10 9:31 7680 IsolatorMigrationDemo.exe
-a--- 2014/10/10 9:30 189 IsolatorMigrationDemo.exe.config
-a--- 2014/10/10 9:31 28160 IsolatorMigrationDemo.pdb
-a--- 2014/10/10 9:30 23168 IsolatorMigrationDemo.vshost.exe
-a--- 2014/10/10 9:30 189 IsolatorMigrationDemo.vshost.exe.config
-a--- 2013/06/18 21:28 490 IsolatorMigrationDemo.vshost.exe.manifest
PM> padd -af (dir .\IsolatorMigrationDemo.exe).FullName
PM> padd -as "System.Windows.Forms, Version=4.0.0.0"
PM>
view raw 08_01.cs hosted with ❤ by GitHub

次に、Isolator のサンプルで使用しているメソッドのための間接設定を取得しましょう。PowerShell(コンソール)を開き、情報を取得するために以下のコマンドを実行します:
PS> $pwd
Path
----
C:\Users\Akira\Documents\Visual Studio 2013\Projects\IsolatorMigrationDemo\IsolatorMigrationDemo\bin\Debug
PS> powershell
Windows PowerShell
Copyright (C) 2013 Microsoft Corporation. All rights reserved.
PS> ipmo "C:\Users\Akira\Documents\Visual Studio 2013\Projects\IsolatorMigrationDemo\packages\Prig.1.0.0\tools\Urasandesu.Prig"
PS> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\IsolatorMigrationDemo\IsolatorMigrationDemo\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/10/10 9:31 7680 IsolatorMigrationDemo.exe
-a--- 2014/10/10 9:30 189 IsolatorMigrationDemo.exe.config
-a--- 2014/10/10 9:31 28160 IsolatorMigrationDemo.pdb
-a--- 2014/10/10 9:30 23168 IsolatorMigrationDemo.vshost.exe
-a--- 2014/10/10 9:30 189 IsolatorMigrationDemo.vshost.exe.config
-a--- 2013/06/18 21:28 490 IsolatorMigrationDemo.vshost.exe.manifest
PS> $asmInfo = [System.Reflection.Assembly]::LoadFrom((dir .\IsolatorMigrationDemo.exe).FullName)
PS> $asmInfo.GetTypes()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False Form1 System.Windows.Forms.Form
False False Program System.Object
True False SomeClass System.Object
True False UserOfSomeClass System.Object
False False Resources System.Object
False False Settings System.Configuration.ApplicationSettingsBase
PS> $asmInfo.GetTypes() | ? { $_.Name -match 'Class' }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False SomeClass System.Object
True False UserOfSomeClass System.Object
PS> $asmInfo.GetTypes() | ? { $_.Name -match 'Class' } | pfind
Method
------
Void MyMethod()
Void .ctor()
Void DoSomething()
Void .ctor()
PS> $asmInfo.GetTypes() | ? { $_.Name -match 'Class' } | pfind | pget | clip # この結果については、IsolatorMigrationDemo.v4.0.30319.v1.0.0.0.prig に貼り付けてください。
PS> $asmInfo = [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
PS> $asmInfo.GetTypes() | ? { $_.Name -eq 'messagebox' }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False MessageBox System.Object
PS> $asmInfo.GetTypes() | ? { $_.Name -eq 'messagebox' } | pfind -m 'show\(system\.string\)'
Method
------
System.Windows.Forms.DialogResult Show(System.String)
PS> $asmInfo.GetTypes() | ? { $_.Name -eq 'messagebox' } | pfind -m 'show\(system\.string\)' | pget | clip # この結果については、System.Windows.Forms.v4.0.30319.v4.0.0.0.prig に貼り付けてください。
PS> exit
PS>
view raw 08_02.ps1 hosted with ❤ by GitHub

Visual Studio に戻り、IsolatorMigrationDemo.v4.0.30319.v1.0.0.0.prig と System.Windows.Forms.v4.0.30319.v4.0.0.0.prig に各々の間接設定を貼り付けます。ビルドが成功したら、サンプルを移行していきますよ!





Example Test 1 - Simple test using MessageBox
Isolator は、プロファイリング API による強力なメソッドの入れ替え機能に加え、JustMock と同様、Mock Object を生成する機能を持っています。Prig はそのような機能をサポートしていませんが、最初に説明した通りMoq と連携することで、それを実現することができましたね。
[Test]
public void MessageBoxShow_should_be_callable_indirectly()
{
using (new IndirectionsContext())
{
// Arrange
var mockMessageBox = new Mock<IndirectionFunc<string, DialogResult>>();
mockMessageBox.Setup(_ => _(string.Empty)).Returns(DialogResult.OK);
PMessageBox.ShowString().Body = mockMessageBox.Object;
// Act
MessageBox.Show("This is a message");
// Assert
mockMessageBox.Verify(_ => _("This is a message"));
}
}
view raw 08_03.cs hosted with ❤ by GitHub

Isolate.WhenCalled は、Prig の間接スタブ(この場合、PMessageBox.ShowString().Body)に、Moq.Mock.Setup でセットアップした Mock Object を割り当てることで、置き換えることができます。Isolate.Verify.WasCalledWithExactArguments は、Moq.Mock.Verify と機能的に同じですね。問題は無いでしょう。次へ行きますよ!





Example Test 2 - Complex Test
「複雑な」と付いていますが、そう難しいものではありません ( ̄ー ̄)
[Test]
public void UserOfSomeClassDoSomething_should_show_MessageBox_if_an_exception_is_thrown()
{
using (new IndirectionsContext())
{
// Arrange
PSomeClass.MyMethod().Body = () => { throw new Exception("foo"); };
var mockMessageBox = new Mock<IndirectionFunc<string, DialogResult>>();
mockMessageBox.Setup(_ => _(string.Empty)).Returns(DialogResult.OK);
PMessageBox.ShowString().Body = mockMessageBox.Object;
// Act
var user = new UserOfSomeClass();
user.DoSomething();
// Assert
mockMessageBox.Verify(_ => _("Exception caught: foo"));
}
}
view raw 08_04.cs hosted with ❤ by GitHub

特別な条件が無いのであれば、Isolate.WhenCalled(..).WillThrow は、Prig の間接スタブ(この場合、PSomeClass.MyMethod().Body)に、直接例外をスローする関数を割り当てることで、置き換えることができます。Isolate.WhenCalled(..).WillReturn や Isolate.Verify.WasCalledWithExactArguments は、前に説明しましたので・・・おっと、これで全部です!

ちなみに、対象が、MessageBox 処理があるにも関わらずテストコードを書きたくなるほど複雑な場合、設計をしくじっている可能性が高いと、個人的には思います。既存のコードやレガシーコードに対しては仕方がないでしょうが、こんなライブラリのような闇の力を、新規のプロダクトコードには使わなくて済むことを願いたいものですね (^^ゞ



2014年12月4日木曜日

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

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

3 回に渡る JustMock の公式ドキュメントにあるサンプルを実装するシリーズ、Part 3 となります。最後は、「Static Mocking samples」を Prig に移行してみましょう。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
How often are fakes assemblies generated? - Stack Overflow
Brownfield Development: Taming Legacy Code with Better Unit Testing and Microsoft Fakes
Nested Types in Generic Classes - Haibo Luo's weblog - MSDN Blogs
c# - Behavedbase in fakes - Stack Overflow
CLR Profiler - Documentation
Resize image in the wiki of github usin markdown - Stack Overflow
Behind iPhone's Critical Security Bug, a Single Bad 'Goto' WIRED
microsoft fakes only stub static property of a static class - Stack Overflow
Home Page - Code Impact - .NET Community Event
PowerShell で SIGPIPE 連鎖 - NyaRuRuが地球にいたころ
Generics and Your Profiler - David Broman's CLR Profiling API Blog - Site Home - MSDN Blogs





目次

準備
このサンプルでは、シリーズということもあり、1 つのソリューションに複数のプロジェクトを追加するという構成を採っています(前回前々回の記事もご参照くださいませ)。これまでのサンプルと同様、Package Manager Console と PowerShell を使って説明を続けたいと思います。各使用コマンドは以前のサンプルで解説していますので、詳細はそちらもご覧ください。

さて、間接スタブ設定を作成しましょう。Package Manager Console を開き、Default project: をテストプロジェクトに変更します
PM> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2014/09/17 19:34 FinalMockingMigration
d---- 2014/09/23 16:55 FinalMockingMigrationTest
d---- 2014/09/28 8:46 packages
d---- 2014/09/27 6:45 SealedMockingMigration
d---- 2014/09/27 7:06 SealedMockingMigrationTest
d---- 2014/09/16 20:37 StaticMockingMigration
d---- 2014/09/21 11:35 StaticMockingMigrationTest
-a--- 2014/09/16 6:31 3665 JustMockMigrationDemo.sln
PM> cd .\StaticMockingMigration\bin\Debug
PM> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\StaticMockingMigration\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/09/25 6:18 5120 StaticMockingMigration.exe
-a--- 2014/09/16 6:31 189 StaticMockingMigration.exe.config
-a--- 2014/09/25 6:18 15872 StaticMockingMigration.pdb
PM> padd -af (dir .\StaticMockingMigration.exe).FullName
PM> padd -as "System.Web, Version=4.0.0.0"
PM>
view raw 07_01.ps1 hosted with ❤ by GitHub

次に、Assembly を解析します。PowerShell(コンソール)を開き、以下のコマンドで情報を取得します:
PS> $pwd
Path
----
C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\StaticMockingMigration\bin\Debug
PS> powershell
Windows PowerShell
Copyright (C) 2013 Microsoft Corporation. All rights reserved.
PS> ipmo "C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\packages\Prig.1.0.0\tools\Urasandesu.Prig"
PS> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\StaticMockingMigration\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/09/28 11:51 5632 StaticMockingMigration.exe
-a--- 2014/09/16 6:31 189 StaticMockingMigration.exe.config
-a--- 2014/09/28 11:51 17920 StaticMockingMigration.pdb
PS> $asmInfo = [System.Reflection.Assembly]::LoadFrom((dir .\StaticMockingMigration.exe).FullName)
PS> $asmInfo.GetTypes()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False Foo System.Object
False False FooInternal System.Object
True False FooStatic System.Object
True False Bar System.Object
True False BarExtensions System.Object
False False Program System.Object
PS> $asmInfo.GetTypes() | pfind
Method
------
Void Submit()
Int32 Execute(Int32)
Int32 get_FooProp()
Void set_FooProp(Int32)
Void .cctor()
Void .ctor()
Void DoIt()
Void .ctor()
Void Do()
Void Execute()
Void .ctor()
Int32 Echo(StaticMockingMigration.Bar, Int32)
Void Main(System.String[])
Void .ctor()
PS> $asmInfo.GetTypes() | pfind | pget | clip # この結果については、StaticMockingMigration.v4.0.30319.v1.0.0.0.prig に貼り付けてください。
PS> $asmInfo = [System.Reflection.Assembly]::LoadWithPartialName("System.Web")
PS> $asmInfo.GetTypes() | ? { $_.Name -eq 'httpcontext' }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False HttpContext System.Object
PS> $asmInfo.GetTypes() | ? { $_.Name -eq 'httpcontext' } | pfind -m 'get_current\b'
Method
------
System.Web.HttpContext get_Current()
PS> $asmInfo.GetTypes() | ? { $_.Name -eq 'httpcontext' } | pfind -m 'get_current\b' | pget | clip # この結果については、System.Web.v4.0.30319.v4.0.0.0.prig に貼り付けてください。
PS> exit
PS>
view raw 07_02.ps1 hosted with ❤ by GitHub

Visual Studio に戻り、各間接設定 StaticMockingMigration.v4.0.30319.v1.0.0.0.prig、System.Web.v4.0.30319.v4.0.0.0.prig に貼り付けます。ビルドは通りましたか?さあ、実際に移行してみましょう!





Static Constructor Mocking
静的コンストラクタの間接スタブは、文字通り StaticConstructor という名前になります:
[Test]
public void Prig_should_arrange_static_function()
{
using (new IndirectionsContext())
{
// Arrange
PFoo.StaticConstructor().Body = () => { };
PFoo.FooPropGet().Body = () => 0;
// Act
var actual = Foo.FooProp;
// Assert
Assert.AreEqual(0, actual);
}
}
view raw 07_03.cs hosted with ❤ by GitHub






General Static Method Mocking
一般的な静的メソッドのモック化です。んー、これは Moq と連携したほうがわかりやすいでしょうね:
[Test]
public void Prig_should_throw_when_not_arranged()
{
using (new IndirectionsContext())
{
// Arrange
PFoo.StaticConstructor().Body = () => { };
var executeMock = new Mock<IndirectionFunc<int, int>>(MockBehavior.Strict);
executeMock.Setup(_ => _(10)).Returns(10);
PFoo.ExecuteInt32().Body = executeMock.Object;
var submitMock = new Mock<IndirectionAction>(MockBehavior.Strict);
PFoo.Submit().Body = submitMock.Object;
// Act, Assert
Assert.AreEqual(10, Foo.Execute(10));
Assert.Throws<MockException>(() => Foo.Submit());
}
}
view raw 07_04.cs hosted with ❤ by GitHub

ところで、個人的には ExpectedException より Assert.Throws のほうが好きだったり。ExpectedException を使うケースだと、意図しない場所で例外が発生しても(例えば、上記の例だと、Foo.Execute(10) が例外をスロー時)、テストは成功してしまいますからね。





Mocking Static Property Get
静的プロパティの getter を入れ替えます。何度か説明してきているように思いますが・・・一応紹介しておきましょう (^_^;)
[Test]
public void Prig_should_fake_static_property_get()
{
using (new IndirectionsContext())
{
// Arrange
PFoo.StaticConstructor().Body = () => { };
var called = false;
PFoo.FooPropGet().Body = () => { called = true; return 1; };
// Act
var actual = Foo.FooProp;
// Assert
Assert.AreEqual(1, actual);
Assert.IsTrue(called);
}
}
view raw 07_05.cs hosted with ❤ by GitHub






Mocking Static Property Set
静的プロパティの setter を入れ替えます:
[Test]
public void Prig_should_fake_static_property_set()
{
using (new IndirectionsContext())
{
// Arrange
PFoo.StaticConstructor().Body = () => { };
var fooPropSetMock = new Mock<IndirectionAction<int>>(MockBehavior.Strict);
fooPropSetMock.Setup(_ => _(10));
PFoo.FooPropSetInt32().Body = fooPropSetMock.Object;
// Act, Assert
Foo.FooProp = 10;
}
}
view raw 07_06.cs hosted with ❤ by GitHub

検証の方法について、JustMock のサンプルとは若干違いがあります。個人的には、MockBehavior.Strict を指定すれば、条件によってメソッドが呼び出されているかどうかを再度検証する必要はないとは思いますね。条件を満たさないメソッドの呼び出しがあれば、自動的に例外がスローされますので。





Mocking Internal Static Call
internal な静的メソッドを入れ替える例です。コメントに残すだけでなく、例外がスローされないことも検証すべきでしょう:
[Test]
public void Prig_should_fake_internal_static_call()
{
using (new IndirectionsContext())
{
// Arrange
PFooInternal.DoIt().Body = () => { };
// Act, Assert
Assert.DoesNotThrow(() => FooInternal.DoIt());
}
}
view raw 07_07.cs hosted with ❤ by GitHub






Mocking Static Class
静的クラスのメソッドを入れ替えるサンプルです。説明すべきことはあまりないですね (^-^;
[Test]
public void Prig_should_mock_static_class()
{
using (new IndirectionsContext())
{
// Arrange
PFooStatic.Do().Body = () => { };
// Act, Assert
Assert.DoesNotThrow(() => FooStatic.Do());
}
}
view raw 07_08.cs hosted with ❤ by GitHub






Mocking Current HttpContext
現在の HTTP コンテキストを入れ替えるサンプルです。本来であれば、元の処理は HTTP リクエスト中にのみ有効なのですが、もはや制限はありません。
[Test]
public void Prig_should_assert_mocking_http_context()
{
using (new IndirectionsContext())
{
// Arrange
var called = false;
PHttpContext.CurrentGet().Body = () => { called = true; return null; };
// Act
var ret = HttpContext.Current;
// Assert
Assert.IsTrue(called);
}
}
view raw 07_09.cs hosted with ❤ by GitHub






Mocking Extension Methods
拡張メソッドの入れ替えです。結局のところ、拡張メソッドは静的メソッドですので、前までのサンプルと同じように入れ替えることができます:
[Test]
public void Prig_should_fake_extension_method()
{
using (new IndirectionsContext())
{
// Arrange
var foo = new Bar();
var echoMock = new Mock<IndirectionFunc<Bar, int, int>>();
echoMock.Setup(_ => _(foo, 10)).Returns(11);
PBarExtensions.EchoBarInt32().Body = echoMock.Object;
// Act
var actual = foo.Echo(10);
// Assert
Assert.AreEqual(11, actual);
}
}
view raw 07_10.cs hosted with ❤ by GitHub




2014年12月1日月曜日

移行サンプル:Telerik JustMock によるモック化② シールされたクラスのモック化 - from "Prig: Open Source Alternative to Microsoft Fakes" Wiki -

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

3 回に渡る JustMock の公式ドキュメントにあるサンプルを実装するシリーズ、Part 2 となります。今回は、「Sealed Mocking samples」を Prig に移行してみましょう。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
Unity を使って AOP - present
How do microsoft fakes' shims actually work internally? - Stack Overflow
c# - Good and free unit-testing alternatives to Telerik's JustMock - Software Recommendations Stack Exchange
Unable to create Fakes for Google APIs - Stack Overflow
Covering basics of unit testing with Typemock - .NET Unit Testing Tips
The Difference Between Unit Tests and Integration Tests - Typemock Isolator
Typemock Isolator Quick Start -
Home - Run Jekyll on Windows
Setup Jekyll on Windows - Yi Zeng
OctopressをWindows7にインストールしてみたメモ by @pon_zu on @Qiita





目次

準備
このサンプルでは、シリーズということもあり、1 つのソリューションに複数のプロジェクトを追加するという構成を採っています。前回のサンプルと同様、Package Manager Console と PowerShell を使って説明を続けたいと思います。各使用コマンドは前回のサンプルで解説していますので、詳細はそちらもご覧ください。

まずは、間接スタブ設定を作成しましょう:
PM> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2014/09/17 19:34 FinalMockingMigration
d---- 2014/09/23 16:55 FinalMockingMigrationTest
d---- 2014/09/25 6:13 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/21 11:35 StaticMockingMigrationTest
-a--- 2014/09/16 6:31 3665 JustMockMigrationDemo.sln
PM> cd .\SealedMockingMigration\bin\Debug
PM> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\SealedMockingMigration\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/09/25 6:18 5120 SealedMockingMigration.exe
-a--- 2014/09/16 6:31 189 SealedMockingMigration.exe.config
-a--- 2014/09/25 6:18 13824 SealedMockingMigration.pdb
PM> padd -af (dir .\SealedMockingMigration.exe).FullName
PM>
view raw 06_01.ps1 hosted with ❤ by GitHub

あー・・・「やっべ!Default project: にテストプロジェクト設定してねえ!変なとこに設定が追加された!!!!」って方。

お気の毒さまですが、Prig はそれを削除するコマンドをサポートしていません(PreBuildEvent 内の解析ェ・・・)。恐縮ですが、手動で *.csproj を元に戻す必要があります。以下のイメージのように、\.prig" にマッチするタグ Reference、None、PreBuildEvent の全てを削除してください:









Git のようなバージョン管理システム下で作業することを強くお勧めしておきます (^q^)

次は、PowerShell(コンソール)による対象メソッドの絞り込みです。慣れれば一気にできるでしょう:
PS> $pwd
Path
----
C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\SealedMockingMigration\bin\Debug
PS> powershell
Windows PowerShell
Copyright (C) 2013 Microsoft Corporation. All rights reserved.
PS> ipmo "C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\packages\Prig.1.0.0\tools\Urasandesu.Prig"
PS> dir
Directory: C:\Users\Akira\Documents\Visual Studio 2013\Projects\JustMockMigrationDemo\SealedMockingMigration\bin\Debug
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2014/09/27 6:45 5120 SealedMockingMigration.exe
-a--- 2014/09/16 6:31 189 SealedMockingMigration.exe.config
-a--- 2014/09/27 6:45 17920 SealedMockingMigration.pdb
PS> $asmInfo = [System.Reflection.Assembly]::LoadFrom((dir .\SealedMockingMigration.exe).FullName)
PS> $asmInfo.GetTypes()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False FooSealed System.Object
True False FooSealedInternal System.Object
True False IFoo
True False Foo System.Object
False False Program System.Object
PS> $asmInfo.GetTypes() | pfind
Method
------
Int32 Echo(Int32)
Void .ctor()
Int32 Echo(Int32)
Void .ctor()
Void Execute()
Void SealedMockingMigration.IFoo.Execute(Int32)
Void .ctor()
Void Main(System.String[])
Void .ctor()
PS> $asmInfo.GetTypes() | pfind | pget | clip
PS> exit
PS>
view raw 06_02.ps1 hosted with ❤ by GitHub

今回は、Moles の例以降、表に出てこなかったコマンド pfind(Find-IndirectionTarget)を使っています。実はこのコマンド、前回までのサンプルで、標準リフレクション API によって掛けていたざっくりとしたフィルタを、既に含んでいます。なので、上記のように手順の簡易化ができるようになるんですね。そうしたら、Visual Studio に戻り、前回と同様 SealedMockingMigration.v4.0.30319.v1.0.0.0.prig に貼り付けます。ビルドは通りましたか?さあ、実際の移行をしてみましょう!





Assert Final Method Call on a Sealed Class
シールされたクラスの final メソッドを置き換えてみましょう:
[Test]
public void Prig_should_assert_final_method_call_on_a_sealed_class()
{
using (new IndirectionsContext())
{
// Arrange
PFooSealed.EchoInt32().Body = (@this, arg1) => 10;
// Act
var actual = new FooSealed().Echo(1);
// Assert
Assert.AreEqual(10, actual);
}
}
view raw 06_03.cs hosted with ❤ by GitHub

特定のインスタンスだけの置き換えるのでない時は、PProxy<original class name> を使うより、P<original class name> を使うほうが簡単かも。





Create Mock for Sealed Class with Internal Constructor
ところで、入れ替える型のコンストラクタが非 public だとどうなるのか、心配になったりします?:
[Test]
public void Prig_should_create_mock_for_a_sealed_class_with_internal_constructor()
{
using (new IndirectionsContext())
{
// Arrange
var fooProxy = new PProxyFooSealedInternal();
fooProxy.EchoInt32().Body = (@this, arg1) => 10;
var foo = (FooSealedInternal)fooProxy;
// Act
var actual = foo.Echo(1);
// Assert
Assert.AreEqual(10, actual);
}
}
view raw 06_04.cs hosted with ❤ by GitHub

はい!何も問題ありませんね!





Create Mock for Sealed Class with Interface
これはインターフェイスを使った振る舞いの例です。まずは直呼び出し:
[Test]
public void Prig_should_assert_call_on_void()
{
using (new IndirectionsContext())
{
// Arrange
var called = false;
PFoo.Execute().Body = @this => called = true;
// Act
new Foo().Execute();
// Assert
Assert.IsTrue(called);
}
}
view raw 06_05.cs hosted with ❤ by GitHub

次に、インターフェイスを通じた呼び出し:
[Test]
public void Prig_should_assert_call_on_void_through_an_interface()
{
using (new IndirectionsContext())
{
// Arrange
var called = false;
PFoo.Execute().Body = @this => called = true;
var foo = new Foo();
// Act
var iFoo = (IFoo)foo;
iFoo.Execute();
// Assert
Assert.IsTrue(called);
}
}
view raw 06_06.cs hosted with ❤ by GitHub

加えて、明示的に実装されたインターフェイスのケースです。これについては、JustMock はサンプルを準備していないようですね。なので、一応念のため説明させていただきます:
[Test]
public void Prig_should_assert_call_on_void_through_an_explict_implemented_interface()
{
using (new IndirectionsContext())
{
// Arrange
var called = false;
PFoo.SealedMockingMigrationIFooExecuteInt32().Body = (@this, arg1) => called = true;
var foo = new Foo();
// Act
var iFoo = (IFoo)foo;
iFoo.Execute(1);
// Assert
Assert.IsTrue(called);
}
}
view raw 06_07.cs hosted with ❤ by GitHub

明示的に実装されたインターフェースを入れ替える時は、間接スタブが特殊な名前になることだけに気を付ければ大丈夫です。通常の名前は、単に <メソッドの間接スタブ名> ですが、この場合の名前は、<名前空間> + <インターフェイスの間接スタブ名> + <メソッドの間接スタブ名> となります。