考虑以下类:
public class Node {
private final Collection<Node> mDependants = new ArrayList<>();
private Node mDependency;
public void initialize(final Node node) {
// complex code that might call registerDependency;
}
private void registerDependency(final Node node) {
mDependency = node;
node.registerDependent(this);
}
private void registerDependent(final Node node) {
mDependants.add(node);
}
}
然后是单元测试,如:
import static org.mockito.Mockito.mock;
public class NodeTest {
private Node mTarget;
private Node mDependent;
@Before
public void setUp() {
mTarget = new Node();
mDependent = mock(Node.class);
}
@Test
public void test() {
mTarget.initialize(mDependent);
}
}
由于registerDependent是私有的,mockito实际上不会模拟它。由于mTarget实际上是一个真实的实例,因此当通过initialize执行registerDependency方法时,它将尝试在mock上执行私有方法registerDependent。作为mock的mock将不会被初始化,并且mDependants实际上将为空,导致mdependents .add(node)上的NullPointerException。
正确的测试方法应该是什么?我应该使用两个真实节点而不是一个模拟节点吗?我是否应该使方法公开以允许对方法进行嘲弄?我还错过了别的选择吗?
因为这是一个针对Node的测试,所以尽可能避免模拟Node。比起测试实现是否正确,测试mock框架是否正确工作或规范是否正确定义要容易得多。
我很喜欢JB Nizet在这里给出的SO答案:如果你正在建造一个炸弹雷管,你的频繁测试应该使用真实的雷管和模拟的炸弹。模拟应该针对被测系统的依赖项和协作者,而不是被测系统本身。
如果您的Node是一个接口,并且您的NodeImpl实现可以接受任何节点作为依赖,那么使用mock Node可能更有意义,因为您可以传入具有不同实现的节点,这些实现甚至可能还不存在,而且当您将自己限制为mock接口时,许多Mockito的问题都会消失。然而,由于Node和它的依赖Node是相同的具体类,并且依赖于私有实现细节,因此您可能会更成功地处理实际实例。
此外,这些节点不太可能涉及繁重的服务层或其他依赖关系,这些依赖关系使它们易于嘲弄,并且节点是否表现良好是毫无疑问的:您可以在相邻的测试中看到它。
(旁白:有一些技术可以模拟被测系统中的单个方法——"部分模拟"——但当您不使用遗留代码或繁重的服务时,最好避免使用这些技术。)