Java:单元测试级联的外部接口



考虑一个使用外部jar的类。该类处理类型为D的对象,这些对象是通过对象ABC获得的,所有这些对象都是来自jar的外部对象。

class DProcessor() {  
public void process(PoolOfA pool) {
A a = pool.borrowObject()
...
B b = a.getB()
C c = b.getC()
for (D d : c.getAllDs()) {
// Do something meaningful with d
}
}
}

如何对process(PoolOfA pool)进行单元测试?

到目前为止,我最好的尝试是为所有外部类编写模拟:

PoolOfA pool = mock(PoolOfA.class);
A a          = mock(A.class);
B b          = mock(B.class);
C c          = mock(C.class);
D d1         = mock(D.class);
D d2         = mock(D.class);
D d3         = mock(D.class);
D d4         = mock(D.class);
List listOfDs = new ArrayList<D>();
listOfDs.add(d1);
listOfDs.add(d2);
listOfDs.add(d3);
listOfDs.add(d4);
// Set specific behaviour for each d
when(pool.borrowObject()).thenReturn(a);
when(b.getC()).thenReturn(a);
when(c.getAllDs()).thenReturn(d);
when(b.getC()).thenReturn(c);
when(c.getAllDs()).thenReturn(listOfDs);

这看起来既麻烦又不雅。有更好的方法吗?

当然,更好的方法是重写方法。但如果您因为某种原因无法做到这一点,mockito提供了一个名为"深度存根"的伟大功能。查看文档。

流程真正做的是在循环中处理一些D。我首先要通过更改签名来明确这一点:

public void process(Collection<D> allDs)

现在,您可以通过只嘲笑D来更容易地测试这一点。

如果该方法可以替换现有的方法,那么它可以是公共的,或者如果你不想公开它,那么它也可以是包私有的。在后一种情况下,你可能仍然想测试另一个process方法(采用poolOfA的方法)是否正确地提取了Ds。但这意味着process(PoolOfA)需要对poolOfA了解很多,这似乎是不对的。

这是这本"编写可测试代码指南"提出的想法之一,我认为它包含了有趣的概念。你提到的内容可能会进入"深入合作者"部分。

我建议对流程方法进行一个小的重新设计:它目前负责做两件事:从输入的内部提取D的Iterable并处理它们。

因此,实际上,尽管您的方法声明它期望PoolOfA类型的输入,但它对这个对象绝对不感兴趣。它想要里面的东西。我会将该方法声明为采用Iterable,并将责任传递给调用者,以便为其提供正确的输入。这将阐明该方法的意图,并使其更易于测试。

你可能会说:"这不是一个真正的解决方案,这只是把问题转移到另一个地方!现在我需要测试调用方法!">

好吧,是和否。首先,请记住,你不必对所有内容进行UT,只是为了覆盖范围。你应该把UT的工作重点放在算法代码上,跳过琐碎的对象交互是可以的。

若你们坚持,你们可以使用更强大的嘲讽库,比如PowerMock,只嘲讽你们班的过去,但这是针对另一个问题的讨论。