web-dev-qa-db-ja.com

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

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

 }

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

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

8

はい。

モックとスタブを使用して、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);
}
12
M. Dudley

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

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

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

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

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

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

編集

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

3
Phil

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

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

1
Kilian Foth

さらに追加 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");メソッドを変更し、準備ができたら実際のサービスを見つける必要があります。これは、使用しているサービスの実装を取得するロジックを抽象化する service locator patter のバリエーションです。そのクイックバージョンは次のようになります。

_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;
    }
}
_
1
Phil Patterson

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

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

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

0
Telastyn

モックは、外部の依存関係を置き換えることに関する入力または出力ではありません。そのため、ユニットテストを記述して外部Webサービスをモックアウトするのが適切です。

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

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

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

0
jk.

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

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

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

}
_

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

0
k3b