硬编码模拟对象与模拟框架

时间:2010-12-06 20:24:42

标签: unit-testing testing mocking

我很好奇人们喜欢用什么方法进行模拟,为什么。我所知道的两种方法是使用硬编码模拟对象和模拟框架。为了演示,我将使用C#概述一个示例。

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

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;
    }
}

然后,在我们的测试中,我们可以使用setter或依赖注入显式地告诉我们的服务使用MockEmployeeRepository。我是嘲笑框架的新手,所以我很好奇我们为什么要使用它们,如果我们能够做到以上几点呢?

6 个答案:

答案 0 :(得分:11)

那不是模拟,它是一个存根。对于存根,你的例子是完全可以接受的。

来自Martin Fowler:

模拟是我们在这里讨论的内容:预先编程了预期的对象,形成了预期接收的呼叫规范。

当你嘲笑某事时,通常会调用“验证”方法。

看看这个Mocks和Stubs之间的差异 http://martinfowler.com/articles/mocksArentStubs.html

答案 1 :(得分:4)

我认为手工编写虚拟对象或使用框架之间的选择很大程度上取决于您正在测试的组件类型。

如果它是被测组件的合同的一部分,以便按照精确的协议与其协作者进行通信,那么仪表化的虚拟对象(“模拟”)就是要使用的东西。使用模拟框架而不是手工编码来测试此类协议通常要容易得多。考虑打开存储库,按照规定的顺序执行一些读取和写入,然后关闭存储库所需的组件 - 即使面对异常也是如此。模拟框架可以更容易地设置所有必要的测试。与电信和过程控制相关的应用(选择几个随机示例)中充满了需要以这种方式进行测试的组件。

另一方面,一般业务应用程序中的许多组件对于他们与协作者的通信方式没有特别限制。考虑一个对大学课程负荷进行某种分析的组件。组件需要从存储库中检索讲师,学生和课程信息。但是它检索数据的顺序并不重要:教师 - 学生 - 课程,学生 - 课程 - 讲师,一次性或其他什么。无需测试和实施数据访问模式。实际上,测试该模式可能是有害的,因为它会不必要地要求特定的实现。在这种情况下,简单的未经装配的虚拟对象(“Stubs”)就足够了,而且模拟框架可能有点过分。

我应该指出,即使在存根时,框架仍然可以让您的生活更轻松。人们并不总是乐于指挥一个人的签名者的签名。想象一下,对从IDataReaderResultSet等厚接口处检索的数据进行处理所需的组件进行单元测试。手工处理此类接口最多是令人不快的 - 特别是如果被测组件实际上只使用了接口中的三种无法使用的方法。

对我来说,需要模拟框架的项目几乎总是系统编程性质(例如数据库或Web基础结构项目,或业务应用程序中的低级管道)。对于应用程序编程项目,我的经验是看到的嘲讽很少。

鉴于我们总是努力尽可能地隐藏凌乱的低级基础设施细节,我们似乎应该将简单的存根远远超过模拟。

答案 2 :(得分:2)

一些区分嘲笑和存根。模拟对象可以验证它是否以预期的方式与之交互。模拟框架可以轻松生成模拟和存根。

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

我一直在手写它们。我在使用Moq时遇到了麻烦,但后来我读了TDD: Introduction to Moq,我觉得我现在得到了他们对古典与模仿方法的看法。今晚我将再次尝试Moq,我认为理解“模仿者”的方法会让我能够让Moq更好地为我工作。

答案 5 :(得分:1)

模拟接口每个测试可以有不同的输出 - 一个测试你可能有一个方法返回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();