8

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

これらは説明目的のためのクラスです:

クラス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)
        }
    }
}

サービス アダプタ クラス

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);
    }
}

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

クラス A の単体テスト中に ServiceAdapter の依存関係をモックするにはどうすればよいですか。B.

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

4

3 に答える 3

6

これで、静的メソッドが単体テストの悪い習慣と見なされる理由がわかりました。特に、モッキングがほとんど不可能になるためです。それらがステートフルである場合。

したがって、Bstaticメソッドを非静的パブリック メソッドのセットにリファクタリングする方がより実用的です。

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

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


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

 B.setServiceAdapter(new ServiceAdapter());       

あなたはにいATestます。ではありませんBTestのモックATestのみを持つ必要があるため、のインスタンスを渡す必要はありません。BServiceAdapter

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

また、奇妙に感じるのは、テストしたいメソッドが基本的に B へのラッパーのみであることです。おそらくこれはあなたのケースでは理にかなっていますがObject、これは、B のインスタンスの代わりに A に既に注入したいことを示唆しています。

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

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

于 2016-10-07T17:51:16.650 に答える