我正在尝试对一个类'A'进行单元测试,该类调用类'B'的静态方法。类'B'本质上具有google guava缓存,其在给定密钥的情况下从缓存中检索值(Object),或者使用服务适配器将对象加载到缓存中(在缓存未命中的情况下)。服务适配器类又具有其他自动连接依赖项来检索对象。
这些是用于说明目的的类:
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类编写单元测试?
答案 0 :(得分:4)
您现在知道为什么静态方法被认为是单元测试的不良做法, 因为他们使嘲弄几乎不可能,尤其是如果他们是有状态的。
因此,将B static
方法重构为一组非静态公共方法更为实际。
A类应该通过构造函数或setter注入获得B类实例。在你的ATest中,你然后用类B的模拟实例化A类,并根据你的测试用例返回你喜欢的任何东西,然后根据你的断言做出判断。
通过这样做你真的测试单元,它最终应该是A类的公共接口。(这也是我喜欢一个类只有一个公共方法的原因。理想的世界。)
关于你的具体例子:B的模拟也不应该关心它自己的依赖关系。您目前在考试中写道:
B.setServiceAdapter(new ServiceAdapter());
你在ATest
。不在BTest
。 ATest
应该只有模拟 B
,因此不需要传递ServiceAdapter
的实例。
你应该关心A的公共方法是如何表现的,并且鉴于B公共方法的某些反应,这可能会改变。
我也觉得奇怪的是你想要测试的方法基本上只是B的包装。也许这在你的情况下有意义但这也暗示我你可能想要注入一个Object
而不是B的实例。
如果你不想在模仿地狱中迷失方向,那么每个类的公共方法越少越好,这反过来会减少依赖性。我努力争取每班三个依赖,并在特殊场合允许最多五个。 (每个依赖项可能会对模拟开销产生巨大影响。)
如果你有太多的依赖关系,肯定有些部分可以转移到其他/新服务。
答案 1 :(得分:1)
在另一个答案中已经解释了重写代码以使其更易于测试。有时很难避免这些情况。
如果您真的想模拟静态调用,可以使用PowerMock。您需要为您的类使用@PrepareForTest({CACHE.class})注释,然后在单元测试中使用下面的代码。
1265
答案 2 :(得分:0)
为了解决这个问题,您可以围绕类B包装一个接口存储库类型类。一旦有了接口,就可以将其存根以进行测试。
通过执行此操作,您可以将A与B的内部工作隔离开,并仅关注B的结果操作(我想这只是说编程到接口而不是具体类的另一种说法)