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

use*_*485 5 java static-methods unit-testing easymock google-guava-cache

我试图对类“ A”进行单元测试,该类调用类“ B”的静态方法。类“ B”本质上具有Google番石榴缓存,该缓存从给定键的缓存中检索值(对象),或使用服务适配器将对象加载到缓存中(如果发生缓存丢失)。反过来,service-adapter类还具有其他自动关联的依赖项来检索对象。

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

A级

public class A {
    public Object getCachedObject(String key) {
        return B.getObjectFromCache(key);
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

服务适配器类

public class ServiceAdapter {
        @Autowired
        private MainService mainService

        public Object getFromService(String key) {
            return mainService.getTheObject(key);
        }
    }
Run Code Online (Sandbox Code Playgroud)

我能够成功进行集成测试,并从缓存中获取(或加载)值。但是,我无法编写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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我运行单元测试时,它在ServiceAdapter类上失败,并出现NullPointerException,其中发生了调用:mainService.getTheObject(key)。

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

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

k0p*_*kus 5

您现在知道为什么将静态方法视为单元测试的不良做法,因为它们使模拟几乎变得不可能,尤其是。如果它们是有状态的。

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

A类应该通过构造函数或setter注入获得注入的B类实例。然后,在您的ATest中,使用类B的模拟实例化类A,并根据您的测试用例返回您想要的任何内容,并以此为依据。

这样,您就可以真正测试该单元,该单元最后应该是A类的公共接口。(这也是为什么我希望一个类在理想的世界中只有一个公共方法)。


关于您的特定示例:B的模拟也应该不在乎其自身的依赖性。您当前在测试中编写:

 B.setServiceAdapter(new ServiceAdapter());       
Run Code Online (Sandbox Code Playgroud)

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

您只需要关心A的公共方法的行为,并且在B的公共方法的某些响应下可能会改变。

我还感到奇怪的是,您要测试的方法基本上仅是B的包装器。也许这在您的情况下是有道理的,但这也向我暗示,您可能想已经Object在A中注入了A,而不是B的实例。

如果您不想迷失在模拟地狱中,那么在每个类中使用尽可能少的公共方法确实有帮助,而公共方法又会尽可能减少依赖项。我为每个班级争取三个依存关系,在特殊情况下最多允许五个依存关系。(每个依赖关系可能会对模拟开销产生巨大影响。)

如果您有太多的依赖项,那么肯定可以将某些部分移到其他/新服务中。