如何使用 mockito 对图节点进行单元测试?
How to unit test graph nodes with mockito?
考虑以下 class:
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。作为模拟的模拟将不会被初始化,并且 mDependants 实际上将为空,从而导致 mDependats.add(node).
上的 NullPointerException
正确的测试方法应该是什么?我应该使用两个真实节点而不是模拟节点吗?我应该使方法 public 允许模拟该方法吗?我还缺少其他选项吗?最终节点 node
因为这是对 Node 的测试,所以尽可能避免模拟 Node。这使得测试模拟框架是否正常工作或者您的规范是否正确定义变得非常容易,而不是测试您的 实现 是否正确。
我很喜欢 JB Nizet 的说法:如果你正在制造炸弹雷管,你的频繁测试应该使用 真实 雷管和模拟 炸弹。模拟应该用于被测系统的 dependencies 和 collaborators,而不是被测系统本身。
如果您的 Node 是一个接口,并且您的 NodeImpl 实现可以接受任何 Node 作为依赖项,那么使用模拟 Node 可能更有意义——两者都是因为您可以传入具有不同实现的 Node,这些实现甚至可能不还存在,并且因为当您将自己限制在模拟接口时,Mockito 的许多陷阱都会消失。但是,由于 Node 及其依赖的 Node 是相同的具体 class 并且依赖于私有实现细节,因此使用真实实例可能会取得更大的成功。
此外,这些节点不太可能涉及繁重的服务层或其他有助于模拟的依赖项,并且毫无疑问节点是否表现良好:您可以在相邻测试中看到它。
(另外:有一些技术可以模拟被测系统中的各个方法——"partial mocking"——但是当你不使用遗留代码或繁重的服务时,也最好避免这些方法。)
考虑以下 class:
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。作为模拟的模拟将不会被初始化,并且 mDependants 实际上将为空,从而导致 mDependats.add(node).
上的 NullPointerException正确的测试方法应该是什么?我应该使用两个真实节点而不是模拟节点吗?我应该使方法 public 允许模拟该方法吗?我还缺少其他选项吗?最终节点 node
因为这是对 Node 的测试,所以尽可能避免模拟 Node。这使得测试模拟框架是否正常工作或者您的规范是否正确定义变得非常容易,而不是测试您的 实现 是否正确。
我很喜欢 JB Nizet 的说法
如果您的 Node 是一个接口,并且您的 NodeImpl 实现可以接受任何 Node 作为依赖项,那么使用模拟 Node 可能更有意义——两者都是因为您可以传入具有不同实现的 Node,这些实现甚至可能不还存在,并且因为当您将自己限制在模拟接口时,Mockito 的许多陷阱都会消失。但是,由于 Node 及其依赖的 Node 是相同的具体 class 并且依赖于私有实现细节,因此使用真实实例可能会取得更大的成功。
此外,这些节点不太可能涉及繁重的服务层或其他有助于模拟的依赖项,并且毫无疑问节点是否表现良好:您可以在相邻测试中看到它。
(另外:有一些技术可以模拟被测系统中的各个方法——"partial mocking"——但是当你不使用遗留代码或繁重的服务时,也最好避免这些方法。)