模拟和存根有什么区别?

我已经阅读了有关测试中的模拟与存根的各种文章,包括Martin Fowler 的 Mocks Are n't Stubs ,但仍然不了解它们之间的区别。

答案

前言

对象有几种定义,它们不是真实的。通用词是test double 。该术语包括: 虚拟伪造存根模拟

参考

根据马丁 · 福勒的文章

  • 虚拟对象被传递,但从未实际使用过。通常它们仅用于填充参数列表。
  • 对象实际上具有有效的实现,但是通常采取一些捷径,这使它们不适合生产(内存数据库就是一个很好的例子)。
  • 存根提供对测试期间进行的呼叫的固定答复,通常通常根本不响应测试中编程的内容。存根还可以记录有关呼叫的信息,例如电子邮件网关存根,它可以记住 “已发送” 的消息,或者仅记住 “已发送” 的消息数量。
  • 嘲笑是我们在这里谈论的话题 :用期望预先编程的对象,这些对象形成了期望接收的呼叫的规范。

样式

嘲弄与存根 = 行为测试与状态测试

原理

根据测试的原理, 每个测试只有一件事 ,一个测试中可能有多个存根,但通常只有一个模拟。

生命周期

使用存根测试生命周期:

  1. 设置 - 准备要测试的对象及其存根协作者。
  2. 练习 - 测试功能。
  3. 验证状态 - 使用断言检查对象的状态。
  4. 拆卸 - 清理资源。

使用模拟测试生命周期:

  1. 设置数据 - 准备要测试的对象。
  2. 设置期望 - 在主要对象正在使用的模拟中准备期望。
  3. 练习 - 测试功能。
  4. 验证期望 - 验证是否已在模拟中调用了正确的方法。
  5. 验证状态 - 使用断言检查对象的状态。
  6. 拆卸 - 清理资源。

摘要

模拟和存根测试都为以下问题提供了答案: 结果是什么?

使用模拟进行测试还对以下方面感兴趣: 如何获得结果?

存根

我相信最大的区别是您已经以预定的行为编写了存根。因此,您将拥有一个类,该类实现您为测试目的而伪装的依赖项(最有可能是抽象类或接口),并且该方法将仅通过设置的响应进行处理。他们不会做任何花哨的事情,并且您已经在测试之外为其编写了存根代码。

嘲笑

模拟是在测试过程中必须设置的期望值。模拟不是以预定的方式设置的,因此您具有在测试中执行该模拟的代码。嘲笑是在运行时确定的,因为设置期望的代码必须在它们执行任何操作之前运行。

存根和存根之间的区别

用模拟编写的测试通常遵循initialize -> set expectations -> exercise -> verify测试模式。虽然预写的存根将遵循initialize -> exercise -> verify

存根和存根之间的相似性

两者的目的都是消除测试一个类或函数的所有依赖关系,以便您的测试在尝试证明时更加专注和简单。

存根是一个简单的伪造对象。它只是确保测试顺利进行。
模拟是更聪明的存根。您验证您的测试通过了。

这是每个示例的说明,后面是真实示例。

  • 虚拟 - 伪造的值可以满足API

    示例 :如果要测试的类的方法在构造函数中需要许多强制性参数,而这些参数对测试没有影响 ,则可以创建虚拟对象以创建类的新实例。

  • 伪造 - 创建一个类的测试实现,该类可能依赖于某些外部基础结构。 (优良作法是单元测试实际上与外部基础结构交互。)

    示例 :创建用于访问数据库的伪造实现,将其替换in-memory集合。

  • 存根重写方法可返回硬编码的值,也称为state-based

    示例 :您的测试类取决于需要 5 分钟才能完成的Calculate()方法。无需等待 5 分钟,您可以用返回硬编码值的存根替换其实际实现。仅花费一小部分时间。

  • 模拟 - 与Stub非常相似,但interaction-based而不是基于状态。这意味着您不希望Mock返回任何值,而是假设已完成特定的方法调用顺序。

    示例:您正在测试用户注册类。调用Save ,应该调用SendConfirmationEmail

StubsMocks实际上是Mock子类型,它们都将实际实现与测试实现互换,但是出于不同的特定原因。

codeschool.com课程“僵尸的 Rails 测试” 中 ,他们给出了以下术语的定义:

存根

用返回指定结果的代码替换方法。

嘲笑

带有断言该方法被调用的存根。

因此,正如肖恩 · 哥本哈根(Sean Copenhaver)在回答中所描述的那样,区别在于嘲笑设定了期望(即做出断言,即是否或如何调用它们)。

存根不会使您的测试失败,模拟可以。

我认为Roy Osherove在他的书《单元测试的艺术》 (第 85 页)中给出了关于此问题的最简单,更清晰的答案。

告诉我们正在处理存根的最简单方法是注意到存根永远不会使测试失败。测试使用的断言始终与被测类相对。

另一方面,测试将使用模拟对象来验证测试是否失败。 [...]

同样,模拟对象是我们用来查看测试是否失败的对象。

这意味着,如果您对假货进行断言,则意味着您将假货用作模拟,如果仅使用假货来运行测试而没有断言,则将假货用作存根。

阅读以上所有说明,让我尝试凝结一下:

  • 存根(Stub) :一段伪代码,可以运行测试,但是您并不关心会发生什么。
  • 模拟 :作为验证的一部分,您正确验证了一段虚拟代码。
  • 间谍 :一段伪代码,它拦截对真实代码的某些调用,使您可以在不替换整个原始对象的情况下验证调用。

模拟只是测试行为,确保调用了某些方法。存根是特定对象的可测试版本(本身)。

你用苹果的方式是什么意思?

如果将其与调试比较:

存根就像确保方法返回正确的值

模拟就像实际上进入了该方法,并在返回正确的值之前确保其中的所有内容正确。