web-dev-qa-db-ja.com

静的メソッドを呼び出すクラスのユニットテスト

クラス 'B'の静的メソッドを呼び出すクラス 'A'を単体テストしようとしています。クラス 'B'は基本的に、キーを指定してキャッシュからvalue(Object)を取得する、またはサービスアダプターを使用してキャッシュにオブジェクトをロードする(キャッシュミスの場合)google guavaキャッシュを持っています。次に、サービスアダプタクラスには、オブジェクトを取得するための他の自動配線された依存関係があります。

これらは、説明のためのクラスです。

クラスA

public class A {
    public Object getCachedObject(String key) {
        return B.getObjectFromCache(key);
    }
}

クラスB

public class B {

    private ServiceAdapter serviceAdapter;

    public void setServiceAdapter(ServiceAdapter serAdapt) {
        serviceAdapter = serAdapt;
    } 

    private static final LoadingCache<String, Object> CACHE = CacheBuilder.newBuilder()
                .maximumSize(100) 
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build(new MyCacheLoader());

    public static Object getObjectFromCache(final String key) throws ExecutionException {
        return CACHE.get(warehouseId);
    }

    private static class MyCacheLoader extends CacheLoader<String, Object>  {

        @Override
        public Object load(final String key) throws Exception {
            return serviceAdapter.getFromService(key)
        }
    }
}

Service-Adapter Class

public class ServiceAdapter {
        @Autowired
        private MainService mainService

        public Object getFromService(String key) {
            return mainService.getTheObject(key);
        }
    }

統合テストを正常に実行し、キャッシュから(またはキャッシュに)値をフェッチ(またはロード)できます。ただし、クラスAの単体テストを作成できません。これは私が試したものです。

クラスAの単体テスト

@RunWith(EasyMocker.class)
public class ATest {
    private final static String key = "abc";
    @TestSubject
    private A classUnderTest = new A();

    @Test
    public void getCachedObject_Success() throws Exception {
        B.setServiceAdapter(new ServiceAdapter());
        Object expectedResponse = createExpectedResponse(); //some private method 
        expect(B.getObjectFromCache(key)).andReturn(expectedResponse).once();
        Object actualResponse = classUnderTest.getCachedObject(key);
        assertEquals(expectedResponse, actualResponse);
    }
}

単体テストを実行すると、ServiceAdapterクラスでNullPointerExceptionが発生して失敗し、mainService.getTheObject(key)が呼び出されます。

クラスAのユニットテスト中にServiceAdapterの依存関係をモックする方法を教えてください。クラスAの直接の依存関係、つまりB..

私は根本的に間違っていることをしていると確信しています。クラスAの単体テストはどのように記述すればよいですか?

6
user1639485

静的メソッドがモックを作成することをほとんど不可能にするため、なぜユニットテストの悪い習慣であると見なされるのかがわかります。ステートフルな場合。

したがって、B staticメソッドを静的でないパブリックメソッドのセットにリファクタリングする方が実際的です。

クラスAは、コンストラクターまたはセッターの注入によって、クラスBのインスタンスを注入する必要があります。次に、ATestでクラスBのモックを使用してクラスAをインスタンス化し、テストケースに応じて好きなものを返し、それに基づいてアサーションを作成します。

そうすることで、実際にnitをテストします。これは、最終的にはクラスAのパブリックインターフェイスになるはずです(これが、理想的な世界でクラスがパブリックメソッドを1つだけ持つようにする理由でもあります。 )


あなたの特定の例に関して:Bのモックはそれ自身の依存関係も気にするべきではありません。現在テストに書き込みます:

 B.setServiceAdapter(new ServiceAdapter());       

ATestにいます。 BTestにはありません。 ATestにはmockBのみを含める必要があるため、ServiceAdapterのインスタンスを渡す必要はありません。

Aのパブリックメソッドがどのように動作するかを気にするだけでよく、Bのパブリックメソッドの特定の応答によっては変更される可能性があります。

私が奇妙に思うのは、基本的にBへのラッパーのみをテストする方法です。多分これはあなたのケースでは理にかなっているかもしれませんが、代わりにすでにAにObjectを注入したいかもしれないことを示唆していますBのインスタンスの.

地獄のあざけりで迷子になりたくない場合は、クラスごとのパブリックメソッドの数を減らし、依存関係をできるだけ少なくすることが実際に役立ちます。私はクラスごとに3つの依存関係に努め、特別な場合には最大5つの依存関係を許可します。 (各依存関係は、モックのオーバーヘッドに大きな影響を与える可能性があります。)

依存関係が多すぎる場合は、確かに一部を他の新しいサービスに移動できます。

5
k0pernikus

テストしやすくするためにコードを書き直すことは、別の回答ですでに説明されています。これらのケースを回避することが難しい場合があります。

静的な呼び出しをモックしたい場合は、PowerMockを使用できます。ユニットテストでは、クラスに@PrepareForTest({CACHE.class})アノテーションを使用し、その後に以下のコードを使用する必要があります。

      Object testObject = null; // Change this
      PowerMock.mockStatic(CACHE.class);
      expect(CACHE.get(anyString())).andReturn(testObject);
      PowerMock.replay(CACHE.class);
2
Andy

これを回避するために、インターフェース化されたリポジトリー型クラスをクラスBでラップすることができます。インターフェースを取得したら、テスト用にスタブ化できます。

これを行うことにより、AをBの内部動作から隔離し、Bの結果のアクションのみに焦点を当てます(これは、具象クラスではなく、インターフェースにプログラムするための別の言い方だと思います)

0
Gavin