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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39922637/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 21:33:44  来源:igfitidea点击:

Unit-testing a class that calls a static method

javaunit-testingstatic-methodseasymockgoogle-guava-cache

提问by user1639485

I am trying to unit-test a class 'A' which calls a static method of a class 'B'. Class 'B' essentially has a google guava cache which retrieves a value(Object) from the cache given a key, or loads the object into the cache (in case of a cache-miss) using a service adapter. The service-adapter class in turn has other autowired dependencies to retrieve the object.

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

These are the classes for illustration purposes:

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

Class A

A级

public class A {
    public Object getCachedObject(String key) {
        return B.getObjectFromCache(key);
    }
}

Class B

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

Service-Adapter Class

服务适配器类

public class ServiceAdapter {
        @Autowired
        private MainService mainService

        public Object getFromService(String key) {
            return mainService.getTheObject(key);
        }
    }

I am able to do the integration test successfully and fetch (or load) the value from (or into) the cache. However, I am unable to write the unit-test for class A. This is what I have tried:

我能够成功进行集成测试并从(或到)缓存中获取(或加载)值。但是,我无法为 A 类编写单元测试。这是我尝试过的:

Unit-Test for Class 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);
    }
}

When I run the unit-test, it fails with a NullPointerException at ServiceAdapter class where the call: mainService.getTheObject(key) is made.

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

How do I mock the dependency of ServiceAdapter while unit-testing class A. Shouldn't I be just concerned about the immediate dependency that class A has, viz. B.

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

I am sure I am doing something fundamentally wrong. How should I write the unit-test for class A?

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

回答by k0pernikus

You now know why static method are deemed bad practice for unit testing, as they make mocking almost impossible, esp. if they are stateful.

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

It is hence more practical to refactor B staticmethods into a set of non-static public ones.

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

Class A should get an instance of class B injected, either via constructor or setter injection. In Your ATest you then instantiate class A with a mock of class B and have it return whatever you like depending on your test case and base your assertions on that.

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

By doing so you really test the unit, which in the end should be the public interface of class A. (This is also why I like for a class to have only one public method in an ideal world.)

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



Regarding to your specific example: The mock of B should also not care about its own dependencies. You currently write in your test:

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

 B.setServiceAdapter(new ServiceAdapter());       

You are in ATest. Not in BTest. ATestshould only have a mockof B, so passing an instance of the ServiceAdaptershould not be required.

你在ATest. 不在BTest. ATest应该只有一个模拟B,所以路过的一个实例ServiceAdapter,不应要求。

You only should care how A's public methods behaves, and that may change given certain responses of B's public methods.

您只应该关心 A 的公共方法的行为方式,并且鉴于 B 的公共方法的某些响应,这可能会改变。

What I also find odd is that the method you want to test basically only a wrapper to B. Maybe this makes sense in your case yet this also hints to me that you maybe want to already inject an Objectin A instead of an instance of B.

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

If you want to not get lost in mocking hell it really helps to have as less public methods per class which in turn have as less dependencies as possible. I strive for at three dependencies per class, and allow up to five on special occasions. (Each dependency may have huge impact on the mocking overhead.)

如果你不想迷失在模拟地狱中,那么每个类拥有尽可能少的公共方法真的很有帮助,这反过来又尽可能少地依赖。我争取每个类三个依赖项,并在特殊情况下允许最多五个。(每个依赖项都可能对模拟开销产生巨大影响。)

If you have too many dependencies, certainly some parts can be moved to other/new services.

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

回答by Andy

Re-writing code to make it more testable has already been explained in another answer. At times it is difficult to avoid these cases.

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

If you really wanted to mock a static call, you could use PowerMock. You will need to use @PrepareForTest({CACHE.class}) annotation for your class followed by code below in the unit test.

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

      Object testObject = null; // Change this
      PowerMock.mockStatic(CACHE.class);
      expect(CACHE.get(anyString())).andReturn(testObject);
      PowerMock.replay(CACHE.class);

回答by Gavin

In order to get around this you could wrap an interfaced repository type class around class B. Once you have an interface you can then stub it for testing purposes.

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

By doing this you insulate A from the inner workings of B and focus only on the resulting actions of B (I guess this is just another way of saying to program to an interface rather than a concrete class)

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