测试策略
就像工厂的质检员一样,把机器生产的残次品筛选出来,留下合格的产品。你看,这机器生产的产品都会残次品,更何况我们写的代码,软件测试就是产品在使用者使用之前进行质检,尽量做到交付可靠的软件
自动化测试
由于手动测试的效率太低,且无法进行全天候的测试,所以我们使用自动化测试的方式。
自动化测试的四个阶段分别为设置环境、执行测试、验证测试结果以及清除测试环境,所以一般测试或有一个测试类进行初始化环境,在执行完所有的测试方法后删除测试环境和不必要的数据。
使用模拟和桩进行测试
被测系统在运行过程中时常会依赖另一些服务。依赖的麻烦在于它们可能把测试复杂化,并减慢测试速度。
解决方案是用测试替身来消除被测系统的依赖性。测试替身是一个对象,该对象负责模拟依赖项的行为。如下图
有两类测试替身:桩(stub)和模拟(mock)。术语桩和模拟的行为略有不同,但通常可以交换使用。
桩:是一个替身测试,用来代替依赖项来向被测系统发送调用的返回值。
模拟:也是一个替身测试,用来验证被测系统是否正确调用依赖项。
测试的不同类型
- 单元测试:测试服务中最小的部分,比如函数、类
- 集成测试:验证服务是否可以和基础服务(数据库)或其他的应用服务
- 组件测试:单个服务的测试
- 端对端测试:整个系统客户端、服务端之间的测试
在开发过程中,对一些重要的函数、类进行单元测试,然后进行集成测试,紧接着是组建测试和端对端测试。
测试它还有两种分类和四个象限。
两个分类:- 面向业务
- 面向技术
4个象限: - Q1协助开发/面向技术:单元和集成测试
- Q2协助开发/面向业务:组建和端对端测试
- Q3寻找产品缺陷/面向业务:易用性和探索性
- Q4寻找产品缺陷/面向技术:非功能性验收测试,性能测试等
4个象限的关系如下图:
4个测试所占比例
根据下图测试金字塔可以看出,单元测试因为它的快速、可靠、低成本占据测试比例的最大位置,其它的依次递减,最后到端对端的测试,由于它的缓慢、脆弱、高成本导致它所占测试比例最少
通信测试
微服务中的通信测试非常重要,由于微服务是分布式架构,其中一个服务更新后必须保证,和他交互的其他服务能够正常的通信,所以编写通信测试就显得异常重要。
通信分类
通信方式分为两大类
1. 同步协议实现通信交互,如REST或gRPC
2. 异步协议实现通信交互,如消息订阅,异步接口
通信测试方法
以消费者驱动的契约测试
服务的双方分别为消费者和提供者,就如同客户端和服务端,消费者相当于客户端而服务者相当于服务端。消费者契约测试着重于验证提供者API的参数定义和输出是否符合消费者的期望,具体如下:
1. 具有期望的HTTP方法和路径
2. 如果需要HTTP的头部,则要能够接受得到
3. 接受请求主体(如果有的话)
4. 返回预期中的响应,包括状态代码、头部和主题
契约测试不需要完全测试提供者的业务逻辑这是单元测试的任务
消费者服务需要编写契约测试套件,如果测试不通过,需要及时反馈给提供者服务,提供者服务团队必须修复这些API,或者和消费者服务团队讨论。
同样既然是契约,消费者服务也要遵守契约,按照契约使用提供者服务的API
部署流水线
每一个服务都有一条部署流水线,就比如说常见的持续交付DevOps。就如下图带有测试的部署流水线,理想状态下他是全自动的,但也可能包含手动步骤,持续集成服务可以用jenkins实现
其中部署流水线中的阶段:
- 提交前的测试:执行单元测试。这是开发人员在提交更改代码之前执行
- 提交测试阶段:编译服务,执行单元测试,并执行静态代码分析
- 集成测试阶段:执行集成测试
- 组建测试阶段:执行服务组建测试
- 部署阶段:将服务部署到生产环境
为服务编写单元测试
根据上面的描述,单元测试是测试金字塔中最低的级别,是面向技术的测试,目的是协助开发。单元测试是验证单元是否正常运行,比如在python中单元有时会是一个函数或一个类,因此单元测试的目的在于测试类或函数的行为是否符合预期。
单元测试有两种类型:
- 独立型单元测试:使用针对类的依赖性的模拟对象隔离测试类
- 协作型单元测试:测试一个类及其依赖项
简而言之独立型就是模拟需要依赖的类进行测试,而协作型使用的是真实类进行测试。类的职责决定是使用独立型单元测试还是协作型单元测试,例子如下图。从下图也可以看出需要做单元测试有值对象、实体、控制器、领域服务(saga)、时间和消息处理
编写集成测试
集成测试位于单元测试之上,它主要验证服务是否可以与依赖项进行通信,其中包括基础设施服务(如数据库)和应用程序服务。
服务通常与其他服务交互,就比如Order Service与多个服务交互,它的REST API由API Gateway调用,他的领域时间被其他服务使用,同样它也依赖其他的服务,它把Order持久化到MySQL数据库中。它还向其他几个服务发送命令式消息并使用来自它们的回复。如下图的集成服务测试就是***验证服务是否可以与其客户端和依赖项进行通信。***
测试Order Service等服务是否按预期运行,所以我们必须编写测试来验证服务是否以正常的与基础设施服务和其它应用程序服务进行交互。
测试方法可以使用端对端测试,但是根据测试金字塔端对端测试具有缓慢、高成本的特点,所以我们尽量减少端对端的测试。而端对端正好符合这些条件,针对它的测试有两种测试方案。
- 测试不针对整个服务,而是测试实现通信的各个适配器类。针对每一个适配类进行编写集成测试代码,验证是否按照标准运行。
- 使用契约测试的方式。根据上面的契约测试中介绍,契约的结构取决于服务之间的交互类型。具体类型如下图
一个契约会包含一个或者两个消息,例如在发布/订阅方式中有一个消息,在异步请求/相应中有两个消息。
契约用于测试消费者和提供者,确保它们的API达成一致,它们的使用方式略有不同,具体取决于你是在测试消费者还是提供者 - 消费者端测试:这些是用于消费者适配器的测试。它们使用契约来配置桩,以此来模拟提供者程序的行为,使你能够直接运行测试,而不需要运行消费者对应的提供者程序。
- 提供者端测试:这些是用于提供者适配器的测试。它们使用契约来测试适配器,使用模拟来满足适配器的依赖关系。
针对持久层的集成测试
服务通常把数据存入数据库中。我们之前的单元测试只是测试内存中的对象。为了确保服务正常工作,我们必须编写持久化集成测试,已验证服务的数据库访问逻辑是否按照预期工作。
持久化集成测试每个阶段的行为如下:
- 设置:通过创建数据库结构设置数据库,并将其初始化为已知状态。也可能开始执行一些必要的数据库事物
- 执行:执行数据库操作
- 验证:对数据库的状态和从数据库中检索的对象进行断言
- 拆解:可选阶段,可以撤销对数据库所做的更改,例如,回滚在设置阶段提交的事务
组件测试
现在我们想验证Order Service是否按预期工作,我们希望编写服务的验收测试,将其视为黑盒并调用api验证服务的行为。这时我们就要用到组件测试。组件测试单独测试服务。它通常使用桩来代替服务的依赖性,他甚至可能使用内存版本的基础设施服务,例如数据库。
端到端测试
端对端测试由大量组件构成。必须部署多个服务及支撑它基础设备服务。因此,端到端测试很慢。此外,如果你的测试需要部署大量服务,则很可能其中一个服务无法部署,从而使测试不可靠。因此,你应该尽量控制端到端测试的数量。