考虑一个使用外部jar的类。该类处理类型为D
的对象,这些对象是通过对象A
、B
和C
获得的,所有这些对象都是来自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,只嘲讽你们班的过去,但这是针对另一个问题的讨论。