web-dev-qa-db-ja.com

POJO(値オブジェクト)に何が含まれているか気にならない場合、モックするのは本当に悪い習慣ですか?

良いテストの書き方に関するMockitoのアドバイスでは、値オブジェクトをモックしないように書かれています( https://github.com/mockito/mockito/wiki/How-to-write-good-tests #dont-mock-value-objects )。彼らも言う

オブジェクトのインスタンス化が面倒なので!? =>正当な理由ではありません。

しかし、私にはわかりません。そのオブジェクトが何を含んでいるかについて本当に気にしない場合はどうでしょうか?単純なコンストラクタを持たない値オブジェクトクラス "Foo"があるとしましょう(すべて引数のコンストラクタがあり、すべて引数のコンストラクタも必要なオブジェクトが含まれています)。また、次のサービスがあるとします。

public class MyService {
    private SomeService someService;
    private OtherService otherService;

    public void myAwesomeMethod() {
        Foo foo = someService.getFoo();
        someOtherService.doStuff(foo);
    }
}

そして、私はその方法をテストする必要があります。明らかにここでは、fooが別のメソッドに渡されているだけなので、fooに何が含まれているかは気にしません。テストしたい場合は、コンストラクタ/ビルダーがネストされたfooオブジェクトを実際に作成する必要がありますか?私にとっては、たとえPOJOであってもfooをモックするのは本当に簡単です:

public class MyServiceTest {
    @InjectMocks
    MyService myService;

    @Mock
    private SomeService someService;

    @Mock
    private OtherService otherService;

    @Test
    public void testMyAwesomeMethod() {
        Foo mockedFoo = Mockito.mock(Foo.class);

        Mockito.when(someService.getFoo()).thenReturn(mockedFoo);

        Mockito.verify(otherService).doStuff(mockedFoo);
    }

}

しかし、それはMockitoによって与えられたガイドラインを尊重しません。別の選択肢はありますか?

6
Ricola

スタブ(質問でモックと呼んだもの)には理由があります。これにより、テストしているメソッドで依存するクラスのビジネスコードを、テストするのに十分なデータを提供する非常に基本的なステートメントに置き換えることができます。方法。モックは、スタブと同様に、ビジネスロジックをテストに必要なカスタムロジックに置き換えます。

  • POJOにビジネスコードがない場合(つまり、POJOがデータオブジェクトでもある場合)、スタブとモックは役に立ちません。テストでそのようなデータオブジェクトを使用することは非常に簡単です。メソッドで入力にデータオブジェクトが必要な場合は、テストで作成するだけです。

    一部のデータオブジェクトは非常に大きく、多数のプロパティを含んでいる場合があります。一部の言語では、テスト中に必要なプロパティのみを設定することにより、データオブジェクトを部分的に初期化できます。他の言語では、完全に初期化する必要があります。これが当てはまる場合は、デフォルト値でデータオブジェクトを作成する単純な静的メソッドを作成し、このメソッドをテスト全体で再利用することが解決策になる可能性があります。

  • POJOにビジネスコードが含まれている場合は、他のクラスの場合と同様に、ユニットテストをそこから分離する必要があります。ここでは、スタブとモックは問題なく、モックされたオブジェクトがPOJOか何かであるかどうかに関係なく、違いはありません。

9

私の意見ではあなたのリンクからの最高のラインは

すべてをあざけるな、それはアンチパターンだ

すべてがモックされている場合、実際に本番コードをテストしていますか?モックしないことを躊躇しないでください!

通常、実際の入力と予想される出力を使用してコードをテストします。通常、これらはデータ構造またはpojos/pocosです。

すなわち。

actual = myfunc(input)
Assert.AreEqual(actual, expected)

そのテスト用に特別に設定されているため、その入力または出力を模擬しても意味がありません。あなたはいつもそれを気にします。

あなたの例では、実行する入力、出力、または実際にロジックはありません。コードがオブジェクトをあるサービスから別のサービスに渡すだけの実際の例がある場合でも、nullや非常に大きなオブジェクトなどのEdgeケースをテストしたいですか?あなたすべきそのオブジェクトが何であるかを気にします。

3
Ewan

コードのにおいがするかもしれません。

明らかにここでは、fooが別のメソッドに渡されているだけなので、fooに何が含まれているかは気にしません。

この例で私にとって際立っているのは、コードが交換されるオブジェクトがFooであることさえ気にしていないことです。

public class MyService<T> {
    private Java.util.function.Supplier<? extends T> someService;
    private Java.util.function.Consumer<? super T> otherService;

    public void myAwesomeMethod() {
        T foo = someService.get();
        someOtherService.accept(foo);
    }
}

次に、単体テストで、Fooを使用する代わりに、またはFooの代わりに使用できるテストdoubleを作成する代わりに、サプライヤー/コンシューマーのペアを注入できます。

単純なコンストラクタを持たない値オブジェクトクラス "Foo"があるとしましょう(すべて引数のコンストラクタがあり、すべて引数のコンストラクタも必要なオブジェクトが含まれています)。

もう1つの一般的なパターンは、 テストデータビルダー を使用して必要な値を作成することです。テストデータビルダーは通常、オブジェクトのどの詳細が テストのコンテキストで重要 であるかをテスト内で通信するために fluent interfaces を使用します。

@Test
public void testMyAwesomeMethod() {
    Foo mockedFoo = new FooBuilder().anyOldFoo();

    Mockito.when(someService.getFoo()).thenReturn(mockedFoo);

    Mockito.verify(otherService).doStuff(mockedFoo);
}

ここで、ビルダーとの相互作用は、このテストの動作が特定のfoo値に依存してはならないことを明確に示しています。

目を細めると、テストがプロパティをアサートしていることがわかります

public void testMyAwesomeMethod() {
    for (Foo candidate : allPossibleFoo()) {
        Mockito.when(someService.getFoo()).thenReturn(candidate);
        // ...
    }
}

これらのビルダーアプローチのいずれも、フールコンストラクターの呼び出しの問題を直接解決しません。ビルダーが行うのは、その複雑さをテストから移動することだけです。

2
VoiceOfUnreason

あなたはやや正しいです。

オブジェクトをモックするときは、通常、メソッドをスタブして、代わりに既定値を返します。ただし、値オブジェクトに対してこれを行う正当な理由はありません。代わりに値オブジェクトを使用してください。

ただし、この特定のシナリオでは、どのメソッドもスタブしていません。ある関数から返された同じオブジェクトが別の関数に渡されることを確認しているだけです。したがって、値オブジェクトをモックすることの欠点はありません。

それにもかかわらず、Fooを構築するテストコードがほぼ確実にあるはずです。 SomeServiceのテストは、おそらくassertEqualsと、いくつかの構築されたFooを使用したgetFooの結果になるはずです。 OtherServiceのテストでは、作成されたFooオブジェクトをdoStuffに渡す必要があります。これらのテスト用にFooオブジェクトを構築するコードをどこかに配置する必要があるため、その構築コードを再利用できる場所に簡単にリファクタリングできるはずです。

ただし、一般的に、MyServiceFoo.のメソッドを呼び出さないようにすることは疑わしいことです。これは、MyServiceがFooをまったく処理してはならないことを示しています。すべてのMyServiceSomeServiceからオブジェクトを取得してOtherServiceに渡す場合、MyServiceの意味は何ですか?コードを簡略化し、2つのサービスを直接接続します。

1
Winston Ewert