2

モック オブジェクトは、一部のプログラム ユニットの詳細な動作テストを行うための優れたアプローチを導入します。モックされた依存関係をテストされたユニットに渡し、依存関係で正常に動作するかどうかを確認するだけです。

2 つのクラス A と B があるとします。

public class A
{
    private B b;

    public A(B b)
    {
        this.b = b;
    }

    public void DoSomething()
    {
        b.PerformSomeAction();
        if(b.State == some special value)
        {
            b.PerformAnotherAction();
        }
    }
}


public class B
{
    public BState State { get; private set; }

    public void PerformSomeAction()
    {
        //some actions

        State = some special value;
    }

    public void PerformAnotherAction()
    {
        if(State != some special value)
        {
           fail();  //for example throw new InvalidOperationException();
        }
    }
}

クラス B が単体テスト TestB でテストされているとします。

クラス A を単体テストするには、B をそのコンストラクターに渡す (状態ベースのテストを行う) か、B のモックを渡す (動作ベースのテストを行う) ことができます。

2 番目のアプローチ (たとえば、A の状態を直接検証できず、間接的に検証できる) を選択し、単体テスト TestA (B への参照を含まない) を作成したとします。

したがって、インターフェイス IDependency を導入すると、クラスは次のようになります。

public interface IDependency
{
    void PerformSomeAction();
    void PerformAnotherAction();
}

public class A
{
    private IDependency d;

    public A(IDependency d)
    {
        this.d = d;
    }

    public void DoSomething()
    {
        d.PerformSomeAction();
        if(d.State == some special value)
        {
            d.PerformAnotherAction();
        }
    }
}


public class B : IDependency
{
    public BState State { get; private set; }

    public void PerformSomeAction()
    {
        //some actions

        State = some special value;
    }

    public void PerformAnotherAction()
    {
        if(State != some special value)
        {
           fail();  //for example throw new InvalidOperationException();
        }
    }
}

単体テスト TestB は次のようなものです。

[TestClass]
public class TestB
{
    [TestMethod]
    public void ShouldPerformAnotherActionWhenDependencyReturnsSomeSpecialValue()
    {
        var d = CreateDependencyMockSuchThatItReturnsSomeSpecialValue();
        var a = CreateA(d.Object);

        a.DoSomething();

        AssertSomeActionWasPerformedForDependency(d);
    }

    [TestMethod]
    public void ShouldNotPerformAnotherActionWhenDependencyReturnsSomeNormalValue()
    {
        var d = CreateDependencyMockSuchThatItReturnsSomeNormalValue();
        var a = CreateA(d.Object);

        a.DoSomething();

        AssertSomeActionWasNotPerformedForDependency(d);
    }
}

Ok。これは開発者にとって嬉しい瞬間です。すべてがテストされ、すべてのテストがグリーンです。すべてが良いです。

しかし!

誰かがクラス B のロジックを変更すると (たとえば、 if(State != some special value) を if(State != another value) に変更する)、TestB のみが失敗します。

この男はこのテストを修正し、すべてが再びうまくいくと考えています。

しかし、B を A のコンストラクターに渡そうとすると、A.DoSomething は失敗します。

その根本的な原因は、モック オブジェクトです。B オブジェクトの古い動作を修正しました。B がその動作を変更したとき、モックはそれを反映しませんでした。

それで、私の質問は、 B のモックを B の動作の変更に追従させる方法ですか?

4

2 に答える 2

1

これは視点の問題です。通常、具体的なクラスではなく、インターフェイスをモックします。あなたの例では、B のモックは、B と同様に IDependency の実装です。B のモックは、IDependency の動作が変更されるたびに変更する必要があります。

そのため、強制はコード ベースで従うべき 2 つの単純なルールによって行われます。

  1. クラスがインターフェイスを実装する場合、変更後にインターフェイスの定義されたすべての動作を満たす必要があります。
  2. インターフェースを変更するときは、新しいインターフェースを満たすようにすべての実装者を適応させる必要があります。

理想的には、B と BMock の両方に適用され、これらのルールの違反をキャッチする、IDependency の定義された動作に対してテストする単体テストを配置します。

于 2013-02-14T13:44:50.203 に答える