微服务应用程序是一组通过网络进行通信的分布式程序,偶尔与第三方服务和数据库进行交互。与传统的单体相比,微服务的网络特性提供了更多的故障点。因此,我们需要一种不同的、更广泛的测试方法。

那么,我们如何测试一个微服务应用程序呢?测试金字塔仍然有效吗?当涉及第三方服务并且可能出现网络中断时,我们如何进行测试?我们将尝试在这篇文章中回答所有这些问题。

1测试微服务的挑战

  • 分布式:微服务部署在多个服务器上,可能跨地理位置,增加延迟并使应用程序暴露于网络中断。依赖网络的测试可能会因为代码没有错误、中断CI/CD 管道和阻塞开发而失败。

  • 自治:只要不破坏 API 兼容性,开发团队可以随时自由地部署他们的微服务。

  • 增加测试区域:由于每个微服务都至少公开了一些 API 端点,因此有更多可测试的表面需要覆盖。

  • Polyglot:开发团队可以为他们的微服务选择最好的语言。在一个大系统中,我们不太可能找到一个适用于所有组件的单一测试框架。

  • 生产是一个移动的目标:因为微服务可以独立部署并由自治团队构建,所以需要额外的检查和边界来确保它们在部署时仍然能够正常运行。

所有这些特征都迫使我们思考新的测试策略。

2微服务的测试金字塔

测试金字塔是自动化软件测试的规划工具。在其传统形式中,金字塔使用三种类型的测试:

  • 单元测试

  • 集成测试

  • 端到端测试

微服务金字塔增加了两种新类型:组件测试和合约测试

这是微服务测试金字塔的一个版本。在其他情况下,顺序可能会有所不同。有些可能包括集成层中的合同测试。事实是,金字塔更像是一个指导方针,而不是一成不变的东西。

让我们更详细地了解每个金字塔层的工作原理。

3微服务的单元测试

单元测试是最细粒度且数量众多的测试形式之一。一个单元由可以单独测试的类、方法或函数组成。单元测试是开发实践不可分割的一部分,如测试驱动开发行为驱动开发

与单体相比,微服务中的单元需要网络调用来实现其功能的可能性要高得多。发生这种情况时,我们可以让代码访问外部服务——接受一些延迟和不确定性——或者用测试替身替换调用,为我们提供两种处理微服务依赖关系的方法:

  • 单独的单元测试:当我们需要测试结果始终是确定性的时,应该使用它。我们使用模拟或存根将被测代码与外部依赖项隔离开来。

  • 社交单元测试:允许社交测试调用其他服务。在这种模式下,我们将测试的复杂性推入测试或暂存环境。社交测试不是确定性的,但是当它们通过时,我们可以对它们的结果更有信心。

我们可以使用test doubles 单独运行单元测试。或者,我们可以允许经过测试的代码调用其他微服务,在这种情况下,我们谈论的是社交测试。

正如您将看到的,平衡信心与稳定性将是贯穿整篇文章的主题。模拟使事情变得更快并减少了不确定性,但是你模拟的越多,你就越不能相信结果。社交测试尽管有缺点,但更现实。因此,您可能需要在这两种类型之间取得良好的平衡。

4微服务的合同测试

每当两个服务通过接口耦合时,就会形成契约。合约指定了所有可能的输入和输出及其数据结构和副作用。服务的消费者和生产者必须遵守合同中规定的规则才能进行通信。

合同测试确保微服务遵守他们的合同。他们没有彻底测试服务的行为;它们只确保输入和输出具有预期的特性,并且服务在可接受的时间和性能限制内执行。

根据服务之间的关系,契约测试可以由生产者、消费者或两者运行。

  • 消费者端合约测试由下游团队编写和执行。在测试期间,微服务连接到生产者服务的虚假或模拟版本,以检查它是否可以使用其 API。

  • 生产者端合约测试在上游服务中运行。这种类型的测试模拟客户端可以发出的各种 API 请求,验证生产者是否与合同匹配。生产者端测试让开发人员知道他们何时会破坏对消费者的兼容性。

合同测试可以在上游或下游运行。生产者测试检查服务没有实现会破坏依赖服务的更改。消费者测试针对上游生产者的模拟版本(不是真正的生产者服务)运行消费者端组件,以验证消费者可以发出请求并使用来自生产者的预期响应。我们可以使用 wiremock等工具来重现HTTP请求。

如果合约双方测试通过,则生产者和消费者是兼容的,应该能够通信。合同测试应始终在持续集成中运行,以在部署前检测不兼容性。

您可以在Pact 5 分钟入门指南中进行在线合同测试。Pact 是一个基于 HTTP 的测试工具,用于编写和运行基于消费者和生产者的合约测试。

5微服务的集成测试

微服务集成测试的工作方式与其他架构略有不同。目标是通过使微服务交互来识别接口缺陷。与合同测试总是模拟一侧不同,集成测试使用真实的服务

集成测试对评估服务的行为或业务逻辑不感兴趣。相反,我们希望确保微服务可以相互通信以及它们自己的数据库。我们正在寻找诸如丢失 HTTP 标头和不匹配的请求/响应配对之类的问题。因此,集成测试通常在接口级别实现。

使用集成测试来检查微服务是否可以与其他服务、数据库和第三方端点进行通信。

6微服务的组件测试

组件是在更大的系统中完成一个角色的一个微服务或一组微服务。

组件测试是一种验收测试,我们通过用模拟资源或模拟替换服务来孤立地检查组件的行为。

组件测试比集成测试更彻底,因为它们经历了快乐和不快乐的路径——例如,组件如何响应模拟的网络中断或格式错误的请求。我们想知道组件是否满足其消费者的需求,就像我们在验收或端到端测试中所做的那样。

undefined

组件测试对一组微服务执行端到端测试。组件范围之外的服务会被模拟。

有两种执行组件测试的方法:进程内和进程外。

# 过程中组件测试

在这个组件测试的子类中,测试运行器与微服务存在于同一线程或进程中。我们以“离线测试模式”启动微服务,其中所有依赖项都被模拟,允许我们在没有网络的情况下运行测试。

只有当组件是单个微服务时,进程内测试才有效。乍一看,组件测试看起来与端到端或验收测试非常相似。唯一的区别是组件测试选择系统的一部分(组件)并将其与其余部分隔离。该组件经过全面测试,以验证它是否执行其用户或消费者所需的功能。

组件和端到端测试可能看起来很相似。但不同之处在于,在类生产环境中端到端测试整个系统(所有微服务),而组件在整个系统的一个孤立部分上进行测试。两种类型的测试都从用户(或消费者)的角度检查系统的行为,遵循用户将执行的旅程。

我们可以使用任何语言或框架编写组件测试,但最流行的可能是Cucumber和Capybara。

# 进程外组件测试

进程外测试适用于任何规模的组件,包括由许多微服务组成的组件。在这种类型的测试中,组件被部署(未更改)在所有外部依赖项都被模拟或删除的测试环境中。

在这种类型的组件测试中,复杂性被推到测试环境中,这应该复制系统的其余部分。

7微服务中的端到端测试

到目前为止,我们已经对系统进行了零碎的测试。单元测试用于测试微服务的各个部分,合约测试涵盖 API 兼容性,集成测试检查网络调用,组件测试用于验证子系统的行为。只有在自动化测试金字塔的最顶端,我们才会测试整个系统。

端到端 (E2E) 测试可确保系统满足用户需求并实现其业务目标。E2E 套件应涵盖应用程序中的所有微服务,使用与用户相同的接口——通常结合 UI 和 API 测试。

应用程序应在尽可能接近生产的环境中运行。理想情况下,测试环境将包括应用程序通常需要的所有第三方服务,但有时,可以模拟这些服务以降低成本或防止滥用。

端到端是模拟用户交互的自动化测试。只能模拟外部第三方服务。

正如测试金字塔所描绘的,E2E 测试数量最少,这很好,因为它们通常最难运行和维护。只要我们关注用户的旅程和他们的需求,我们就可以通过少量的端到端测试来提取大量价值。

8结论

不同的范式需要改变策略。在微服务架构中进行测试比以往任何时候都更加重要,但我们需要调整我们的技术以适应新的开发模型。该系统不再由单个团队管理。相反,每个微服务所有者都必须尽自己的一份力量来确保应用程序作为一个整体运行。

一些组织可能认为单元、合同和组件测试就足够了。其他不满足于没有端到端和集成测试的人可能会选择建立一个 QA 团队来促进跨团队的测试覆盖。

微服务架构是一种如此深刻的范式转变,以至于我们必须重新考虑传统的测试技术。微服务在许多方面不同于经典的单体结构: