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

C#でのテスト(3) NMock 2.0を使ったテスト

C#でのテスト(2) nantによるビルドの自動化 - ぐるぐる〜に続いて、今回はNMock 2.0の使い方を紹介する。


NMock 2.0は、

The original NMock was a .NET port of the Java-based DynaMock, whereas NMock 2.0 is inspired by the newer jMock library.

NMock: A Dynamic Mock Object Library for .NET

とあるように、jMockの影響により、NMockとは別物となっている。以降、面倒なのでNMockはNMock 2.0を指すものとする。


NMockはまず、Mockery*1というクラスのオブジェクトを生成する必要がある。
この生成したMockeryオブジェクトを使用してモックオブジェクトを生成する。

// Mockeryオブジェクトを生成し、
Mockery mock = new Mockery();
// モックオブジェクトを生成する。
IHoge hoge = mock.NewMock<IHoge>();


生成したモックオブジェクトには、「何回」、「どのメソッド(プロパティ、インデクサ、イベント)」、「どういう結果」等を指定する必要がある。
例えば、Hogeメソッドが1回、引数が10で呼び出され、20が返されるような場合、

Expect.Once.On(hoge).Method("Hoge").With(10).Will(Return.Value(20));

というコードになる。


Onceのほかには、Never、AtLeaseOnce、Exactly(n)、AtLease(n)、AtMost(n)、Between(a, b)が使用できる。
それぞれ、一回も呼ばれない、少なくとも一回、n回、少なくともn回、多くてもn回、a回からb回を表す。
これだけでも、NMockは「読みやすく」、(IDEの力を借りる前提は付くが)「書きやすい」。



例えば、以下のインターフェイス

public interface ISample
{
    int Method(int i);
    string Property { get; set; }
    int this[string str] { get; set; }
}

を使用するメソッド

int TargetMethod(ISample sample)
{
    sample.Property = "piyo";
    string key = sample.Property;
    sample[key] = 42;
    return sample.Method(sample["piyo"]);
}

のテストは例えば、

using (Mockery mock = new Mockery())
{
    Target target = new Target();
    
    ISample sample = mock.NewMock<ISample>();
    Expect.Once.On(sample).SetProperty("Property").To("piyo");
    Expect.Once.On(sample).GetProperty("Property").Will(Return.Value("hoge"));
    Expect.Once.On(sample).Set["hoge"].To(42);
    Expect.Once.On(sample).Get["piyo"].Will(Return.Value(0));
    Expect.Once.On(sample).Method("Method").With(0).Will(Return.Value(1));
    
    Assert.AreEquals(1, target.TargetMethod(sample));
}

のようになる*2
Willの引数には戻り値の設定のほかに、Throw.Exceptionメソッドにより例外を投げるように設定することも出来る。


以上、NMockの基本的な使い方を紹介した。
さらに詳しくは、NMock2説明資料や、ソースを参照するといいだろう。

*1:mockeryはmockの名詞で、模倣、偽物、まがい物という意味を持つ

*2:本来メソッドの最後にmock.VerifyAllExpectationsHaveBeenMet()を呼ぶのだが、MockeryクラスはIDisposableを実装しており、さらにDisposeメソッドではVerifyAllExpectationsHaveBeenMetメソッドを呼び出しているので、この例のようにusingも使用できる