【问题标题】:Unit-testing a class that calls a static method对调用静态方法的类进行单元测试
【发布时间】:2017-02-16 18:30:57
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 你的课程 B 甚至没有编译
  • 我只是想说明这些课程是怎样的。我抽象了很多只是为了说明的目的,这不是真正的类。
  • 一般来说,如果你必须模拟一个对静态方法的调用,这意味着你的代码没有正确编写,应该重写为可测试的,所以如果你提供一些工作是很重要的想知道你应该如何重写你的代码
  • 将缓存类作为依赖注入而不是使其成为静态。

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


【解决方案1】:

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

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

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

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


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

 B.setServiceAdapter(new ServiceAdapter());       

您在ATest。不在BTestATest 应该只有Bmock,因此不需要传递ServiceAdapter 的实例。

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

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

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

如果你有太多的依赖,当然有些部分可以移动到其他/新的服务。

【讨论】:

  • 我认为您还应该解释一下,为了清楚起见,除了将 B 类的实例注入 A 之外,还需要将 getObjectFromCache 方法设为实例方法而不是静态方法。很想建议也使用 CACHE 字段和实例字段而不是静态字段,即使它是私有的。
  • 有时我们无法折射,因为我们在类中使用了第 3 方静态类,并且仍然需要对其进行测试。
  • @mFeinstein 这对我的回答有何影响?
  • 嗯,你说“因此折射B更实用”的部分......这是一个有时我们无法做到的解决方案。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-20
      • 1970-01-01
      • 1970-01-01
      • 2020-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多