为什么要创建模拟对象?

时间:2009-09-12 02:14:41

标签: unit-testing mocking testing

在最近的一次采访中,有人问我为什么要创建模拟对象。我的回答是这样的:“拿一个数据库 - 如果你正在编写测试代码,你可能不希望那个测试实时连接到将要执行实际操作的生产数据库。”

从回答来看,我的回答显然不是面试官所寻求的。什么是更好的答案?

7 个答案:

答案 0 :(得分:30)

我总结如下:

  • 隔离 - 您可以独立测试方法,只测试方法。您的测试成为真实单元测试(最重要的恕我直言)
  • 减少测试开发时间 - 使用模拟然后创建整个类通常更快,只是为了帮助您进行测试
  • 它允许您测试即使您没有实现所有依赖项 - 您甚至不需要创建例如您的存储库类,并且您将能够测试将使用此存储库的类
  • 让您远离外部资源 - 从某种意义上讲,您无需访问数据库,调用Web服务,读取文件,发送电子邮件或向信用卡,等等...

在一次采访中,我建议包括,当开发人员使用依赖注入时,一旦它允许您拥有更多控制权,并且更容易构建测试,那么模拟效果会更好。

答案 1 :(得分:5)

进行单元测试时,每个测试都旨在测试单个对象。但是,系统中的大多数对象都将具有与之交互的其他对象。模拟对象是这些其他对象的虚拟实现,用于隔离被测对象。

这样做的好处是,任何失败的单元测试通常会将问题与测试对象隔离开来。在某些情况下,问题将出在模拟对象上,但这些问题应该更容易识别和修复。

也可以为模拟对象编写一些简单的单元测试。

它们通常用于创建模拟数据访问层,以便单元测试可以与数据存储隔离运行。

其他用途可能是在MVC模式中测试控制器对象时模拟用户界面。这样可以更好地自动测试UI组件,这可以在某种程度上模拟用户交互。

一个例子:

public interface IPersonDAO
{
    Person FindById(int id);
    int Count();
}

public class MockPersonDAO : IPersonDAO
{
    // public so the set of people can be loaded by the unit test
    public Dictionary<int, Person> _DataStore;

    public MockPersonDAO()
    {
        _DataStore = new Dictionary<int, Person>();
    }

    public Person FindById(int id)
    {
        return _DataStore[id];
    }

    public int Count()
    {
        return _DataStore.Count;
    }
}

答案 2 :(得分:5)

为了在这里添加精细答案,模拟对象用于自上而下或自下而上的结构编程(也是OOP)。它们用于向上层模块(GUI,逻辑处理)提供数据或充当模拟输出。

考虑自上而下的方法:首先开发GUI,但GUI应该有数据。所以你创建了一个模拟数据库,它只返回一个std :: vector&lt;&gt;数据的。您已经定义了关系的“合同”。谁在乎数据库对象内部发生了什么 - 只要我的GUI列表得到一个std :: vector&lt;&gt;我很高兴。这可以提供模拟用户登录信息,无论您需要什么才能使GUI工作。

考虑采用自下而上的方法。您编写了一个读取分隔文本文件的解析器。你怎么知道它是否有效?您为这些对象编写了一个模拟“数据接收器”,并将数据路由到那里以验证(尽管通常)数据是否正确读取。上一级的模块可能需要2个数据源,但您只写了一个。

在定义模拟对象时,您还定义了关系的契约。这通常用于测试驱动编程。您编写测试用例,使用模拟对象使其工作,并且通常情况下,模拟对象的接口成为最终接口(这就是为什么在某些时候您可能希望将模拟对象的接口分离为纯抽象类)

希望这有帮助

答案 3 :(得分:3)

模拟对象/功能在团队中工作时也很有用。如果您正在处理代码库的一部分,该代码库依赖于其他人负责的代码库的不同部分 - 仍在编写或尚未编写 - 模拟对象/函数在给你一个预期的输出,这样你就可以继续工作,而不是等待另一个人完成他们的工作。

答案 4 :(得分:3)

以下是a few situations where mocking is indispensabl e:

  1. 在测试GUI交互时
  2. 当您测试Web App时
  3. 当您测试与硬件交互的代码时
  4. 在测试旧版应用时

答案 5 :(得分:2)

我将在这里采取不同的方向。 Stubbing / Faking完成了上面提到的所有事情,但也许采访者认为模拟是一个假对象导致测试通过或失败。我的基础是xUnit terminology。这可能导致对state testing verses behavior / interaction testing的一些讨论。

他们可能一直在寻找的答案是:模拟对象与存根不同。存根模拟受测试方法的依赖关系。存根不应导致测试失败。模拟执行此操作还会检查调用方式和时间。模拟导致测试根据基础行为通过或失败。这有利于在测试期间减少对数据的依赖,但将其与该方法的实现更紧密地联系起来。

当然这是猜测,他们更有可能只是想让你描述存根和DI的好处。

答案 6 :(得分:0)

采取略微不同的方法(因为我认为上面已经很好地覆盖了模拟):
“拿一个数据库 - 如果您正在编写测试代码,您可能不希望该测试实时连接到将要执行实际操作的生产数据库。”

IMO是陈述示例使用的一种不好的方式。在使用或不使用模拟测试期间,您永远不会“将其连接到prod数据库”。每个开发人员都应该有一个开发人员本地数据库来测试。然后你会移动测试环境数据库然后可能是UAT并最终生产。您并不是在嘲笑要避免使用实时数据库,而是在模拟,以便不直接依赖于数据库的类不需要您设置数据库。

最后(我接受我可能会对此有所评论)IMO开发人员本地数据库是单元测试中有效的事情。在测试直接与数据库交互并在测试间接访问数据库的代码时使用模拟的代码时,您应该只是命中它。