硬编码的模拟对象 vs 模拟框架

10

我很好奇人们喜欢使用哪种模拟方法以及为什么。我知道的两种方法是使用硬编码的模拟对象和一个模拟框架。为了说明这一点,我将用C#举一个例子。

假设我们有一个IEmployeeRepository接口,其中有一个名为GetEmployeeById的方法。

public interface IEmployeeRepository
{
    Employee GetEmployeeById(long id);
}
我们可以轻松地创建这个的模拟:
public class MockEmployeeRepository : IEmployeeRepository
{
    public Employee GetEmployeeById(long id)
    {
        Employee employee = new Employee();
        employee.FirstName = "First";
        employee.LastName = "Last";
        ...
        return employee;
    }
}

在我们的测试中,我们可以显式地告诉我们的服务使用MockEmployeeRepository,可以使用setter或依赖注入。我对模拟框架很陌生,所以我很好奇为什么我们要使用它们,如果我们可以做以上操作的话?


如果你对框架没有经验,硬编码会更容易。框架更强大,特别是当你想要对方法是否被调用过多进行断言时。 - k3b
6个回答

11

这不是Mock,而是Stub。对于存根(stubbing)来说,你的示例是完全可接受的。

按照Martin Fowler的说法:

Mocks是我们在这里讨论的:预设期望的对象,形成了它们所期望接收到的调用的规范。

当你在模拟某个东西时,通常会调用一个"Verify"方法。

查看此处以了解Mocks和Stubs之间的区别:http://martinfowler.com/articles/mocksArentStubs.html


哇,我终于明白了模拟对象和存根之间的区别。 - eric

4
我认为手动编写虚拟对象或使用框架之间的选择很大程度上取决于您正在测试的组件类型。如果组件遵循精确协议与其合作者进行通信是测试合同的一部分,那么仪器化的虚拟对象(“模拟”)就是要使用的工具。使用模拟框架测试这种协议通常比手工编码容易得多。考虑一个需要打开存储库、按预定顺序执行某些读取和写入操作,然后关闭存储库的组件,即使面临异常情况也是如此。模拟框架将更容易设置所有必要的测试。与电信和过程控制相关的应用程序(随意挑选几个例子)充满了需要以这种方式进行测试的组件。
另一方面,一般业务应用程序中的许多组件在与其协作者通信的方式上没有特定的限制。考虑一个组件,它执行某种分析,比如大学课程负载分析。该组件需要从存储库中检索教师、学生和课程信息。但是,检索数据的顺序并不重要:教师-学生-课程,学生-课程-教师,一次全部检索或其他任何方式。没有必要测试和强制执行数据访问模式。事实上,测试该模式可能会有害,因为这会不必要地要求特定的实现。在这种情况下,简单的未经仪器化的虚拟对象("桩")就足够了,模拟框架可能过于复杂。
我应该指出,即使在存根时,框架仍然可以大大简化您的生活。人们并不总是有权决定协作者的签名。想象一下对组件进行单元测试,该组件需要处理从 IDataReader ResultSet等厚重接口检索到的数据。手动存根这样的接口最好也很不愉快,特别是如果要测试的组件实际上只使用接口中的三个方法之一。 对于我来说,需要模拟框架的项目几乎总是系统编程性质的(例如数据库或Web基础设施项目,或业务应用程序中的低级管道)。对于应用程序编程项目,我的经验是几乎没有模拟。 鉴于我们始终努力尽可能隐藏混乱的低级基础结构细节,因此似乎我们应该旨在让简单的存根远远超过模拟。

2

一个模拟接口可以在每个测试中有不同的输出 - 在一个测试中,您可能会让一个方法返回 null,在另一个测试中该方法返回一个对象,在另一个测试中该方法抛出异常。这都是在单元测试中配置的,而您的版本则需要编写多个手动对象。

伪代码:

//Unit Test One

MockObject.Expect(m => m.GetData()).Return(null);

//Unit Test Two

MockObject.Expect(m => m.GetData()).Return(new MyClass());

//Unit Test Three

MockObject.Expect(m => m.GetData()).ThrowException();

2

有些人区分模拟对象和存根。模拟对象可以验证它是否按预期方式进行了交互。模拟框架可以使生成模拟对象和存根变得容易。

在您的示例中,您已经为接口中的一个方法存根。考虑一个具有 n 个方法的接口,其中 n 可以随时间而改变。手动实现可能需要更多的代码和维护。


1

我一直在手写它们。我在使用Moq时遇到了麻烦,但是后来我阅读了TDD: Introduction to Moq,现在我认为我理解了他们关于经典方法和模拟方法的说法。今晚我将再次尝试Moq,并且我认为理解“模拟方法”将使我更好地使用Moq。


1
你发布的链接无法使用。请尝试:http://stephenwalther.com/archive/2008/06/12/tdd-introduction-to-moq - IcedDante

1

我倾向于先手写存根和模拟对象,如果可以使用模拟对象框架轻松表达,则重写它,以便我维护的代码更少。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接