8

我正在尝试对调用类“B”的静态方法的类“A”进行单元测试。类'B'本质上有一个谷歌番石榴缓存,它从给定键的缓存中检索值(对象),或使用服务适配器将对象加载到缓存中(以防缓存未命中)。服务适配器类又具有其他自动装配的依赖项来检索对象。

这些是用于说明目的的类:

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

当我运行单元测试时,它在进行调用的 ServiceAdapter 类中出现 NullPointerException 失败: mainService.getTheObject(key) 。

如何在单元测试类 A 时模拟 ServiceAdapter 的依赖关系。我不应该只关心 A 类的直接依赖关系,即。B.

我确信我做的事情根本上是错误的。我应该如何为 A 类编写单元测试?

4

3 回答 3

6

您现在知道为什么静态方法被认为是单元测试的坏习惯,因为它们使模拟几乎不可能,尤其是。如果它们是有状态的。

因此,将 Bstatic方法重构为一组非静态公共方法更为实用。

A 类应该通过构造函数或 setter 注入获得 B 类的实例。然后,在 Your ATest 中,您使用类 B 的模拟实例化类 A,并让它根据您的测试用例返回您喜欢的任何内容,并以此为基础您的断言。

通过这样做,你真正测试了单元,它最终应该是类 A 的公共接口。(这也是我喜欢一个类在理想世界中只有一个公共方法的原因。)


关于您的具体示例: B 的模拟也不应该关心它自己的依赖关系。您目前在测试中写道:

 B.setServiceAdapter(new ServiceAdapter());       

你在ATest. 不在BTest. ATest应该只有一个模拟,因此不需要B传递一个实例。ServiceAdapter

您只应该关心 A 的公共方法的行为方式,并且考虑到 B 的公共方法的某些响应,这可能会发生变化。

我还觉得奇怪的是,您要测试的方法基本上只是 B 的包装器。也许这对您的情况有意义,但这也暗示我您可能已经想Object在 A 中注入一个而不是 B 的实例。

如果您不想在模拟地狱中迷失方向,那么每个类的公共方法越少越好,而这些方法的依赖关系越少越好。我争取每个班级有三个依赖项,并在特殊情况下最多允许五个。(每个依赖项都可能对模拟开销产生巨大影响。)

如果您有太多依赖项,当然可以将某些部分移至其他/新服务。

于 2016-10-07T17:51:16.650 回答
2

重写代码以使其更具可测试性已在另一个答案中进行了解释。有时很难避免这些情况。

如果你真的想模拟一个静态调用,你可以使用 PowerMock。您将需要为您的类使用 @PrepareForTest({CACHE.class}) 注释,然后在单元测试中使用下面的代码。

      Object testObject = null; // Change this
      PowerMock.mockStatic(CACHE.class);
      expect(CACHE.get(anyString())).andReturn(testObject);
      PowerMock.replay(CACHE.class);
于 2016-10-20T22:36:32.240 回答
0

为了解决这个问题,您可以在类 B 周围包装一个接口存储库类型类。一旦有了接口,您就可以将其存根以进行测试。

通过这样做,您将 A 与 B 的内部工作隔离开来,只关注 B 的结果操作(我想这只是另一种说法,即对接口而不是具体类进行编程)

于 2017-11-21T16:52:15.740 回答