使用存根提供预设结果

上一节解释了测试替身是一种可以替代生产对象的对象,以便我们可以更轻松地编写测试。在本节中,我们将更仔细地研究该测试替身并对其进行概括。

在前面的 DiceRoll 示例中,测试更容易编写,因为我们用已知的固定值替换了随机数生成。我们真正的随机数生成器使得编写断言变得困难,因为我们永远不确定预期的随机数应该是什么。我们的测试替身是一个提供已知值的对象。然后我们可以计算出断言的预期值,使我们的测试易于编写。

提供这种值的测试替身称为存根。存根总是用我们可以控制的仅用于测试的版本替换我们无法控制的对象。它们总是为我们的被测代码提供已知的数据值。从图形上看,存根如下所示:

image 2025 01 12 16 06 04 525
Figure 1. Figure 8.1 – Replacing a collaborator with a stub

在图中,我们的测试类负责在 Arrange 步骤中将 SUT 连接到适当的存根对象。当 Act 步骤要求我们的 SUT 执行我们想要测试的代码时,该代码将从存根中提取已知的数据值。Assert 步骤可以根据这些已知数据值将导致的预期行为编写。

重要的是要注意为什么这有效。对这种安排的一个反对意见是我们没有测试真实系统。我们的 SUT 连接到一个永远不会成为我们生产系统一部分的对象。这是真的。但这有效,因为我们的测试只测试 SUT 中的逻辑。这个测试不测试依赖项本身的行为。事实上,它绝不能尝试这样做。测试测试替身是单元测试的经典反模式。

我们的 SUT 使用了依赖倒置原则,将自己与存根所替代的对象完全隔离。SUT 如何从其协作者获取数据并不重要。这就是为什么这种测试方法是有效的。

何时使用存根对象

存根(Stubs)在我们的被测试单元(SUT)使用拉取模型与依赖项协作时非常有用。以下是一些使用存根的典型场景:

  • 存根仓库接口/数据库:使用存根替代真实的数据库进行数据访问。

  • 存根参考数据源:用存根数据替换包含参考数据的属性文件或 Web 服务。

  • 提供应用程序对象以转换为 HTML 或 JSON 格式:当测试将数据转换为 HTML 或 JSON 的代码时,使用存根提供输入数据。

  • 存根系统时钟以测试时间相关行为:为了获得可重复的时间调用行为,用已知时间存根时钟调用。

  • 存根随机数生成器以创建可预测性:用存根替代随机数生成器的调用。

  • 存根身份验证系统以始终允许测试用户登录:将对身份验证系统的调用替换为简单的“登录成功”存根。

  • 存根来自第三方 Web 服务的响应,例如支付提供商:将对第三方服务的真实调用替换为对存根的调用。

  • 存根操作系统命令调用:将对操作系统的调用(例如列出目录)替换为预设的存根数据。

在这一节中,我们看到使用存根允许我们控制传递给被测试单元的数据。它支持通过拉取模型从其他地方获取对象。但这并不是对象协作的唯一机制。有些对象使用推送模型。在这种情况下,当我们调用被测试单元的方法时,我们希望它会调用另一个对象的方法。我们的测试必须确认这个方法调用实际上发生了。这是存根无法帮助实现的,因此需要一种不同的方法。在下一节中,我们将介绍这种方法。