支付宝用例自生成技术实践

讲师简介:
乾雨,蚂蚁集团高级开发工程师。2018年加入蚂蚁金服,现一直从事工程效能,智能化测试相关的工作。2018之前一直从事电商相关工作,参与过日均过亿的网关开发工作。

热爱技术,做过开源监控工具Onyxia,为公司产品打下稳定性的基础,面对公司db中间件针对java语言的漏洞,重写过对应的事务管理器,推动部门内java编写的产品线能稳定上线。

本次分享分以下四个部分:
一、背景介绍
二、当前业内方案
三、支付宝的用例自生成实践
四、总结和价值
戳我观看视频

一、背景介绍

支付宝用例自生成技术实践

如图所示,假如已经有一个作品在线上,当月提出一个需求,按照正常流程进行,开发完测试,验收产品,产品如期上线。

但是在这个测试中大部分同学都比较侧重于验收新功能,对于之前已有的功能很少验证或者不去验证,下意识认为两个功能点之间没有关联,不会出事情,但开发人员为了本次新功能开发需求,可能会改变业务代码当中某些公共的方法,这些公共方法可能会服务已有的功能点,缺少测试验证,上线后可能会导致已有的功能异常,引发线上故障。
支付宝用例自生成技术实践

如图所示,即将有一个很复杂的功能要上线,要实现这个功能,代码中有很多if...else或者switch...case的语句,并且排序计划需要这个功能上线,那么在时间有限的情况下,大家都会优先去保证核心链路的测试,其他链路的测试可能会简单或者不去测试,甚至在测试过程中不会去考虑异常场景,比如说PPT中展示的例子,当type为空时,会有什么影响,这些基本不会做验证,上线之后未做详细验证的分支,很有可能有bug,引发线上故障。
支付宝用例自生成技术实践

测试流程,在该流程中,查bug会占比较长的时间,无论自测,还是交给测试人员做,都会耗费长时间。回顾上述场景一、场景二,出现的问题都需要长时间测试才能避免。
支付宝用例自生成技术实践

测试包括四种测试方法:单元测试、集成测试、系统测试、全链路测试。

测试用例:指覆盖更多分支的测试用例。比如说我有10个接口,每个接口有10个分支逻辑,那么我需要用100个用例来覆盖所有的业务范围,如果能有100个用例,上线之前全部跑一遍,无论结果如何,起码业务分支都已经运行过,一些常见异常都能暴露出来,剩下要做的就是开发测试同学去查看跑这100个用例的过程当中,是不是符合预期,有没有业务逻辑的错误,如果我们能有这种大量的用例集合,那么就会大大的减少测试成本,毕竟运行用例的时间是有限的,比查找bug的时间减少很多,这样就会将之前业务上遇到的问题转化为用例生成的问题。

一般来看,用例的来源主要有两个方面:

①开发测试同学自己构造。这种方式依赖于编写用例人员的多少,以及人员水平的高低,比如之前举的例子,由于之前各种原因:项目周期短,人员少,只会覆盖有限的人员逻辑,很难去覆盖所有的人员分支,甚至不会去管异常的用例;

②线上录制。通过一些技术手段将线上发的技术参数录制下来,当做测试用例,这种方式无法解决测新的问题。

比如我新写了一个接口,或者将原来接口的参数做了改变,线上没办法录制到最新接口的参数的请求,所以没有办法使用。

并且线上一般都是正常请求,很难去录制到异常的参数,有些异常参数只会在特定情景下复现,不能录制到异常用例,并且这种方式录制量很大,有可能录制到100万条用例,由于不知道这些用例对应哪些分支逻辑,所以会全部跑一遍,测试的时间很长,如果测试过程中异常较多,则还会做数据分析工作,比如100万条用例,其中5万条是失败的,那么就需要用户去查看失败的用例是什么原因导致的,如果逐条去查看,是不太可行的,中间就会需要聚类的方式,有一定排查和解决问题的成本。还有一些其他问题,比如说我们现在已经有一些用例集合,这时候还需要用户去维护这些用例,当接口迭代更新的时候,需要把对应的用例更新,让他跑用例的时候不至于随着时间的推移测试质量下降,这个也需要一定的成本。

针对上述提到的相关问题,我们要考虑如何将生成用例做到智能化和自动化,若用例覆盖度不够,则自动生成需要的用例集合;若用例数目太多,则根据需要去缩减用例数量;若迭代有更新,那么能动态感知生成新的用例集合。

上述就是目前我们需要解决的问题。

智能化测试
支付宝用例自生成技术实践

现如今智能化测试是一种大趋势,现阶段开发过程中,bug的查找、定位占用了大量时间、人力成本,很多人意识到这个问题,慢慢的向自动化发展,能自动查找问题,并且在自动化基础上,加一些大数据和算法的支持,做到智能化的成产用例。

如今业内也有很多的方式方法去推进智能化的落地,比如学术界有一些论文,像《基于符号执行的方式》、《基于模糊测试的方式》、《基于模型的方式》等去生成用例,每种论文的方式或多或少都会有一些开源的项目去支撑这些论文的思想;工业界也有一些很有价值的工业产品,比如Randoop、Evosuite、AFl等,通过这些各式各样的方法和工具,走出了多少人工才能查找定位走出bug死胡同。智能化的用例生成,将会减少很多查bug的时间,研发效率将会有很大的提升。

二、目前业内方案

Randoop、Evosuite
支付宝用例自生成技术实践

Randoop和Evosuite都是比较好的开源工具,都能够自动生成单元测试。

比如说有一个link list对象,现在想测试里面用到的方法,通过运行各自支撑、支持的命令,可自动生成类似于PPT展示的单侧用例,达到用例自生成的目的。生成的单侧new了一个link list的对象,里面设置了一个字符串,之后调用-isEmpty的方法去做测试。

观察源代码,发现有些数据是这样实现的:通过随机值生成一些随机的数据放到里面,或者说根据类型去设置。Evosuite里面包含了GA的思想,它会根据覆盖率去进化生成的参数。

Randoop和Evosuite主要面向单侧,而我们的业务方向可能面向接口测试,比如说我是一个接口测试平台,调用各个业务方的接口,那么就不会去生成单侧,因为没办法去了解业务的实现类是什么,所以暂时不采用此方式。
AFL
支付宝用例自生成技术实践

AFL是谷歌的一个工程师开发师,以代码运行的路径为指导,构造大量输入参数,对软件进行大量测试,进而发现问题的一种模糊测试方法。

与之前Randoop和Evosuite不同的是,它不会生成单侧,比如像PPT中展示的那样,用户首先要准备参数的种子文件,第一次可能不需要编译,只需要将种子文件放在接口当中去做测试,测试过程当中对业务代码进行插装,计入本次代码的执行路径,如果跑到了新的路径,那么编译的这个种子文件将编译后的种子文件再输入到应用程序当中,针对同一个接口继续做测试。

AFL经过一些开源项目验证,这种方法是可行的,但也有一些问题,这个相对来说,是比较适合C和C++的应用。如果要做AFL测试,必须要做一些种子文件,种子文件的质量和大小都有一定的限制。

接下来我们来看一些目前比较常用的用例生成方式。

第一种:组合测试
支付宝用例自生成技术实践

组合测试:之前已经有一些用例集合,这些用例按照字段属性单独来看,id的范围只有三种,name的范围只有三种,age的范围只有两种,那么可以做两两组合来基于已有的参数编译生成新的参数,如上图所示。

第二种:交叉测试
支付宝用例自生成技术实践

交叉测试与组合测试有很多相似地方,有如图所示的一些集合,它针对某个参数有多个请求,那么我们把这个参数拆开来看,我们将第一个用例的用户对象和第二个用例的性别对象组合作交叉,再将第二个用例的用户对象和第一个用例的性别对象再组合做交叉,这样就会得到变异的一些参数,将这些参数再拿去进行测试,用来找到业务当中更多的问题。

三、支付宝的用例自生成实践

支付宝用例的使用方式
支付宝用例自生成技术实践

如图:支付宝使用的方案有2种使用模式,生产模式和执行模式。

生产模式,顾名思义,就是遗传算法,不断变异参数,生成新的参数集合做测试。

为了要生成符合语义的变异参数,我们先做了一层代码的解析,感知到参数的组成结构,即就是说,参数是一个user对象,参数里面有个id,还有name,要想知道它的结构和类型,从而生成参数的数据规约配置,解析出数据规约配置之后,能保证我们生成的参数都是符合语义的,之后到基因生成器模块,这一部分是做数据的变异基础,参数有多种多样,有int、set,也有各种对象,有user,那么我们如何做到公平的变异,就需要把各种各样的参数全部转化成同一个维度的数据,做一次归一化处理,归一化处理的结果我们就把它称为参数的基因,本质上是一串数字,每一串基因序列都能够唯一的对应一个参数值,当我们基因库里面没有当前被测参数有效基因的时候,会自动生成一串随机的基因序列,这样就能保证说需要种子文件,我也能够自动的去生成一个参数值。

如果我们基因库里面已经有一个有效基因,那么就会对有效基因做变异,最终会生成一串基因序列。

这个基因系列是通过我们的参数解码器,转化为对应的用例参数原数据,这个源数据会服务一些测试平台,因为有些测试平台没有被测接口请求测试的class,就是不知道它是否为一个user对象,所以我把这个源数据给到测试平台,测试平台能拿到我们的源数据,直接发起http或者rpc的请求,假如说我们的用户可以提供class。

我们可以把源数据自动转化为实际的用例,比如user对象,用户拿着user对象去做测试,执行一次测试之后,会做一次适应度的计算,通过覆盖率为指导,指引下一个变异参数去覆盖更多的代码行,而具体的参数变异,我们支持多种的变异算法,比如之前提到的交叉和组合,以及根据参数基因做编译都做了支持。当然,如果用户有有效的参数数据,也可以提供给我们,通过我们的参数编码器转成基因序列,保存在基因库里面。

执行模式相对来说比较简单,它会从之前基因库里面拿到沉淀的用例集合,重跑一次测试流程,用来验证服务的稳定性。

自生成用例常见使用场景
我们现在支持从本地、IDE、测试平台、全链路测试、定时任务这几种场景去跑用例流程的成产流程,主要是为了沉淀数据。

模糊测试很难说在非常短的时间内把大量的有效用例给生成出来,我们的用例自生成技术其实也相当于模糊测试的一种形态,有些应用可以配置个定时任务,比如每天凌晨2、3点左右,大家都在休息的时候,我们把服务拉起来跑一个用例生成功能,比如说我一个接口有10个分支逻辑,第一天只生成出面向三个分支的基因,没有达到要求,第二天继续跑,之后在白天的时候来看一下结果即可,生成和测试都做到了自动化。

用例集也是会自动更新的,假如说今天生成了10个有效用例,然后代码迭到了新的需求,代码里面删除了某些分支逻辑,那么在下一次跑用例生成功能的时候,会自动的将这些删除的业务分支所对应的用例集给抛弃掉,保持用例的有效性。

沉淀下来的用例集之后,我们可以在持续集成某个阶段去使用他们,比如用户开发完功能,普适到当前的迭代,做了静态检测,做了代码MR的合并,之后就可以用我们沉淀下来的用例重跑一遍,来验证这次MR对之前的流程是否有影响,如果没有影响,则可以合并至master,推到上限。
支付宝用例自生成技术实践

变异测试过程当中,会生成大量的参数值。刚才提到的是遗传算法筛选,保留对我们有用的参数,接下来来看其中的细节。
支付宝用例自生成技术实践

这段代码当中,有一个case语句,针对这个方法有三个分支流程,那么生成一个参数之后我们会去保存它走过的分支信息。

比如我们生成了一个参数name=A,那么它会走到a,这样走过了一个分支,继续变异这个参数,当下一次编译的时候,走了相同的路径,如果再次生成了A,那么此时A没有保存的必要,因为代码分支走的是一模一样的路径,保留一个即可,这样就会简化用例数量,从原来几百个甚至上万个用例最后过滤成几个,运行执行模式的时候,将会大大的去减小测试的时间。

除此之外,我们在运行过程当中,还做了Trace的跟踪,以及收集第一层业务的返回值,比如a是个业务接口,我们会保存a方法的返回值i,我们只会保存业务接口的返回值,不会保存其他的返回值。保存业务接口的返回值要做什么?
支付宝用例自生成技术实践

生产模式中,生成了参数A,参数A执行了各种业务接口,拿到它的返回值user 、id、address,那么当我们在持续集成阶段,将沉淀的用例重新跑一下,还会生成A,那么再收集到user’、id’、address’,正常来讲,相同的参数,在业务流程中跑一下,它的返回值应该都是一样的,那么我们可以做一个操作,比如说,我们会去判断生产用例模式当中收集到的数据和执行模式当中收集到的数据是不是一样的。

如果不一样,业务同学就需要进去查看一下,防止说某次迭代不小心改了一些公共的类,导致服务异常。如果是非x等的接口,判断就需要加一些规则,需要业务同学去查看一下,加一些规则看是否符合业务预期。

接下来我们看一下实际当中我们是如何通过用例自生成来识别服务异常。
支付宝用例自生成技术实践

如图所示,针对test接口,生成的用例的参数,这个用例参数跑了多少行代码,以及最后的结果都会展示给我们的用户。

比如说像这个test,它的参数是一个复杂的对象,图中显示的参数数据就是我们生产出来的参数值,它覆盖了28行,中间有两个业务方法的调用。

当用户点击执行详情的时候,就能看到中间业务调用方法的返回值,然后点击覆盖率按钮的时候,可以看到这条请求的Trace信息。

这个Trace信息不是指说当我做测试的时候整个应用的覆盖率信息,而是指针对这个用例它跑过哪些行,我们会给收集起来,用户通过这些数据就能比较清晰的感知到:如果发生异常了就比较清晰的看到是什么样的异常,方便用户去排查问题。
支付宝用例自生成技术实践

四、总结和价值

支付宝用例自生成技术实践

案例一:正常调用是没有任何问题的,从业务逻辑上将,它请求参数的某些属性是不能为空,通过用例自生成这项技术会生成正常的用例以及一些异常的用例,就是那些属性为空的用例也会生成出来,将异常用例调用到服务端之后,能发现接口报异常了,跟踪Trace的信息以及查看异常报错能看到这种非空的校验是放在了DB层,通过DB非空条件约束,满足业务需求,这样做的话,服务确实能稳定运行,能满足业务需求,但这种做法其实是有隐患的,把业务参数直接通传到了DB,请求量小的时候体现不出来,当请求量比较大,比如说搞一些运营活动,类似于双十一活动的流量给DB带来很大的压力造成系统故障。

案例二:正常测试没有问题,它的场景是我输入一些查询条件来查询业务数据,但当有些业务属性为空时,会将DB里所有的数据查出来,然后针对这些所有数据的结果做便利,就是指对一个一个的数据去做业务处理。这种方式塞口没有作业务兜底,如果一个数据为空时,DB里所有的数据都查出来之后,导致业务GC应用非常频繁,它要做大量的业务处理,接口的耗时直接就增加了5倍,并且有大量的请求超时。

案例三:相比较来说简单,一般对外提供服务的接口,是不会对外抛出异常的,服务最外面会有一个trycatch,否则会把异常就去抛给了调用方,若调用方没有处理,那么就一层一层的往上抛,很有可能就会让用户感知到,有个弹窗或者会显示异常信息。

通过用例自生成技术,我们也发现当满足一些参数组合的情况下,有些对外提供服务的接口是抛出了未识别的异常。在这些项目实际运行当中,我们能看到测试覆盖率是有了显著的增长,从而发现问题的能力也有所增强,提高了服务的稳定性,节省了一部分时间和人力的成本。

本文由社区志愿者整理而成,设区内容志愿者火热招募中,有好礼相赠哦。欢迎加入!戳我了解详情加入!

上一篇:持续定义Saas模式云数据仓库+实时分析


下一篇:JS 中require和import总结