aSIT:面向接口的分布式自动化测试系统*
2020-05-07
1 引言
随着互联网技术、持续交付(Continuous Delivery)[1]的发展,分布式系统和应用已成为当前软件开发的主要方向之一。与集中式的串行程序相比,现代互联网服务通常都是复杂的大规模分布式系统,这些系统由多个软件模块构成。由于分布式系统软件的模块可能由不同的团队、使用不同的编程语言开发,系统测试需要多团队协作,将企业内多种异构服务集成,给分布式系统测试工具带来了新的挑战。
在微服务架构[2]下,系统进一步细化为面向特定服务的子系统,结构图如图1所示。一个系统功能往往需要组合调用多个接口完成,既要测试每个接口的正确性,也要从功能角度编排所依赖的接口进行验证,系统复杂度的增长,使得测试用例数量相应增加。
图1 微服务架构
另一方面,企业为了在市场竞争中赢得优势,要求更短的需求交付周期,及早推出新功能,而测试是检验系统是否满足需求及上线标准的重要环节。Google、Facebook等公司依托其强大的自动化测试系统,能实现一天数次的发布[3]。
目前已有的自动化测试工具如JUnit[4]、Postman、Selenium[5]、Appium[6]等,都无法很好支持企业内部异构服务集成、多团队协作、海量测试用例执行的场景。本文提出的aSIT是一套面向接口的分布式自动化测试系统,能较为有效地解决企业自动化接口测试面临的问题。
2 背景知识和相关介绍
2.1 持续交付与自动化测试
持续交付是一种软件工程方法,让软件产品的产出过程在一个短周期内完成,以保证软件可以稳定、持续的保持在随时可以释出的状况。它的目标在于让软件的建立、测试与部署变得更快以及更频繁,同时还能保持高质量。
持续交付的核心措施是,代码集成到主干、发布之前,必须通过自动化测试。自动化测试不通过,则不能进行交付。
持续交付对测试过程提出了更高要求:测试用例低成本、可重复、频繁执行,并且在不同测试环境下稳定运行。
2.2 已有的工具及其不足
以JUnit、TestNg为代表的开源单元测试[7]框架,为开发人员编写和运行可重复的单元测试用例提供支持。但用户需要使用跟被测系统相同的开发语言,编写白盒测试代码,要求用户具备一定的编程能力。单元测试的粒度要求尽可能小,一般在函数级别,用于验证函数本身的逻辑正确性,对外部系统的依赖采用Mock[8]来模拟。
Postman是一款用于网页调试与发送HTTP请求的工具,可用于编写、管理、执行HTTP接口的测试用例。但其只支持HTTP协议接口,而分布式系统间通讯广泛使用的Dubbo、gRPC、thrift[9]等RPC协议尚未支持。
Selenium、Appium为代表的GUI界面测试工具,可以让录制用户在界面的操作,并进行重放。由于是在界面上进行操作,使用相对简单。但要求系统必须有用户界面,不适合中台、基础子系统的测试。同时用户界面往往频繁,做好的用例可能在下个版本就不适用,导致维护自动化用例的成本较高。
上述3类工具分别代表分层测试中的单元测试、接口测试、GUI测试。在Google等头部IT企业,往往没有专职的测试人员[10],软件质量由开发人员保证。开发人员编写大量的单元测试用例,追求代码级的覆盖率。因此形成了“金字塔模型”,即单元测试占绝大部分、接口测试略少、GUI测试最少。
但国内大部分的IT企业,特别是传统行业的IT部门,开发人员、测试人员间分工明确,往往还分属不同的团队。测试人员大多数不具备使用开发语言编写高质量单元测试用例的能力。因此,推行上述金字塔模型很难落地。
考虑到GUI测试的局限性,从接口测试切入自动化测试是较为可行的办法。主要原因是:① 接口测试投入产出比较高,可以实现较高的自动化率;② 可以帮助加强开发与测试人员之间的协作,提高测试质量。接口测试需要开发跟测试人员共同定义,因为开发知道内部实现的细节,测试掌握业务场景。将测试的重心放在接口测试上,并且提倡高度自动化,单元测试、GUI相对较少,形成“橄榄球模型”如图2所示。
图2 分层自动化:金字塔模型与橄榄球模型
此外,一个业务流程的实现往往涉及不同系统,需要不同团队合作,要求测试系统支持多团队协同。
3 aSIT功能和架构
企业内部异构服务的示意如图3所示。系统A使用Java开发,系统B使用Go语言开发。A对外暴露HTTP接口,A通过gRPC调用B。A、B由不同团队负责测试。B有两套环境,网络隔离。
图3 企业内部异构服务
上述例子要求测试系统需支持:
① 调用不同协议的接口
② 多人协作,数据互不影响
③ 一套测试系统在多套环境执行用例
3.1 aSIT的功能与特点
基于上述考虑,我们实现了一个接口自动化测试系统aSIT,aSIT的主要功能如下。
① 在图形化界面录入、管理用例,定义数据初始化和断言操作;
②对HTTP协议接口,支持从Swagger导入接口定义;
③ 对RPC协议接口,支持通过接口包、IDL定义文件导入接口定义;
④ 将用例编排为用例集,用于全量回归、新需求验收等不同场景;
⑤ 不同用户操作同一用例时,用例数据实现隔离,避免相互影响;
⑥ 系统本身基于分布式设计,可横向伸缩,支撑不同用例数量执行;
⑦ 按优先级执行用例,资源受限时保障高优先级用例先执行;
⑧ 支持通过信号触发或定时触发用例执行;
⑨ 一套系统支持企业内部多套环境用例执行。
3.2 aSIT系统架构设计
aSIT的系统结构如图4所示,主要分为用例管理与用例执行两大部分。
图4 aSIT系统架构
(1)用例管理(Test Case Management):供用户编写、维护用例,查看用例执行结果,隔离用户数据。
(2)用例执行部分:用户执行用例时,Execution Dispatcher负责根据用例数据和待执行环境信息,组装为执行期数据,附上执行优先级,发送到消息队列Priority Message Queue;Execution Agent从消息队列获取用例执行任务,按运行期数据调用用例中的接口,将返回结果加入运行期数据,并发送到Priority Message Queue;Assertion模块从Priority Message Queue获取用例接口调用结果,并与断言进行比对。
4 aSIT系统实现
4.1 用例管理
4.1.1 用例定义
首先将用例定义为以下七元组:
其中,Protocal为接口所用的协议;Env为接口环境信息;Init为数据初始化步骤,可为接口用例准备数据;Identifier为接口名;Input为接口输入参数值,可引用Init中获取的数据;Assertion为断言信息。
基于用例我们定义用例集:
Ordered::=True | False
用例集是一组用例的集合,可引用多个用例或其他用例集,并指定其中的用例是否串行执行。Ordered默认值为False,适用于用例间无相互依赖的情况。对于用例间存在依赖的场景,如“下订单”用例执行前需要先执行“登录”用例,则将Ordered设置为True。
在用例执行阶段,仅Ordered为True的用例集中,用例需串行执行,其他用例都是并发执行性,降低用例执行耗时。
4.1.2 多团队协同使用
对于多人同时编辑同一用例的场景,我们借鉴了Git代码分支管理的思路,将用例分为主干用例与分支用例进行管理。主干用例是经验证可重复稳定运行的用例,可供他人使用,往往用于回归测试。分支用例是未经审核的用例,用于个人调试。分支用例可申请提升为主干用例。
如图5所示,测试人员A希望修改主干用例1以覆盖新需求的改动,先创建用例1的一个副本(用例1a),并在用例1a上进行修改。测试人员A将用例共享给开发人员B,让其检查测试用例。检查无误后,测试人员A申请将用例1a合入主干。测试人员C希望基于主干用例1创建新的用例,于是创建用例1的一个副本(用例1b),修改其中的输入参数,提交申请成为主干用例2。在各自修改其分支用例期间,主干用例不会受到影响,仍正常用于回归测试。
图5 团队协作与用例分支
4.2 用例执行
用例执行部分,我们利用消息队列(Priority Message Queue)将执行数据准备(Execution Dispatcher)和具体的执行操作(Execution Agent)解耦。Priority Message Queue将执行任务按优先级排序,Execution Agent会先取到优先级高的任务进行处理。
单个用例执行时,会默认生成一个仅包含自身的虚拟用例集。用户在界面发起执行的用例集,其默认优先级为AboveNormal。从后台发起执行的用例集(集成流水线触发、定时触发等)默认优先级为Normal。这样的默认设置是考虑到:用户在界面发起执行用例时,往往是单个调试,期望尽快获得结果反馈;而后台触发的大批量用例执行通常是版本发布前跑的回归用例,对结果返回的时效要求相对较低。当然,用户可以在界面调整用例集的执行优先级,满足其需要。
多个Execution Agent通过Multicast相互通讯,基于Bully算法[11]组成集群。由于Execution Agent集群并不需要强一致性,因此没有使用更复杂的Paxos算法[12]。
算法1 Execution Agent集群Leader选举流程
Leader负责从Priority Message Queue获取待执行的用例,并分配给自己和Follower执行用例。Execution Agent集群是可横向伸缩的,当待执行的用例数量巨大,已有集群执行用例的速度无法满足要求时,可增加Execution Agent部署数量,新增的Agent将自动加入集群,处理测试用例。
如图6所示,当aSIT需要执行网络隔离环境下执行测试用例时,由于环境仅在实际调用接口时有影响,因此,只需要在每套环境部署Execution Agent负责该环境的用例执行即可。Execution Agent Leader开通到Priority Message Queue的网络访问策略,即可获取到待执行的用例数据。
图6 网络隔离环境部署图(执行部分)
5 aSIT应用实践
5.1 效能提升数据
aSIT推出市场超过两年,目前使用的客户涉及通信、金融、地产、新零售等多个行业。本节我们回顾某金融企业引入aSIT后的测试效能提升情况。该企业互联网金融项目组,开发、测试人员比例为4:1,每个版本迭代周期为1个月。引入aSIT前,测试团队一直采用手工测试,执行测试用例耗费大量时间,每个迭代的测试周期约半个月。
表1是引入aSIT后,跟踪10个迭代,测试团队工作内容分配情况。在最初3个迭代,部分功能测试、回归测试用例仍依靠手工执行,测试人员大部分时间投入在用例执行,设计自动化用例、将回归测试用例改造成自动化用例的投入逐步增加。从迭代5开始,项目组加大了存量手工测试用例改造为自动化测试用例的投入。最终绝大部分测试用例可自动化执行,仅剩少部分必须人工执行的用例,测试人员主要精力投入到测试用例的设计与优化中。
表1 测试团队工作内容分配
引入aSIT前,该回归测试均由人工执行,用例数24个,仅有限覆盖主干流程。随着在aSIT上维护自动化用例,越来越多的高质量用例成为主干用例,并加入到回归测试集,回归测试覆盖更多的业务场景。如图7所示,迭代0代表使用aSIT前,回归测试数目为24个;到第10个迭代,回归测试用例数已达744个。
图7 回归测试用例数
aSIT大大降低了用例执行耗时,缺陷验证时间由原先接近1天,降低到5分钟以内。
需求交付耗时,是指从需求提出到需求发布的时间。我们按每3个月滚动统计平均需求耗时,平均需求交付耗时从约87天下降到32天,如表2所示。
表2 需求平均交付耗时
5.2 最佳实践总结
在使用aSIT的过程中,我们认识到,aSIT作为一款接口自动化测试工具,其应用效果还与如何使用此工具密切相关。我们从客户落地的实际经验中总结出如下最佳实践。
① 在集成流水线中自动化调用aSIT执行自动化测试
除了在aSIT界面手工发起或配置定时任务定时触发测试执行外,建议在集成流水线中调用aSIT提供的用例执行接口,确保每次集成时测试用例100%通过,否则中止集成流程。
② 定期审查测试用例,确保用例健壮性
自动化测试的有效性是需要通过维护去保持的,定期重新审查本是一种测试用例的必要维护手段。审查应关注测试数据的初始化和使用是否合理,是否会破坏测试环境中数据的健康度甚至带来环境故障,验证点是否足够精确等。
③ RPC接口包不要包含具体实现逻辑
从软件工程角度看,提供给调用方的接口包应只定义接口,不能包含接口的具体实现逻辑。有的开发人员工程意识不强,图方便把接口及其实现一同编译到接口包中,导致在aSIT中通过解析接口包导入接口定义时出现问题,如接口包体积比仅包含接口定义大很多(曾碰到用户上传1个约100 MB的jar文件),导致解析时间长,触发了aSIT的超时保护机制;又或者实现逻辑有依赖其他程序包,解析时缺少依赖信息。
④ 开发、测试一同完善测试用例
开发定义出接口后,测试人员可以随即介入,在aSIT上创建测试用例,并共享给开发人员。开发人员在调试程序时,就可以持续补充用例,基于测试结果完善程序。在此过程中,测试人员也能优化用例。这样实际上自然实现了测试前置。
6 总结与展望
我们设计并实现了接口自动化测试工具aSIT,并在本文中汇报了我们应用aSIT的效果,总结最佳实践。微服务、分布式架构为系统测试带来挑战,需要自动化测试才能支持需求快速交付。本文提出,从接口测试切入自动化测试是适合我国企业现状的。aSIT设计时参考现有的自动化测试理念,考虑如何解决企业面临的多团队协作、接口协议不统一、测试环境隔离等问题。在实际使用中,能够解决上述问题,并提升企业的测试效能。
通过自动化测试提升企业的研发效能是DevOps[13]的重要部分,后续工作将会研究如何根据生产环境接口调用自动生成测试用例、如何度量接口测试覆盖面、更科学评价自动化测试对效能提升等。