【问题标题】:Why would I write a fake class and unit test it?为什么我要编写一个假类并对其进行单元测试?
【发布时间】:2008-11-24 14:42:14
【问题描述】:

我了解需要测试具有逻辑的类(例如,可以计算折扣的类),您可以在其中测试 实际 类。

但我刚刚开始为一个将充当存储库(从数据库获取对象)的项目编写单元测试。我发现自己正在编写一个实现ISomethingRepository 接口的“假”存储库。它使用Dictionary<Guid, Something> 进行内部存储。它实现了接口的Add(Something)GetById(Guid) 方法。

我为什么要写这个?我写的东西在部署时不会被软件实际使用,对吧?我真的看不出这个练习的价值。

我还得到了使用模拟对象的建议,我可以提前设置它来满足某些期望。这对我来说似乎更没有意义:当然测试会成功,我已经模拟/伪造它成功了!而且我仍然不确定实际软件在连接到数据库时是否会正常运行......

困惑...

有人可以指出正确的方向来帮助我理解这一点吗?

谢谢!

【问题讨论】:

  • 我对此表示赞同,因为这经常让我感到厌烦。大多数快速 TDD 倡导者往往会意外陷入这种情况,并最终测试现实中不会发生的场景/代码。

标签: unit-testing mocking


【解决方案1】:

您不是在测试您的模拟对象,而是在与它交互的其他一些类。因此,您可以例如测试控制器是否将保存方法调用转发到您的假存储库。如果你“测试你的假对象”,那就有问题了

【讨论】:

  • 是的,我就是这么想的。那么我对测试我的数据访问代码有错误的想法。例如:tinyurl.com/6qojvh
  • 嗯,在我们讨论的背景下,我有点想知道您对那篇文章的看法。
【解决方案2】:

不要测试模拟类。使用模拟类测试生产类。

测试支持类的全部意义在于拥有可以预测其行为的东西。如果您需要测试测试支持类以预测它的行为,那就有问题了。

在您在评论中链接的假数据库文章中,作者需要对他的假数据库进行单元测试,因为它是他的产品(至少在文章的上下文中)。

编辑:更新的术语更加一致。

  • Mock - 由模拟框架创建
  • 假的 - 手动创建的,实际上可能有一些功能。
  • 测试支持 - 模拟、假冒、存根和所有其他内容。不是生产。

【讨论】:

  • "Do test the production class using the mock class" 好的,那么如果我的生产数据访问类使用NHibernate,我应该模拟NHibernate,然后测试生产类?所以生产类的 NHibernate 部分应该是可注入的。并且不需要编写虚假的数据访问类?
  • 对。您应该模拟 NHib,并且不需要提供假货。除非 NHib 的公共合约过于宽泛......那么你应该包装 NHib 并模拟包装器。
  • 但是由于您的数据访问已经从应用程序的其余部分包装了 NHib... 模拟您的数据访问以测试应用程序的其余部分(最大价值)。
【解决方案3】:

模拟/存根对象的目的不是要测试而不是您要测试的单元,而是允许您在不需要其他的情况下测试该单元em> 类。

基本上这样您就可以一次测试一个类,而不必测试它们也依赖的所有类。

【讨论】:

  • 是的,我就是这么想的。因此,在通常使用存储库代码的“更高”层中,我可以注入一个模拟进行测试,这样它就不会真正连接到数据库,对吗?
【解决方案4】:

您不应该测试模拟类。

您通常做的是:为您正在测试的类与之交互的所有类创建模拟类。

假设您正在测试一个名为 Bicycle 的类,它接收 Wheel、Saddle、HandleBar 等类的构造函数对象。

然后在你想要测试的 Bike 类中测试它的方法 GetWeight,它可能会遍历每个部分并调用它们的属性/方法 Weight,然后返回总数。

你做什么:

  • 您为每个部分编写一个模拟类 (车轮、鞍座等) 实现权重位
  • 然后将这些模拟类传递给 Bicycle
  • 测试 Bicycle 类的 GetWeight 方法

这样您就可以专注于测试 Bicycle 类上的 GetWeight,以一种独立于其他类的方式(比如它们尚未实现、不确定等)

【讨论】:

    【解决方案5】:

    谁在监视观察者?

    有趣的是,例如,如果模拟实现为极端情况抛出特定异常,那么您知道使用或依赖 IRepositorySomething 的类可以处理现实生活中抛出的异常。其中一些异常是您无法使用测试数据库轻松生成的。

    您不使用单元测试来测试 Mock 对象,而是使用它来测试依赖它的类。

    【讨论】:

    • 我发现创造特殊情况是你可以用模拟做的最有用的事情之一。 +1
    【解决方案6】:

    您可以使用工具(如 Rhino 或 Typemock)来模拟它,而不是自己编写假类。这比自己编写所有的模拟要容易得多。而且就像其他人所说的,没有必要测试假代码,如果你使用该工具,那就没有代码了。

    【讨论】:

      【解决方案7】:

      我实际上发现了我们在存储库实现测试中使用的模拟类的两种用途。

      首先是测试使用您提到的“ISomethingRepository”等效实现的服务。但是,我们的存储库实现是由工厂创建的。这意味着我们确实针对“ISomethingRepository”编写测试,而不是直接针对“MockSomethingRepository”。通过针对接口进行测试,我们可以轻松断言我们测试的代码覆盖率覆盖了 100% 的接口。代码审查提供了对新接口成员进行测试的简单验证。即使开发人员正在针对工厂返回的模拟运行,构建服务器也有不同的配置来测试工厂在夜间构建中返回的具体实现。它在测试覆盖率和本地性能方面提供了两全其美。

      第二个用途是我很惊讶没有其他人提到过的用途。我的团队负责中间层。我们的网络开发人员负责网络产品的前端。通过构建模拟存储库实现,不存在在前端工作开始之前等待数据库建模和实现的人为障碍。可以编写基于 mock 构建的视图,以提供最少量的“真实”数据,以满足 Web 开发人员的期望。例如,可以提供数据以包含最小和最大长度的字符串数据,以验证两者都不会破坏它们的实现等。

      由于我们使用的工厂与返回哪个“ISomethingRepository”有关,因此我们有本地测试配置、构建测试配置、生产配置等。我们有意确保项目中的任何团队都没有不合理的等待次因为另一个团队的实施时间。最大的等待时间仍然由开发团队提供,但我们能够以比前端开发更快的速度开发我们的领域对象、存储库和服务。

      当然,YMMV。 ;-)

      【讨论】:

        【解决方案8】:

        您编写名为 Stub 或 Mock 对象的“假”类,因为您想以简单的方式测试实现,而不需要测试真正的具体类。目的是通过仅测试接口(或抽象类)来简化测试。

        在您的示例中,您正在测试具有字典的内容。它可能由数据库真实填充,或者背后有很多逻辑。在您的“假”对象中,您可以通过使所有数据保持不变来简化一切。这样您只测试接口的行为,而不是具体对象的构建方式。

        【讨论】:

        • 我明白,但这有什么价值?我写了 fake 并测试了它。它不在任何地方的生产代码中使用。那么为什么要编写和测试呢?
        • 在路径中没有具体元素的情况下测试您的实现。您可以取出大量代码并专注于测试目的。我在我的帖子中的例子很好......数据库......你不需要有一个“真正的数据库”,你可以伪造它......
        【解决方案9】:

        通常不需要在数据访问层中运行经典的单元测试。 也许您可以使用单元测试框架的功能为您的数据访问类编写集成式单元测试,即集成测试(= 将您的数据访问层代码与数据库集成)。

        例如,在 Spring 项目中,您可以使用 Spring Testcontext 在单元测试中启动 Spring 上下文,然后连接到真实数据库并测试查询是否返回正确的结果。您可能需要一个自己的数据库来进行单元测试,或者您可以将它们与开发人员数据库连接起来。

        【讨论】:

          【解决方案10】:

          请查看以下文章以获得对此的详细解释:

          https://web.archive.org/web/20110316193229/http://msdn.microsoft.com/en-us/magazine/cc163358.aspx

          基本上,如果您编写了一个假对象并且结果证明它相当复杂,那么有时值得对假对象进行单元测试以确保它按预期工作。

          由于存储库可能很复杂,因此为其编写单元测试通常很有意义。

          【讨论】:

          • 不幸的是,这个链接似乎死了。这是指哪一期 MSDN 杂志?
          • @npace - 感谢您告诉我。我已链接到来自 Wayback 机器的缓存副本。尽管信息可能非常有用,但似乎不再在线提供它的副本。
          • 谢谢,我喜欢这篇文章!不过,关于单元测试赝品有一个重要的警告——“如果你开始考虑对赝品本身进行单元测试,那你可能做得过火了。”也就是说,有时无法避免制造复杂的假货。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-07
          相关资源
          最近更新 更多