読者です 読者をやめる 読者になる 読者になる

さすがMoles!Moq たちにできない事を平然とやってのけるッ

.net

はじめに

Jenkins を導入して継続的インテグレーション(以下 CI)を始めてみたものの、作成済みテストの多くは他のテストの事を考えて作成されていないので、テスト環境を破壊するものが結構ありました。このまま常時テストしたところで、常時失敗するだけ。テストを修正する羽目になりましたとさ。トホホ。

既存のテストで気になるのが、単体テストの多くが、実際は結合テストになってしまっているところ。テスト対象外のクラスに意図した動作をさせるための苦労が、テストの前処理と後処理に多く見られました。「モックを使えばいいのに」って何度思ったことか。

Jenkins の導入は良い機会なので、CI を実践するという名目で、モックライブラリも導入することにしました。

ライブラリの選択

ライブラリは、Microsoft Reserch がリリースしている Moles を選択しました。

.NET Framework のクラスの動作さえも差し替えることができる、という強力さが、選択した一番の理由です。これは、Moq や NMock といった他のオープンソースライブラリでは出来ない。

まぁ、Microsoft 製のライブラリやツールだと承認されやすい、という政治的理由もあるんですけどね。

Moles のインストール

Pex のサイトからインストーラーをダウンロードします。Pex は MSDN サブスクリプションが必要ですが、Moles だけなら無料です。

インストーラを実行するだけで完了。

サンプルライブラリプロジェクトを用意

MolesSample というライブラリプロジェクトを作成し、下記のインタフェースやクラスを定義します。

namespace MolesSample
{
    public interface IGreeting
    {
        string Greet(string name);
    }

    public abstract class Greeting : IGreeting
    {
        protected Greeting()
        {
        }

        public abstract string Greet(string name);
    }

    public class Morning : Greeting
    {
        public override string Greet(string name)
        {
            return string.Format("Good morning, {0}.", name);
        }
    }

    public static class GreetingFactory
    {
        public static IGreeting CreateMorning()
        {
            return new Morning();
        }
    }
}

このライブラリプロジェクトのテストを作って、Moles を試してみます。

Moles でモック・スタブを生成

Visual Studio 2010 でソリューションにテストプロジェクト追加し、テスト対象のプロジェクトを参照に追加します。

そして参照設定を右クリックし、メニューから [Add moles assembly for mscolib] を実行。
f:id:griefworker:20110715152547p:image
すると、mscorib.moles というファイルがプロジェクトに追加されます。
f:id:griefworker:20110715152544p:image
中身はこんな感じ。

<Moles xmlns="http://schemas.microsoft.com/moles/2010/">
  <Assembly Name="mscorlib" />
</Moles>

Moles でモック・スタブを作成するアセンブリが指定されていますね。これで、コアライブラリのスタブとモックが生成されるようになります。


また、任意のアセンブリも Moles でモック・スタブが作れます。参照設定の一覧から、目的のアセンブリ(またはプロジェクト)を右クリックし、メニューから [Add Moles Assembly] を実行。
f:id:griefworker:20110715152546p:image
アセンブリ名.moles というファイルがプロジェクトに追加されます。
f:id:griefworker:20110715152545p:image


テストプロジェクトをビルドすると、スタブとモックのソースコードが生成されます。生成されたスタブとモックは、元の名前空間.Moles に定義されています。

生成されたモック・スタブを使ってテスト

インタフェースのスタブを作成

スタブは、元の名前に頭文字 S がついています。今までは、インタフェースを実装したテスト用クラスを作成していましたが、スタブで代用できます。

// モックやスタブを使うテストでは HostType を指定する
[TestMethod]
[HostType("Moles")]
public void インタフェースのスタブを作成()
{
    var stub = new SIGreeting();
    stub.GreetString = name =>
    {
        return string.Format("Hi, {0}.", name);
    };

    var greeting = (IGreeting)stub;
    var result = greeting.Greet("最高");
    Assert.AreEqual("Hi, 最高.", result);
}
抽象クラスのスタブを作成
[TestMethod]
[HostType("Moles")]
public void 抽象クラスのスタブを作成()
{
    var stub = new SGreeting();
    stub.GreetString = name =>
    {
        return string.Format("Hello, {0}.", name);
    };

    var greeting = (Greeting)stub;
    var result = greeting.Greet("秋人");
    Assert.AreEqual("Hello, 秋人.", result);
}
クラスのスタブを作成

クラスのスタブも作れますが、後述のモックがあるので、使う場面は無さそうです。

[TestMethod]
[HostType("Moles")]
public void クラスのスタブを作成()
{
    var stub = new SMorning();
    stub.GreetString = name =>
    {
        return string.Format("おはよう、{0}。", name);
    };

    var morning = (Morning)stub;
    var result = morning.Greet("亜豆");
    Assert.AreEqual("おはよう、亜豆。", result);
}
クラスのモックを作成

モックは元の名前の前に M がついています。

[TestMethod]
[HostType("Moles")]
public void クラスのモックを作成()
{
    var mock = new MMorning();
    mock.GreetString = name =>
    {
        return string.Format("Buongiorno, {0}.", name);
    };

    var morning = (Morning)mock;
    var result = morning.Greet("エイジ");
    Assert.AreEqual("Buongiorno, エイジ.", result);
}

インスタンスの動作を変更する場合は、AllInstance という内部クラスを使います。

[TestMethod]
[HostType("Moles")]
public void クラスのモックを作成2()
{
    MMorning.AllInstances.GreetString = (self, name) =>
    {
        return string.Format("Bonjour, {0}.", name);
    };

    var morning = new Morning();
    var result = morning.Greet("香耶");
    Assert.AreEqual("Bonjour, 香耶.", result);
}

あと、インタフェースのモックは生成されません。また、抽象クラスのモックは生成されてはいますが、コンストラクタで派生クラスのオブジェクトを渡す必要があります。この2つに関しては、スタブを使えばいいでしょう。

静的クラスのモックを作成
[TestMethod]
[HostType("Moles")]
public void 静的クラスのモックを作成()
{
    MGreetingFactory.CreateMorning = () =>
    {
        // スタブを作成して返す
        var stub = new SIGreeting();
        stub.GreetString = name =>
        {
            return string.Format("Guten morgen, {0}.", name);
        };
        return stub;
    };

    var greeting = GreetingFactory.CreateMorning();
    var result = greeting.Greet("服部");
    Assert.AreEqual("Guten morgen, 服部.", result);
}

クラスの静的メソッドや静的プロパティの動作差し替えも、同じ方法でできます。

Moles が生成するモックとスタブの使い分け

これには悩みましたが、

モック 既存の処理を差し替える
スタブ インタフェースや抽象クラスの実装を用意する

という風に使い分けると、役割がはっきりして良いんじゃないでしょうか。

まとめ

Moles でこれだけできれば、たいていのテストに対応できます。自作クラスだけでなく、.NET Framework が提供しているクラスのモックが作れるってのは超強力ですね。

そのくせ、メソッドやプロパティの動作の差し替えは、関数オブジェクトをセットするだけ、という簡単さ。あらかじめインタフェースを用意する、といった面倒な手間が不要なので、この簡単さは導入の後押しになりますね。

ビルドが少し遅くなりますが、そこは目をつぶるしか無いです。