内容来源:2018 年 5 月 20 日,eBay中国研发中心技术主管茹炳晟在“2018全球技术周暨第四届南京(全球)软件大会”进行《Quality Engineering向Engineering Productivity转型下的测试基础架构实践》演讲分享。IT 大咖说(微信id:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。
阅读字数:3994 | 10分钟阅读
Google 的GTAC大会从测试技术上来讲可以说是国际上最权威的技术峰会,从2006年开始举办一直持续到2016年,不过2017年的会议并没有举办。Google对此的说法是当时举办GTAC的初衷是为了提高测试效率,更好的做自动化测试,但是在2016年之后他们发现单靠自动化测试已经不能解决软件工程所面临的众多问题,为此他们提出了一个新的概念Engineering Productivity(工程效能),可以简单将它理解成是为了更好的做自动化测试以及测试产出而衍生出的工程效能上的工具平台或方法论。
基于Google在全球的影响力,这一概念在被提出后,国际上的很多大公司也开始了着手探究。
在Engineering Productivity模式下是没有专职的测试人员的,开发工程师需要自己来做测试。原先的开发流程中,测试和开发是分来的,所以经常会出现由于双方对同一事物的不同认知而产生的纠纷,造成工作效能的低下,而如果开发人员能自行做相应的测试无疑会提高效率。
在原先的传统软件团队中产品测试类似于图中左边结构,自下而上依次是unit test、API test、GUI test。在转型之后unit test减少,API test增多,GUI test依旧,上方多了一层Exploratory test,这一层是真正的基于测试的理念和探索式方法来尽可能多的发现软件系统潜在的缺陷。
从人员的角度来看,无论是转型之前还是之后unit test都是开发来做,但是API test和GUI test在转型之后从测试转向了开发,原来团队的业务测试人员现在专注于Exploratory test。
不管是API test还是GUI test在跑一个case之前都需要准备测试数据,这一阶段一般会耗费很多时间,粗略的估计会占用整个测试的30%-35%的时间。对此一般的做法是采用固定的数据来进行测试,以节省时间,但是这种做法要面临脏数据的问题,有可能数据在使用过一次后无法再继续使用,比如订单数据。测试数据本身也具备一定复杂性和多样性,用户数据的创建就有着各种组合和限制。
微服务化之后Cross domain的数据准备缺乏knowledge,以前的单体架构下,如果需要某些信息可以直接向特定团队要求相应数据,但是微服务化之后,整个团队都被拆分成了各个小的服务,没有了具体的数据提供方。性能测试的数据准备同样要消耗大量时间,因为数据量基本上在百万级到千万级。
针对以上这些问题,我们从工程效能的角度给出了相对完美的解决方案。不过这个解决方案也不是一蹴而就的,而是经历了好几个不同阶段的发展演化而来的。
1.0时代要准备User数据最简便的方式是创建一个函数createUser,这个函数会去调用产品的API来生成user,它的参数列表中包含所有需要的参数信息。这种方式看似不错,但是在真正的工程实践中依然存在问题。其实可以发现在createUser函数的参数列表中有些参数本身就是一个结构,也就是说在定义createUser之前可能还需要定义与参数类型对应的create类。这样一来脚本中测试数据准备代码会占很大一部分并且效率很低。
2.0时代解决了以上的问题,它是基于Builder Pattern的实现。如果build的时候未提供任何的信息就会生成一个全部采用默认值的user,也可以只指定部分信息而其余部分依然采用默认值。
2.0时代虽然已经相对完善,不过数据是作为工具提供给所有的开发者使用,不同的开发团队技术栈都不相同,前面的做法是通过Java实现的 ,对于后端来说当然没有问题,但前端团队就无法使用了。因此我们在3.0时代将这些基于Java的数据工具都封装成了统一的Web Service,以Restful的形式对外提供服务,这样前端、后端以及任何支持Restful接口的工具都可调用它。这时候的3.0还只能算是雏形,在此之上还会继续演进。
之前的架构中对于所有创建数据请求都会重新执行创建数据的操作,3.0演进之后系统对于已经创建的过的数据会保存下来,在下次有同样请求的时候会直接返回数据,这就缩短了数据返回的时间。同时对于一次性返回的数据在返回过一次之后就不会再返回,以避免脏数据。
对使用者来说,所期望的是测试执行环境的“透明性”。比如开发人员在跑UI测试的时候,只需要指定特定的操作系统和浏览器版本,不必关心具体的运行环境或机器,后台会自动找到目标环境。
对维护者来说,期望的是“易维护”。因为整个测试环境是一个集群,大的公司中集群的规模会达到上千台,构成一个小的私有云。面对这一套架构如何更好的维护就成了一个问题。
对于大量测试用例的执行而言,重要的是执行能力的可扩展性。虽然在短时间内跑完大量测试case的一个核心解决方案是应用大量机器并发运行,但是当没有测试执行的空闲时间,就会造成大量机器的闲置。因此动态的根据测试用例的排队数量来决定集群节点是非常必要的。
基于jenkins触发测试执行可能是大多数的测试人员使用的方案,但是在case数量非常庞大的情况下,基于jenkins的来管理管理这些test suite的效率往往并不高。为此我们在jenkins前面做了一个Test Execution System,它相当于一个测试用例管理系统,为jenkins脚本的管理提供了良好的界面,让开发能够直接通过界面操作建立jenkins脚本、触发对应测试、管理测试版本。
后续的版本中我们用Selenium Grid代替了原来的虚拟机,所有的测试节点发给Selenium hub之后,hub就会自动找到相应的node进行测试。此时的jenkins还是单节点的构造,因此当同时运行的测试用例数量非常大的时候,实际的工程中会有大量的工程堵塞在jenkins上。为此我们将jenkins集群化了,同时为保证下游能承受住压力,又用Docker实现了Selenium Grid的动态扩展与收缩。
传统的API测试方案最大的问题在于无法做持续集成,因为常见的postman、soapui都是界面化的工具。这种情况下就需要将API 测试代码化,在此之后还要剥离测试脚本和测试数据,也就是Data-Driven Test。进一步还可以动态的生成测试数据,比如测试API的时候动态分析API的参数类型自动生成一些边缘case,这样不仅提高了效率也方便开发发现一些边界值的问题。
一般的功能测试不是单个API测试,而是并发的测试,因此需要有并发执行控制器,同时在进行全链路压测的时候,还要有Load Generator Cluster。
微服务架构下API测试的最大挑战在于测试用例的数量非常多,面对大量的API无法保证测试的质量。为此我们提出了基于消费者契约的API测试方式,将原先测试数量降低到了原先20%不到,同时还能保证质量,下面是具体实现思路。
假设有A、B、T三个微服务,T是不对外部暴露的被测微服务,A、B是T的消费者。 传统的方案中会对T暴露出的所有接口的可能组合都测试一遍,然后验证是否达标,不达标就再补各种case来覆盖未测试的代码。而在当前的场景下其实只需要测试A和B所调用的接口组合,不用关心其他接口的组合,这样一来就能很大程度的降低测试数量。
接下的问题就是如何找到A、B调用的接口组合。通常的逻辑方案是在被测服务T前面添加一层代理,所有进出T的请求都会经过代理,然后就能获取到进出流量中的request和response,将他们总结起来之后就有了一个针对T的测试contract。
这种方式同时解决了API依赖耦合的问题,如果在之前的架构中T还调用了X和Y且他们之间相互耦合,这种情况下为了能够测试T,可以基于X和Y的contract来启动Mock Service,这时测试T就不会再调用真实的X和Y,而是调用Mock Service X和Mock Service Y。