对调用静态方法的类进行单元测试

时间:2016-10-07 17:09:23

标签: java unit-testing static-methods easymock google-guava-cache

我正在尝试对一个类'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类编写单元测试?

3 个答案:

答案 0 :(得分:4)

您现在知道为什么静态方法被认为是单元测试的不良做法, 因为他们使嘲弄几乎不可能,尤其是如果他们是有状态的。

因此,将B static方法重构为一组非静态公共方法更为实际。

A类应该通过构造函数或setter注入获得B类实例。在你的ATest中,你然后用类B的模拟实例化A类,并根据你的测试用例返回你喜欢的任何东西,然后根据你的断言做出判断。

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

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

 B.setServiceAdapter(new ServiceAdapter());       

你在ATest。不在BTestATest应该只有模拟 B,因此不需要传递ServiceAdapter的实例。

你应该关心A的公共方法是如何表现的,并且鉴于B公共方法的某些反应,这可能会改变。

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

如果你不想在模仿地狱中迷失方向,那么每个类的公共方法越少越好,这反过来会减少依赖性。我努力争取每班三个依赖,并在特殊场合允许最多五个。 (每个依赖项可能会对模拟开销产生巨大影响。)

如果你有太多的依赖关系,肯定有些部分可以转移到其他/新服务。

答案 1 :(得分:1)

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

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

1265

答案 2 :(得分:0)

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

通过执行此操作,您可以将A与B的内部工作隔离开,并仅关注B的结果操作(我想这只是说编程到接口而不是具体类的另一种说法)