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