译者 | 朱钢
策划 | 信远
在测试方面,微服务需要不同的方法。
微服务应用程序是一组通过网络进行通信的分布式程序,并且与第三方服务和数据库接口进行交互。微服务就其网络性质而言,比传统的整体架构具有更多的故障点。因此我们需要一种不同的、更广泛的测试方法。
那么,我们如何测试微服务应用程序呢?测试金字塔是否仍然有效呢?当涉及第三方服务并且可能出现网络中断时,我们如何进行测试?我们将尝试在这篇文章中回答所有这些问题。
测试微服务的挑战
微服务架构是一个如此深刻的范式转变,我们必须对传统的测试技术进行重新思考。微服务在很多方面与经典的整体式架构不同:
- 分布式:微服务部署在多个服务器上,可能因跨地理区域位置而增加延迟并使应用程序具有连接网络中断的风险。依赖于网络的测试可能会因代码没有错误、中断 CI/CD 管道和阻止开发而失败。
- 自治:只要不破坏API兼容性,开发团队就可以随时自由部署其微服务。
- 增加测试区域:由于每个微服务都具有多个 API 节点,因此还有更多可测试的面需要覆盖。
- 多语言:开发团队可以为其微服务选择最佳语言。在一个大系统中,我们不太可能找到一个适用于所有组件的测试框架。
- 生产是一个移动的目标:由于微服务是可独立部署的,并且由自治团队构建,因此需要额外的检查和边界来确保它们在部署时仍然能够正确的协同工作。
所有这些特征迫使我们思考新的测试策略。
微服务的测试金字塔
测试金字塔是自动化软件测试的规划工具。在传统形式中,金字塔使用三种类型的测试:
- 单元测试
- 集成测试
- 端到端测试
微服务金字塔添加了两种新类型:组件测试和协定测试。
这是微服务测试金字塔的一个版本。在其他情况下,顺序可能会有所不同。有些可能包括集成层中的合约测试。金字塔更像是一个指导方针,而不是写在石头上的东西。
让我们更详细地了解每个金字塔图层的工作原理。
微服务的单元测试
单元测试是最细粒度且数量众多的测试形式之一。一个单元由可以单独测试的类、方法或函数组成。单元测试是开发实践不可分割的一部分,如测试驱动开发或行为驱动开发。
与整体架构相比,微服务中的单元需要网络调用来实现其功能的概率要高得多。当这种情况发生时,我们可以让代码访问外部服务(接受一些延迟和不确定性),或者用Mock 替换调用,这为我们提供了两种处理微服务依赖关系的方法:
- 独立的单元测试:当我们需要测试结果始终确定性时应该使用这个测试。我们使用模拟或存根来隔离待测试代码与外部依赖项。
- 社交化单元测试:允许社交化测试调用其他服务。在此模式下,我们将测试的复杂性推送到测试或过渡环境中。社交化测试是不确定性的,但是当它们通过时,我们可以对它们的结果更有信心。
我们可以使用Mock单独运行单元测试。或者我们可以允许测试的代码调用其他微服务,在这种情况下,我们谈论的是社交化测试。
正如你看到的,平衡信心与稳定性将是贯穿整篇文章的主题。模拟使测试变得更快并减少了不确定性,但是你模拟得越多,你就越不能相信结果。社交化测试尽管有缺点,但更现实。因此,你可能需要在这两种类型之间取得良好的平衡。
合约测试
每当两个服务通过接口耦合时,就会形成契约。该协定指定了所有可能的输入和输出及其数据结构和副作用。服务的消费者和生产者必须遵守合约中规定的规则,以便进行通信。
协定测试确保微服务遵守其协定。它们不会彻底测试服务的行为;它们仅确保输入和输出具有预期特征,并且服务在可接受的时间和性能限制内执行。
根据服务之间的关系,合约测试可以由生产者和/或消费者运行。
- 消费者端合约测试,由下游团队编写和执行。在测试期间,微服务连接到创建者服务的虚假版本或模拟版本,以检查它是否可以使用其 API。
- 生产者端合约测试,在上游服务中运行。这种类型的测试模拟客户端可以发出的各种 API 请求,验证生产者是否与合约匹配。生产者端测试让开发人员知道他们何时会破坏消费者的兼容性。
合约测试可以在上游或下游中运行。生产者测试检查服务没有实现会因服务而中断的更改。消费者测试针对上游生产者的模拟版本(不是真正的生产者服务)运行消费者端组件,以验证消费者可以发出请求并使用来自生产者的预期响应。我们可以使用 WireMock 等工具来重现 HTTP 请求。
如果合约双方测试通过了,则生产者和消费者是兼容的,应该能够通信。合约测试应始终运行在持续集成中,以便在部署前检测兼容性。
你可以在 《Pact 5-minute getting started guide》中进行在线合约测试。 Pact 是一个基于 HTTP 的测试工具,用于编写和运行基于消费者和生产者的合约测试。
微服务集成测试
微服务集成测试的工作方式与其他架构略有不同。它的目标是通过使微服务交互来识别接口缺陷。与合约测试总是模拟一侧不同,集成测试使用真实的服务。
集成测试对评估服务的行为和业务逻辑不感兴趣。相反,我们希望确保微服务能够相互通信,而且可以正确和它们自己的数据库进行通信。我们正在寻找例如丢失 HTTP 请求头或不匹配的请求/响应对之类的问题。因此,集成测试通常在接口级别实现。
使用集成测试来检查微服务是否可以与其他服务、数据库和第三方端点进行通信。
微服务的组件测试
组件是在较大系统内完成一个角色的一个微服务或一组微服务。
组件测试是一种验收测试,在这种测试中我们通过用模拟资源或模拟替换服务来检查组件的行为。组件测试比集成测试更彻底,因为它们会经历“快乐”和“不快乐”的路径 。 例如组件如何响应模拟的网络中断或格式错误的请求。我们想知道组件是否满足其消费者的需求,就像我们在验收或端到端测试中所做的那样。
组件测试对一组微服务执行端到端测试。组件范围之外的服务被模拟。
有两种执行组件测试的方法:进程内和进程外。
进程内组件测试
在此组件测试子类中,测试运行程序与微服务存在于同一线程或进程中。我们在“离线测试模式”中启动微服务,其中模拟其所有依赖项,从而允许我们在没有网络的情况下运行测试。
在与微服务相同的进程中运行的组件测试。该测试在适配器中注入模拟服务,以模拟与其他组件的交互。
仅当组件是单个微服务时,进程内测试才有效。乍一看,组件测试看起来与端到端或验收测试非常相似。唯一的区别是组件测试选择系统的一个部分(组件),并将其与其他部分隔离。该组件经过全面测试,以验证它是否执行其用户或消费者所需的功能。
组件测试和端到端测试可能看起来很相似。但不同之处在于,端到端在类似生产的环境中测试整个系统(所有微服务),而组件在整个系统的隔离部分上进行测试。这两种类型的测试都从用户(或消费者)的角度检查系统的行为,遵循用户将要执行的操作。我们可以用任何语言或框架编写组件测试,但最受欢迎的可能是Cucumber和Capybara。
进程外组件测试
进程外测试适用于任何大小的组件,包括由许多微服务组成的组件。在这种类型的测试中,组件部署在测试环境中,其中所有外部依赖项都被模拟或存根。
在这种类型的组件测试中,复杂性被推到测试环境中,测试环境应该复制系统的其余部分。
为了完善合约测试的概念,您可以探索《Java Spring上合约测试的示例代码》。此外,如果您是Java开发人员,那么这篇文章提供了用于在各个级别测试Java微服务的代码示例。
微服务中的端到端测试
到目前为止,我们已经对系统进行了零碎的测试。单元测试用于测试微服务的各个部分,合约测试涵盖API兼容性,集成测试检查网络调用,组件测试用于验证子系统的行为。只有在自动化测试金字塔的最顶端,我们才能测试整个系统。
端到端(E2E)测试确保系统满足用户的需求并实现业务目标。E2E 套件应使用与用户相同的接口覆盖应用程序中的所有微服务,通常结合使用 UI 和 API 测试。
应用程序应在尽可能靠近生产环境中运行。最理想情况是测试环境将包括应用程序通常需要的所有第三方服务,但有时这些服务可以被模拟,以降低成本或防止滥用。
端到端是模拟用户交互的自动化测试。只有外部第三方服务可能会被嘲笑。
正如测试金字塔所描述的那样,E2E测试是数量最少的,因为它们通常是最难运行和维护的。只要我们专注于用户的操作和他们的需求,我们只需要几个端到端的测试就可以提取很多资源价值。
结论
不同的模式要求改变测试策略。在微服务架构中进行测试比以往任何时候都更加重要,但我们需要调整技术以适应新的开发模型。系统不再由单个团队管理。相反每个微服务所有者都必须尽自己的一份力量,以确保应用程序作为一个整体工作。
一些组织可能会认为单元、合约和组件测试就足够了。其他不满足于没有端到端和集成测试的人可能会选择建立一个 QA 团队来促进跨团队的测试覆盖。
译者介绍
朱钢,51CTO社区编辑,2021年IT影响力专家博主,阿里云专家博主,2019年CSDN博客之星20强,2020年腾讯云+社区优秀作者,11年一线开发经验,曾参与猎头服务网站架构设计,企业智能客服以及大型电子政务系统开发,主导某大型央企内部防泄密和电子文档安全监控系统的建设,目前在北京图伽健康从事医疗软件研发工作。
原文Testing Strategies for Microservices,作者:Tomas Fernandez
链接:https://dzone.com/articles/testing-strategies-for-microservices