web-dev-qa-db-ja.com

メソッドが呼び出されたことを確認する

Moqを使用すると、モックでのセットアップが、セットアップするメソッドがパブリックである場合にのみ機能するように見えるという非常に奇妙な問題があります。これがMoqのバグなのか、それとも間違っているだけなのか(Moqの初心者)はわかりません。テストケースは次のとおりです。

public class TestClass
{
    public string Say()
    {
        return Hello();
    }

    internal virtual string Hello()
    {
        return "";
    }
}

[TestMethod]
public void Say_WhenPublic_CallsHello()
{
    Mock<TestClass> mock = new Mock<TestClass>();
    mock.Setup(x => x.Hello()).Returns("Hello World");

    string result = mock.Object.Say();
    mock.Verify(x => x.Hello(), Times.Exactly(1));
    Assert.AreEqual("Hello World", result);     
}

これはこのメッセージで失敗します:

Say_WhenPublic_CallsHelloが失敗しました:Moq.MockException:呼び出しがモックで1回実行されませんでした:x => x.Hello()at Moq.Mock.ThrowVerifyException(IProxyCall expected、Expression expression、Times times).. ..

このようにHelloメソッドを公開すると、テストに合格します。ここでの問題は何ですか?

public virtual string Hello()
{
    return "";
}

前もって感謝します!

23
jle

Hello()が内部の場合、Moqはメソッドのオーバーライドを提供できないため、テストは失敗します。これは、モックのバージョンではなく、Hello()の内部実装が実行され、Verify()が失敗することを意味します。

ちなみに、ここで行っていることは、単体テストのコンテキストでは意味がありません。単体テストでは、Say()が内部のHello()メソッドを呼び出すことを気にする必要はありません。これはアセンブリ内部の実装であり、コードを消費する心配はありません。

19
Adam Ralph

これがカバーの下でどのように機能するかについて、それがなぜその動作であるかについての技術的な答えを提供するのに十分なことはわかりませんが、私はあなたの混乱を助けることができると思います。

あなたの例では、メソッドSay()を呼び出しており、期待されるテキストを返しています。あなたの期待はnot Say()の特定の実装を強制する必要があります。つまり、Hello()と呼ばれる内部メソッドを呼び出してその文字列を返します。これが検証に合格しない理由であり、返される文字列が「」である理由でもあります。つまり、Hello()の実際の実装が呼び出されています。

Helloメソッドを公開することにより、Moqがその呼び出しをインターセプトし、代わりにその実装を使用できるようになったようです。したがって、この場合、テストは合格したように見えます。ただし、このシナリオでは、Say()を呼び出すと結果が「HelloWorld」であるのに対し、実際には結果が「」であるとテストで示されているため、実際には何も役に立ちません。

私はあなたの例を書き直して、Moqがどのように使用されることを期待するかを示しました(必ずしも決定的なものではありませんが、うまくいけば明確です。

public interface IHelloProvider
{
    string Hello();
}

public class TestClass
{
    private readonly IHelloProvider _provider;

    public TestClass(IHelloProvider provider)
    {
        _provider = provider;
    }

    public string Say()
    {
        return _provider.Hello();
    }
}

[TestMethod]
public void WhenSayCallsHelloProviderAndReturnsResult()
{
    //Given
    Mock<IHelloProvider> mock = new Mock<IHelloProvider>();
    TestClass concrete = new TestClass(mock.Object);
    //Expect
    mock.Setup(x => x.Hello()).Returns("Hello World");
    //When
    string result = concrete.Say();
    //Then
    mock.Verify(x => x.Hello(), Times.Exactly(1));
    Assert.AreEqual("Hello World", result);
}

私の例では、IHelloProviderへのインターフェイスを導入しました。 IHelloProviderの実装がないことに気付くでしょう。これは、モックソリューションを使用して達成しようとしていることの中心です。

外部の何か(IHelloProvider)に依存するクラス(TestClass)をテストしようとしています。テスト駆動開発を使用している場合は、おそらくまだIHelloProviderを作成していませんが、ある時点でIHelloProviderが必要になることを認識しています。ただし、気を散らすのではなく、最初にTestClassを機能させたいと考えています。または、IHelloProviderがデータベースまたはフラットファイルを使用しているか、構成が難しい場合があります。

完全に機能するIHelloProviderを使用している場合でも、TestClassの動作をテストしようとしているだけなので、具体的なHelloProviderを使用すると、たとえばHelloProviderの動作に変更があった場合に、テストが失敗しやすくなる可能性があります。それを使用するすべてのクラスのテストを変更する必要はなく、HelloProviderテストを変更するだけです。

コードに戻ると、クラスTestClassがあります。これは、インターフェイスIHelloProviderに依存しており、その実装は構築時に提供されます(これは依存性注入です)。

Say()の動作は、IHelloProviderのメソッドHello()を呼び出すことです。

テストを振り返ると、実際に作成したコードをテストしたいので、実際のTestClassオブジェクトを作成しました。モックIHelloProviderを作成し、Hello()メソッドが呼び出され、呼び出されたときに文字列「HelloWorld」が返されることを期待していると述べました。

次に、Say()を呼び出し、前と同じように結果を確認します。

認識しておくべき重要なことは、IHelloProviderの動作には関心がないため、テストを容易にするためにモックを作成できることです。 TestClassの動作に関心があるため、モックではなく実際のTestClassを作成して、実際の動作をテストできるようにしました。

これが何が起こっているのかを明確にするのに役立ったことを願っています。

11
Modan

Moqは部分的なモックを行わず、パブリック仮想メソッドまたはインターフェースのみをモックできます。モックを作成すると、完全に新しいTが作成され、パブリック仮想メソッドのすべての実装が削除されます。

私の提案はオブジェクトのパブリックサーフェス領域のテストに集中するであり、オブジェクトの内部ではありません。あなたの内部は報道を得るでしょう。 ターゲットクラスが何であるかを明確に理解し、それをモックしないでください(ほとんどの場合) 。

部分的なモックは、抽象クラスの機能をテストする場合にのみ役立ちます抽象メソッドのモック実装を使用します。これを行わない場合、部分的なモックを行うことによるメリットはあまり見られないでしょう。

これがnot抽象クラスである場合は、Modanが提案するアプローチにさらに焦点を当てる必要があります。つまり、ターゲットクラス自体。これはすべて、ルールに要約されますテストしているものをモックしないでください

7
Anderson Imes