模拟,存根和虚假对象之间有什么区别

Mar*_*cel 14 unit-testing mocking stub

虽然有足够的资源,即使是在SO上,但在这些Q/A中,只有两个术语相互比较.

那么,简而言之,他们每个人都是什么?它们如何相互关联?或者根本不是吗?

k.m*_*k.m 21

模拟和存根之间的区别非常简单 - 模拟可以使您的测试失败,而存根则不能.这就是全部.此外,您可以将存根视为提供值的东西.如今,假冒只是两者的通用术语(稍后会详述).

让我们考虑一种情况,你必须建立一个通过通信协议发送包的服务(具体细节是无关紧要的).您只需使用包代码提供服务,剩下的就完成了.鉴于下面的代码片段,您能否确定哪个依赖关系将成为存根以及哪个模拟潜在的单元测试?

public class DistributionService
{
    public double SendPackage(string packageCode)
    {
        var contents = this.packageService.GetPackageContents(packageCode);
        if (contents == null)
        {
            throw new InvalidOperationException(
                "Attempt to send non-exisiting package");
        }

        var package = this.packageBuilder.Build(contents);
        this.packageDistributor.Send(package);
    }
}
Run Code Online (Sandbox Code Playgroud)

告诉它packageBuilder简单地提供价值并且没有任何可能的方法可以使任何测试失败,这是相当容易的.那是个存根.即使它看起来更模糊,packageService也是存根.它提供了一个值(我们对该值的处理与存根的观点无关).当然,稍后我们将使用该值来测试是否抛出异常,但它仍然在我们的控制范围内(因为,我们告诉存根确切地要做什么而忘记它 - 它应该对测试没有进一步的影响).

它变得与众不同packageDistributor.即使它提供任何价值,它也不会消耗掉.然而,调用Send似乎是我们实现的非常重要的一部分,我们很可能想要验证它被调用.

在这一点上,我们应该得出packageDistributor一个模拟的结论.我们将有一个专门的单元测试声明该Send方法被调用,如果由于某些原因它不是 - 我们想要知道,因为它是整个过程重要部分.其他依赖项是存根,因为它们只是为其他可能更相关的代码片段提供值.

快速浏览TDD

Stub是存根,在初始实现中也可以用常量值替换:

var contents = "Important package";
var package = "<package>Important package</package>";
this.packageDistributor.Send(package);
Run Code Online (Sandbox Code Playgroud)

这基本上是模拟框架对存根的作用 - 指示它们返回可配置/显式值.老派,手工制作的存根通常就是这样 - 返回恒定值.

显然,这样的代码没有多大意义,但任何曾经做过TDD的人在类开发的早期阶段肯定会看到一堆这样天真的实现.由TDD产生的迭代开发通常有助于识别类的依赖关系的角色.

如今的存根,嘲笑和假货

在这篇文章的开头我提到的只是一个通用的术语.鉴于mock也可以作为存根(特别是在涉及现代模拟框架时),为了避免混淆,最好将这样的对象称为假的.如今,你可以看到这种趋势在不断增长 - 原始的模拟 - 存根区别正逐渐成为过去,并且使用了更多的通用名称.例如:

  • FakeItEasy使用假货
  • NSubstitute使用替代品
  • Moq使用mock(名称是旧的,但无论是存根还是模拟都没有明显的区别)

参考文献,进一步阅读

  • 模拟不是 Martin Fowler的存根 - 你可以看到这篇文章在任何存根/模拟问题下被虚拟链接,这是有原因的
  • 通过Mark Seemann探索测试双打的连续性 - 所有令人困惑的单元测试术语的概述(如果你认为模拟,假和存根就够了,你可能应该知道还有*虚拟**,间谍,双重和什么不是)
  • xunitpatterns.com - 大型单元测试模式书的后备网站, xUnit测试模式: Gerard Meszaros重构测试代码
  • Roy Osherove的单元测试艺术 - 优秀的单元测试入门书( "模拟可以使测试失败,不存根"是Roy的话,不是我的)


phn*_*kha 6

模拟和存根都被称为假对象.在我看来:

  • Stub用于替换外部依赖项,它使我们的测试运行无异常.我们必须使用Assert来确定测试是否失败.Stub仅适用于测试某些功能的结果是否正确

  • 模拟更复杂,通常用于测试行为,例如,验证是否是一个被调用的函数