【问题标题】:Hard-Coded Mock Objects vs Mocking Framework硬编码的模拟对象与模拟框架
【发布时间】:2010-12-06 20:24:42
【问题描述】:

我很好奇人们喜欢用什么方法来模拟以及为什么。我知道的两种方法是使用硬编码的模拟对象和模拟框架。为了演示,我将概述一个使用 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;
    }
}

然后,在我们的测试中,我们可以明确地告诉我们的服务使用 MockEmployeeRepository,无论是使用 setter 还是依赖注入。我是模拟框架的新手,所以我很好奇我们为什么要使用它们,如果我们可以做到以上几点?

【问题讨论】:

  • 如果您没有使用框架的经验,硬编码会容易得多。框架更强大,特别是如果您想断言某个方法是完全调用还是经常调用。

标签: unit-testing testing mocking


【解决方案1】:

这不是 Mock,而是 Stub。对于存根,您的示例是完全可以接受的。

来自马丁·福勒:

Mocks 就是我们在此讨论的内容:预编程了期望的对象,这些期望形成了它们期望接收的调用的规范。

当你模拟某些东西时,你通常会调用一个“验证”方法。

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

【讨论】:

  • 哇,我终于明白 mock 和 stub 的区别了。
【解决方案2】:

我认为是手动编写虚拟对象还是使用框架编写虚拟对象在很大程度上取决于您正在测试的组件类型。

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

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

我应该指出,即使是存根,框架仍然可以让您的生活更轻松。一个人并不总是有口述合作者签名的奢侈。想象一下对一个组件进行单元测试,该组件需要处理从IDataReaderResultSet 等厚接口检索到的数据。手动打桩这样的接口充其量是令人不快的——尤其是当被测组件实际上只使用接口中无数方法中的三种时。

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

鉴于我们一直努力尽可能地隐藏混乱的低级基础架构细节,看来我们应该致力于让简单存根的​​数量远远超过模拟。

【讨论】:

    【解决方案3】:

    有些区分模拟和存根。模拟对象可以验证它是否已以预期的方式进行交互。模拟框架可以轻松生成模拟和存根。

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          我一直在手写它们。我在使用 Moq 时遇到了问题,但后来我读到了 TDD: Introduction to Moq,我想我现在明白了他们对经典与模拟方法的看法。今晚我将再次尝试 Moq,我认为了解“mockist”方法将为我提供使 Moq 更好地为我工作所需的东西。

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-10-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多