このシナリオでは、単体テストのモックが適切ですか?


8

私はJavaで約20のメソッドを作成し、それらすべてがいくつかのWebサービスを呼び出します。これらのWebサービスはまだ利用できません。サーバー側のコーディングを続行するために、Webサービスが提供すると予想される結果をハードコーディングしました。

これらのメソッドを単体テストできますか?私の知る限り、単体テストは入力値を模倣しており、プログラムがどのように応答するかを確認しています。入力値と出力値の両方をモックすることは意味がありますか?

編集:

ここでの答えは、私が単体テストケースを書くべきであることを示唆しています。

さて、既存のコードを変更せずにそれをどのように書くことができますか?

次のサンプルコード(架空のコード)を考えます。

    public int getAge()
    {
            Service s = locate("ageservice"); // line 1
            int age = s.execute(empId); // line 2
             return age; // line 3

 }

では、出力をどのようにモック化しますか?

現在、「行1」をコメントアウトして、行2をに置き換えていint age= 50ます。これは正しいですか ?誰かが私にそれを行う正しい方法を教えてもらえますか?

回答:


12

はい。

モックとスタブを使用して、Webサービスの予想される動作をシミュレートします。予想されるすべての境界値と等価クラスでコードが正しく機能することを検証します。

編集:

いいえ、で単体テストを手動で編集しないでくださいint age= 50。依存関係注入を使用Serviceして、をモックオブジェクトに簡単に置き換えることができるようにする必要があります。

public int getAge(Service s)
{
    int age = s.execute(empId); // line 2
    return age; // line 3
}

単体テストは次の擬似コードのようになります。

public int getAgeMinimumValueTest()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.age = 10;
    int age = uut.getAge(service);
    Assert(age == 10);
}

1
入力と出力の両方をモックする場合、ポイントは何ですか?そもそもどうやって出力をモックするのですか?
Vinoth Kumar CM

1
単一のメソッドを単体テストする場合は、ユースケースごとに単体テストを作成する必要があります。つまり、各単体テストでメソッドに異なる入力値を指定し、出力が正しい値であることを確認します。メソッドがWebサービスに依存している場合は、そのユースケースで期待どおりに動作するようにWebサービスをモックアウトします。たとえば、Webサービスのモックコードを記述して、メソッドへの入力と見なされる値を提供できます。
M.ダドリー

3

はい、あなたはあなたのサービスをあざける必要があります。モッキングとは、モックオブジェクトを使用して、実行時に通常使用する実際のオブジェクトを置き換えることです。ほとんどの場合、モックフレームワークを使用して、モックオブジェクトを簡単かつ動的に作成します。

モックオブジェクトを使用する場合は、おそらく次のようなコードを記述します。

public int getAge(ServiceInterface service)
{
    return service.execute(empId);
}

どこserviceあなたは、実行時に使用するつもり実際のサービスですが、ユニットテストの際には、モックの挙動とモックオブジェクトです。のパラメーターgetAgeが具体的なクラスではなく、インターフェースになっていることに注意してください。これにより、特定の1つのクラスだけでなく、インターフェースを実装する限り、任意のオブジェクトをパラメーターとして使用できます。単体テストでは、次の手順を実行します。

  1. モックオブジェクトを作成する
  2. execute呼び出されたときに特定の年齢に戻るように伝えます
  3. getAgeモックオブジェクトをパラメーターとして呼び出します
  4. Verify getAgeが正しい年齢を返します。

Javaモックフレームワークの適切なリストについては、このStackoverflowの質問を確認してください。

編集

コードをコメント化しないでください。ユニットテストのためだけにコードを定数に置き換えてください。これの問題は、ユニットテストを実行するときに正しいことをコメントし、顧客にリリースするときに正しいことをコメントする必要があることです。これは、特にモックを必要とする2つ以上の単体テストを書くつもりである場合は、長期のメンテナンス目的にとって悪夢に変わります。それとは別に、テストは完成した製品に対して実行する必要があります。そうでない場合、それはリリースされている実際のコードをテストしていないことを意味し、そもそもテストを行う目的を否定します。


1

はい、そうです。あなたの仕事は、その環境を前提として、コードが正しいことを行うことを示すことです。外部Webサービスは環境の一部です。それらの適切な機能をテストするのはあなたの仕事ではなく、サービスを書く人々の仕事です。テストは確認する必要があることを、入力/出力マッピングは、入力/出力され、あなたのコード。コードが別のコラボレーターがその仕事をするための入力を生成する場合、それは厳密に偶発的です。

実際、Webサービスが利用可能になった後でも、実際の環境ではなくモック環境を使用してユニットテストを維持することをお勧めします。これにより、期待されるシステムコンテキストに関してテストがより簡単になり、壊れにくくなり、おそらくより速くなります。


「たぶんいいアイデア」?必須だと思います。システムの境界を越えて信頼できる単体テストを保証することはできません。
ザハウラー'17年

1

emddudleyの優れた答えに加えて、サービスのモックを作成することで得られる最大の利点は、サービスが正しく機能しない場合に何が起こるかをテストできることです。テストの疑似コードは次のようになります。

public int AgeMinimumValue_LogsServiceError_Test()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    try {
        int age = uut.getAge(service, logger);
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

そして今あなたの実装はこの新しい要件で編集されました

public int getAge(Service s, Logger l)
{
    try {
        int age = s.execute(empId);
        return age;
    }
    catch(Exception ex) {
        l.LogError(ex);
        throw;
    }
}

他のシナリオでは、より複雑な応答に応答する必要がある可能性が高くなります。サービスがクレジットカード処理を提供した場合、成功、サービス利用不可、期限切れのクレジットカード、無効な番号などに対応する必要があります。サービスを模擬することにより、状況に適した方法でこれらのシナリオに確実に対応できます。この場合、サービスからの入力/出力を模擬する必要があります。使用するコードがすべての既知の出力で機能することを知って得られるフィードバックは、本当に意味があり、価値があります。

編集:私はあなたが既存のメソッドを変更せずにモックできるようにしたいことに気づきました。これを行うにはlocate("ageservice");、テストでモックオブジェクトをサポートするようにメソッドを変更し、準備ができたら実際のサービスを見つける必要があります。これは、使用しているサービスの実装を取得するためのロジックを抽象化したサービスロケーターパターンのバリエーションです。そのクイックバージョンは次のようになります。

public Service locate(string serviceToLocate) {
    if(testEnvironment) // Test environment should be set externally
        return MockService(serviceToLocate);
    else
        return Service(serviceToLocate);
}

ただし、サービスの依存関係をコンストラクターに移動することをお勧めします。

public int AgeMinimumValue_LogsServiceError_Test()
{
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    ClassUnderTest uut = new ClassUnderTest(service, logger);

    try {
        int age = uut.getAge();
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

これで、getAgeメソッドはクラスから抽象化されているため、サービスを検索する必要がなくなりました。これは、次のような実装を完全に残しています。

public int getAge()
{
    try {
        // _service is a private field set by the constructor
        int age = _service.execute(empId); 
        return age;
    }
    catch(Exception ex) {
         // _logger is a private field set by the constructor
        _logger.LogError(ex);
        throw;
    }
}

0

入力値と出力値の両方をモックすることは意味がありますか?

いいえ、でも入力値をあざけるわけではありません。テストが実行されており、モックサービスがコントラクトの適切な部分にアクセスしていることを確認します。たまたま特定の値を返すことは関係ありません。

一方、メソッドがロジックを実行する場合は、戻り値が重要になります。ここでは、ロジック(Webサービスの結果)への入力をモックして、出力をテストしています。


0

さて、既存のコードを変更せずにそれをどのように書くことができますか?

できません。ただし、すべてのWebサービスメソッドシグネチャを含む、プログラミングできるインターフェイス "IMyService"を作成できます。

public int getAge()
{
        IMyService s = getService(); 
        int age = s.execute(empId);
         return age; // line 3

}

プロダクションモードでgetService();は、完全に機能するWebサービスへの参照が返され、テストモードでは、偽のデータを返す代替実装(またはモック)が返されます。


0

モッキングは、入力や出力ではなく、外部の依存関係を置き換えることです。そのため、ユニットテストを記述して外部Webサービスをモックアウトするのが適切です。

悪い知らせです。利用できる言語とツールによっては、コードが許可するように設計されていない限り、外部の依存関係を模倣できない場合があります。これが事実である場合、テストは実際には純粋なuntiテストではなく小さな統合テストに変わることがわかります。

プラス面では、コードを変更する(他にどのようにコメントアウトする)のが遅くなり、サービスにインターフェースを渡して、モックアウトできるようにします(または他の形式の依存関係注入を使用します)。

最後に、テストしたいのは、外部サービスの純粋なラッパーのようです。ここでユニットテストを行うことは、サービスを呼び出すこと以外にほとんどありません。これは基本的にインターフェースであるため、ほとんどのテストは後で上位レベルで行う必要があります(統合テスト)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.