从外部开始测试

鉴于自内而外的 TDD 既有挑战也有优势,自外而内的 TDD 有什么不同呢?这一部分将回顾从系统外部开始的替代方法。

自外而内的 TDD 从系统的外部用户开始。这些用户可能是人类用户或机器,消费我们软件提供的某些 API。这种 TDD 方法通过模拟一些外部输入开始,例如提交一个 Web 表单。

测试通常会使用某种测试框架——例如用于 Web 应用程序的 Selenium 或 Cypress——允许测试调用特定的 Web 视图,并模拟在字段中输入文本,然后点击提交按钮。然后我们可以以正常方式使这个测试通过,只是这次我们将编写一些直接处理用户输入的代码。在我们的六边形架构模型中,我们将首先编写用户输入适配器。

我们可以如下说明自外而内的方法:

image 2025 01 12 18 41 58 397
Figure 1. Figure 12.7 – Outside-in view

我们可以看到,一个名为 Web API 的组件是我们关注的焦点。我们将编写一个测试,设置足够的应用程序来运行一个处理 Web 请求的组件。测试将形成一个 Web 请求,将其发送到我们的软件,然后断言发送了正确的 Web 响应。测试还可以检测软件本身,以验证它在内部采取了预期的操作。我们从外部开始测试,随着开发的进展,我们向内移动。

这种 TDD 方法在 Steve Freeman 和 Nat Pryce 的《Growing Object-Oriented Software, Guided by Tests》一书中有所描述。该技术也被称为伦敦或 Mockist 学派的 TDD。原因分别是它最初流行的地点和它对模拟对象的使用。为了测试驱动用户输入适配器作为我们处理的第一个组件,我们需要一个测试替身来代替软件的其余部分。模拟和存根是自外而内 TDD 的固有部分。

自外而内的 TDD,正如预期的那样,有一些优点和缺点。让我们先看看优点:

  • 更少的浪费:自外而内的 TDD 鼓励一种非常最小化的方法来满足外部行为。生成的代码往往高度定制于手头的应用程序。相比之下,自内而外的 TDD 专注于构建一个健壮的领域模型,可能提供比用户最终使用的更多功能。

  • 快速交付用户价值:因为我们从模拟用户请求的测试开始,我们编写的代码将满足用户请求。我们可以几乎立即向用户交付价值。

自外而内的 TDD 也有一些弱点,或者至少是局限性:

  • 最少的抽象:在编写使测试通过所需的最少代码时,自外而内的 TDD 可能导致应用程序逻辑出现在适配器层。这可以在以后重构,但可能导致代码库组织性较差。

  • 倒置的测试金字塔:如果我们所有的 TDD 测试工作都集中在外部响应上,它们实际上是端到端测试。这与测试金字塔的推荐模式相反,后者更喜欢代码库内部更快的单元测试。只有较慢、重复性较差的端到端测试可能会减慢开发速度。

两种传统的 TDD 学派在它们对我们将产生的软件设计的影响方面都提供了一定的优势。下一部分将探讨六边形架构的影响。通过从我们将使用六边形方法的思想开始,我们可以结合两种 TDD 学派的优势。我们最终在自内而外和自外而内的 TDD 方法之间定义了一个自然的测试边界。