第9章 微服务架构中的测试策略(上)
前言
传统测试在微服务架构中有两大缺点:手动测试效率极低、在交付流程中才进行测试为时已晚;应该采取新的测试策略提高微服务架构的可测试性;
进行自动化测试是缩短交付周期的唯一方法;
这是一本关于微服务架构设计方面的书,这是本人阅读的学习笔记。以下对一些符号做些说明:
()为补充,一般是书本里的内容;
[]符号为笔者笔注;
1. 微服务架构中的测试策略概述
本章重点在用于验证应用程序或服务的功能的自动化测试;
1.1 编写自动化测试
图解:
- 自动化测试通常使用测试框架编写,如JUnit;
- 自动化测试通常包括四个阶段:设置环境、执行测试、验证结果、清理环境;
- 清理环境很容易被忽略,在涉及数据库的测试可能需要在测试后将数据库状态回滚到设置环境阶段的初始状态;
- 测试套件是一组测试类的集合,测试由测试运行器执行;
1.2 使用模拟和桩进行测试
用来解决被测系统与其他系统之间的一些麻烦依赖关系;
图解:
- 使用测试替身来消除被测系统的依赖性;
- 测试替身是一个对象,该对象负责模拟依赖项的行为;
- 有两种类型的测试替身:桩(stub)和模拟(mock)
- 桩是一个测试替身,它代表依赖项来向被测系统发送调用的返回值;
- 模拟也是一个测试替身,用来验证被测系统是否正确调用依赖项;此外,模拟通常也扮演桩的角色;
1.3 使用范围对测试进行分类
- 单元测试:测试服务的一小部分,如类;
- 集成测试:验证服务是否可以与基础设施服务(如数据库)或其他应用程序服务进行交互;
- 组件测试:单个服务的验收测试;
- 端到端测试:整个应用程序的验收测试;
1.4 使用测试象限对测试进行分类
图解
- 两个维度:
- 测试是面向业务还是面向技术:使用领域专家的术语来描述面向业务的测试,使用开发人员的术语和实现来描述面向技术的测试;
- 测试的目标是协助开发还是寻找产品缺陷:开发人员使用协助开发的测试作为日常工作的一部分;寻找产品缺陷的测试旨在确定需要改进的部分;
- 四种不同测试类别:
- Q1协助开发 / 面向技术:单元和集成测试;
- Q2协助开发 / 面向业务:组件和端到端测试;
- Q3寻找产品缺陷 / 面向业务:易用性和探索性测试;
- Q4寻找产品缺陷 / 面向技术:非功能性验收测试,如性能测试;
1.5 使用测试金字塔对测试进行分类
图解:
- 测试范围越大,其构成部件越多,可靠性越低;
- 强调要对服务的每一个细分元素进行测试,能最大限度地减少测试整个服务的组件测试数量;
1.6 微服务架构中的测试挑战
进程通信是微服务架构的核心,应用程序模块之间的任何交互都是通过编程语言级别的API进行的;因此测试验证API的服务是否能与其依赖关系和客户端进行正常交互尤为重要;
图解:
- 图中的进程间通信方式:
- REST客户端 - 服务端:API Gateway将请求路由到服务并实现API组合;
- 领域事件使用者 - 发布者:Order History Service使用Order Service发布的事件;
- 命令式消息请求方 - 回复方:Order Service将命令式消息发送到各种服务并使用回复;
- 一对服务之间的交互代表了这两个服务之间的契约或协议,契约要求双方就事件消息结构及其发布的通道达成一致;
- 作为开发人员,需要确保服务具有稳定的API,做出的改动尽量不要破坏原有的API;
- 测试两个服务可以交互的一种方法是同时运行两个服务,调用触发通信的API,并验证是否有预期结果;这会遇到集成的问题,解决方案是消费者驱动的契约测试;
1.7 消费者驱动的契约测试
图解:
- 消费者驱动的契约测试模式:验证服务是否满足它的消费者期望;
- 消费者契约测试模式:验证服务的客户端是否可以与服务通信;
- 消费者驱动的契约测试通常使用样例测试,消费者和提供者之间的交互由一组样例定义,称为契约;每个契约都包含在一次交互期间交换的样例消息;
- 消费者契约测试侧重于验证提供者API的参数定义是否符合消费者的期望;
- 对于REST接口,契约测试将验证提供者程序实现的接口是否:
- 具有预期的HTTP方法和路径;
- 接受预期的HTTP头部;
- 接受请求主体;
- 返回预期中的响应,包括状态代码、头部和主体等;
- REST API的消费者契约测试实际上是通过模拟控制器进行的测试;
1.8 使用Spring Cloud的契约测试服务
图解:
- API Gateway团队(消费者)编写的契约定义API Gateway如何与Order Service进行交互;
- 编写的契约包括API Gateway可能发送给Order Service的HTTP请求和预期的HTTP响应;
- Order Service团队(提供者)使用这些契约来测试Order Service,并使用它们来测试API Gateway;测试代码Spring Cloud Contract生成;
- Order Service团队(提供者)将测试Order Service的契约打包成jar包发布到Maven存储库;
- API Gateway团队(消费者)使用已发布的契约为API Gateway编写测试;
- 一个契约示例;
- 请求元素是REST接口 GET/orders/{ orderId } 的一个HTTP请求;
- 响应元素是一个该接口对应的HTTP响应,它描述了API Gateway所期望的Order;
1.9 部署流水线
图解:
- 其包含一系列执行测试套件的阶段,后面是一个发布或部署服务的阶段;
- 理想情况下流水线是完全自动化的,也可能包含手动步骤;
- 部署流水线包含以下几个阶段:
- 提交前测试阶段:执行单元测试。这是由开发人员在提交代码更改之前执行的;
- 提交测试阶段:编译服务,执行单元测试,并执行静态代码分析;
- 集成测试阶段:执行集成测试;
- 部署阶段:将服务部署到生产环境中;
2. 为服务编写单元测试
2.1 两种类型的单元测试
图解:
- 独立型单元测试:使用针对类的依赖性的模拟对象隔离测试;
- 协作型单元测试:测试一个类及其依赖项;
2.2 类的职责决定使用哪种单元测试
图解:
- 每个类的典型策略如下的:
- Order等具有持久化身份对象的实体,使用协作型单元测试;
- Money等作为值集合对象的实体,使用协作型单元测试;
- Sages等需要维护服务之间的数据一致性,使用协作型单元测试;
- OrderService等实现不属于实体或值对象的业务逻辑的类,使用独立型单元测试;
- OrderController等处理HTTP请求的控制器类,使用独立型单元测试;
- 入站和出站等消息网关,使用独立型测试;
2.3 为实体编写单元测试
- 单元测试可以彻底测试业务逻辑;
2.4 为值对象编写单元测试
- 对值对象的测试通常会创建特定状态的值对象,调用其中一个方法,并对返回值进行断言;
- 这里使用独立型单元测试,因为此时Money类没有任何依赖;前文说使用协作型单元测试,是针对FTGO中Money对象的实际情况;
2.5 为Saga编写单元测试
- Saga会实现重要的业务逻辑,其是一个持久化对象,向Saga参与方发送命令式消息并处理他们的回复;
- 对Saga的测试除了要为正常执行的场景编写测试单元,还必须为Saga回滚的各种场景编写测试;
- 一种方法是编写使用真实数据库和消息代理以及桩服务的测试,以此来模拟各种Saga参与方;这种测试非常缓慢;
- 一种更有效的方法是编写模型与数据库和消息代理交互的类的测试;这样可以专注测试Saga的核心职责;
- 上述代码使用Eventuate Tram Saga测试框架编写,框架提供一个易于使用的DSL,其抽象出于Saga相互作用的细节;
- DSL可以创建一个Saga并验证其是否发出正确的消息;
- 事实上,Saga测试框架使用数据库和消息传递基础设施的模拟来配置Saga框架;
2.6 为领域服务编写单元测试
- 服务的大多数业务逻辑通过实体类、值对象和Saga实现;领域服务类实现业务逻辑的其余部分;
- 测试领域服务类使用独立型单元测试,它可以模拟储存库和消息传递类等依赖项;
- 每个测试按如下方法完成各自测试阶段:
- 设置:配置服务依赖项的模拟对象;
- 执行:调用服务方法;
- 验证:验证服务方法返回的值是否正确,以及是否已正确调用依赖项;
2.7 为控制器编写单元测试
- 服务类通常具有一个或多个控制器,用于处理来自其他服务和API Gateway的HTTP请求;
- 控制器类由一组请求处理程序方法组成,每个方法实现一个REST API端点;
- 使用某些框架,如Spring Mock Mvc编写的测试会产生模拟的HTTP请求,并对HTTP响应进行断言;
- 这些框架在测试HTTP请求路由以及Java对象与JSON之间的转换时,无须进行真正的网络调用;
- 上述代码是一个独立单元测试,利用模拟对象来解决OrderController的依赖项;
- 上述代码使用REST Assured Mock MVC编写,提供一个DSL,抽象出与控制器交互的细节;其可以轻松地模拟HTTP请求发送到控制器并验证响应;
2.8 为事件和消息处理程序编写单元测试
- 与控制器类似,消息适配器往往是调用领域服务的简单类,因此可以使用类似针对控制器进行单元测试的方法;
- 每个测试示例都是消息适配器,向消息通道发送消息,并验证是否正确调用了服务模拟;
- 使用Eventuate Tram Mock Messaging框架进行测试,框架提供了一个易于使用的DSL,用于编写模拟消息测试;
- 为了验证服务是否与正确地与其他服务交互,必须编写集成测试;还需要编写单独测试整个服务的组件测试;这点在下一章进行讨论;
3. 本章小结
- 自动化测试是快速、安全地交互软件的重要基石。更重要的是,由于微服务架构固有的复杂性,要从微服务架构中充分受益,必须实现自动化测试;
- 测试的目的是验证被测试系统(SUT)的行为。在这个定义中,系统是一个泛指,意味着被测试的软件元素。它可能像一个类一样小,也可能像整个应用程序一样大,或者是介于两者之间,例如一组类或一个单独的服务。测试套件是一组相关测试的集合;
- 简化和加快测试的一个好方法是使用测试替身。测试替身是一个模拟被测系统依赖项的行为的对象。有两种类型的测试替身:桩和模拟。桩是一个测试替身,它将值返回给被测系统。模拟也是一个测试替身,由测试用来验证被测系统是否正确调用依赖;
- 使用测试金字塔可确定将测试工作重点放在服务的哪个部分。大多数测试应该是快速、可靠且易于编写的单元测试。必须尽量减少端到端测试的数量,因为它们写入速度慢、脆弱且耗时;
最后
![](https://i-blog.csdnimg.cn/blog_migrate/4c125f9add4205c2c8b94d529690a7c5.jpeg)