cover_image

告别盲测---新车电商精准测试落地实战

闻小龙 之家技术
2018年09月11日 02:33

图片点击关注“之家技术”,了解更多技术干货!


总篇8篇

2018年 第8篇

一个好的程序员往往能够考虑到逻辑发生时所有的情况,所以他们用一行代码去做一件事情,可能会用几十行代码去做前置校验,是否能把这些逻辑分支进行一个充分的覆盖是测试人员应该考虑的问题,但单纯凭经验去对如此之多的分支进行覆盖,难免出现漏测少测得情况。

所以我在拿到一个改动的时候,会花很长的时间去跟开发聊这个改动涉及的影响范围,然后再去用例覆盖,这样虽然也能做到充分的覆盖,但是时间成本跟沟通成本确不小。

还有另外一个比较让人比较为难的事情,开发做了一点小小的改动,但是因为开发也无法评估这个改动对项目的整体影响,迫不得已测试人员只能掘地三尺的遍历所有相关逻辑,明明知道自己做了不少无用功,但是没有办法,因为问题常常就发生在最不起眼的地方。

针对回归测试负担太重的问题,很多公司想从另外一条解决思路,各种自动化解决方案层出不穷,很多公司也一直发力各种自动化技术,但是很多自动化测试确没有发挥出预想的作用。

第一个问题是自动化测试的结果没有说服力,我曾经待过一家公司,就有领导就提出了自动化代替手工测试的思路,当我们编写了大量的自动化脚本后发现,即使我们运行出100%的提测版本,手动测试部门确还能报出大量的BUG。

另一方面自动化测试其实还是覆盖了很大一部分的逻辑,假如说自动化能够告诉手工测试自己覆盖的部分,这也能够大大的减轻手工测试的工作负担,但是这一点也并不容易做到,久而久之很多公司都变成了为做自动化而做自动化,手工做手工的,自动化做自动化,慢慢的自动化测试也变成了一种可有可无的附属品。

总结下来,测试过程中有如下痛点。

图片

针对这些问题我们做了一些思考,并给出了我们的愿景,如果手工测试的时候,可以有一种可视化的覆盖率反馈,那测试策略就不需要依赖于QA的主观经验,并且我可以根据这种覆盖率反馈不断地校准我们的测试用例。

如果测试能够基于代码的变动,我们就能够精准的只测试受影响范围,而不是采用宁可错杀一千不能放过一个的策略,那我们就可以用更少的人力更快的完成项目的测试。

如果我们的自动化测试在给出用例通过率的同时,还能有一套用例的覆盖率的说明,那自动化测试报告就更加有客观性和说服力。

如果我们的自动化测试能够告诉手工测试人员哪些逻辑被覆盖,哪些未被覆盖,而手工测试作为一种自动化测试的补充,对没有覆盖的部分进行补测,那自动化测试就能更好的帮助手工测试人员。

针对以上问题,我们进行了一些调研和选型,给出了我们的解决方案:

图片

接下来我们一起简单的了解下,如果要实现这些目标,我们方案中应该包含哪些功能模块?

图片

·        Diff引擎,它是一个可以提供多维度代码变动细心的功能模块

·        自动化开发平台

·        代码覆盖率组件,基于JaCoCo二次开发,支持增量和全量的代码覆盖率监控

·        通过持续集成工具Jenkins串起所有流程

 

接下来我们对照工具的整体架构图来梳理下,整个系统的工作流程。 

图片

第一步:从Git 版本管理工具获取代码变动信息,我们使用了Python对变动信息进行了加工,变成了方便读取和使用的Json格式,这部分的逻辑和原理后面会详细讲解,这里就不再赘述。

第二步:处理过的行变化信息转化为方法级别的变动信息,这种转变是如何实现的呢?这里用到了AST技术,AST技术又称作抽象语法树工具,它可以把静态的源代码抽象成为树状的表现形式,树上的每一个节点代表源代码中的一种结构我们通过树状结构能够把行变动信息转化为方法级别变动信息。

第三步:我们在知道哪些方法被改动过后,我们很容易的去遍历这些方法,如果这个方法是接口方法,那么我们会通知相关人员对这个方法进行对应的手工及自动化测试。

第四步:是我们的代码覆盖率功能,它基于开源项目JaCoCo,JaCoCo主要是通过ASM在字节码中插入探针,每个探针都是一个BOOL变量(true表示执行、false表示没有执行),程序运行时通过改变探针的结果来检测代码的执行情况,这并不会改变原代码的行为,JaCoCo通过执行时数据+源码+class文件生成覆盖率检测报告。

最后:我们是通过JaCoCo的maven-plugin的形式结合到项目中的,而JaCoCo的几个重要节点是通过maven命令触发,而所有的模块又通过Jenkins融合到一起,让使用者几乎没有任何操作成本,就能拿到最新的代码覆盖率报告。

图片

可以看到这是一个代码级别的变更记录,有文件级别的信息,代码行级别的信息,行变动信息是以差异小结的形式展示的。

a版本到b版本删除的行前面会用减号标注,b版本新增的行前面会用加号标注,差异小结顶部有ab版本的开始行号和行范围,从第一个非删除的行号开始计数,所有新增的行为改动行。

图片

我们最后把解析出来的文件信息跟差异行信息序列化为Json 格式,方便后续的逻辑读取和使用这些数据,我们也可以把每一行的具体代码取到,目前没有用到这部分信息,所以做成配置隐藏掉了,这块要注意一个问题,第二个被删除行不应计入变动行信息,因为差异行信息应该是目标版本存在的行,空行和注释也算一行。

图片

Method级别Diff信息

首先我们要对变动方法给出定义,我们定义每一个方法如果有一行代码发生了变动那我们认为这个方法就是一个被改变了的方法,我们具体的做法是用每一个方法的行范围去比该文件的每一个行变动信息,当一个变动行命中了这个方法的行范围,那么把这个方法的is_change标识置为true,那我们就能获得一个项目的差异方法列表,这个对我们的测试时有很大的指导意义。

我们是如何知道每个方法的行范围的呢?这里我们使用了前面提到过的AST的技术,我们可以从语法树对象中获取到每个方法的行范围,我们会获取到一个方法列表,这个列表里面的对象包含方法的行范围,是否变动,假如是接口方法还会有RequesterMapping字段,接口的类型,还有他的参数列表,其中包含每个方法的参数类型及参数名。

图片

接口级别的Diff信息

我们已经拿到了变动的方法列表,那么我们很容易的想到,其实我们可以更进一步的拿到这些变动方法中所包含的变动接口,我们来看一下这个图,源数据还是行差异信息,还是用方法的行变动信息去命中方法的行范围,假如说命中了一个方法,而这个方法恰好是一个接口方法,我们同样可以通过AST把这个接口的信息加入到一个接口变动列表当中,而这个列表直接可以当做一个项目提测后的测试项,直接通知给相关的接口用例维护人员,这也能解决另外一个问题,自动化接口测试用例更新不及时的问题,这个后面会有详细的论述。

图片

全量代码覆盖率报告展示

大家看到的是一份全量的代码覆盖率报告,而且是我在对一个改动进行了充分的测试后得出的一份报告,大家发现了什么问题?这份报告来看我的覆盖率数据确少的可怜,而我其实已经对功能进行了充分的测试,这明显不是一个客观的量化。

图片

全量代码覆盖率使用中的痛点

从测试人员的角度来看,这份报告不能告诉我本次改动的代码我覆盖了多少,还有多少没有被覆盖。

从项目管理者的角度来看,我不能根据这份报告的覆盖率预估项目的上线风险,这份报告对测试人员跟项目管理人员都是一份无用的报告。

造成这个问题的根本原因是,全量的覆盖率统计的整个项目的代码被覆盖的情况,而我们不会去对整个项目的逻辑进行回归,而解决这个问题的思路应该是根据代码的差异信息,只取有变化的逻辑进行统计,这样测试人员能够很明确的指导我实测部分跟应测部分的差距,从而对未测逻辑进行补测,而项目管理人员也能跟进覆盖率的数据对项目的测试进度进行一个客观的判断,依据数据和具体的项目情况对上线进行决策。

图片

研究方案

确定了思路我们马上对方案进行了调研,经过调研发现大致分为后处理和前处理两个方向,我们马上对这两个方案进行了可行性评估。

首先是后处理,后处理是在报告生成后对HTML的报告进行处理,我们发现在报告页面的每个代码行前面都有一个Class标签,而覆盖未覆盖和部分覆盖是不同的标签,而不需要被覆盖的代码则是普通的文本显示,所以理论上在报告页面层我们是可以把未变动的代码行置为不需要覆盖状态,但是我们在统计各个维度覆盖率的时候遇到了问题,我们发现各维度的覆盖率都需要重复计算,计算的准确性需要经过复杂的测试,而有些指令维度和圈复杂度维度的覆盖率源数据无法从页面获取到,我们很快否定了这个方案。

再来说说前处理,前处理的思路是在进行覆盖率分析前,将覆盖率数据排除掉无变化部分,复用JaCoCo的覆盖率计算逻辑,这种方案需要对开源框架进行二次开发,接入Diff引擎方法级别的变动信息,这种方案开发成本较低,而准确性较高。

图片

方案实施

接下来看一下我们的具体实施,先来看下JaCoCo的原理,JaCoCo通过ASM在字节码中插入Probe指针(探测指针),每个探测指针都是一个BOOL变量(true表示执行、false表示没有执行),程序运行时通过改变指针的结果来检测代码的执行情况(不会改变原代码的行为),其实这个过程有点像很多地图软件的轨迹功能,我走过的每一个坐标都会被记录,系统可以给你一个多次旅行轨迹的一个Merge的记录,然后用户可以通过这份报告看到自己去过哪些地方,哪些地方没有去过。

代码注入又有两种方式,Offline方式和On-the-fly方式,这两种方式实现的方式不同,类比到刚才地图的轨迹功能举例,Offline只有在你结束旅行后才能够显示这次旅行的轨迹,而On-the-fly则是在旅行过程中实时更新当前的旅行轨迹,并且On-the-fly还有另外一个好处就是可以通过TCPdump方式远程的获取目标服务的代码覆盖率,明显第二种方式更加的灵活。

这个地方还有一个坑,还是类比到轨迹软件,我们在多次旅行后希望得到一个所有轨迹的显示,而不是最后一次的轨迹信息,我们的代码覆盖率同样也有这个问题,每次我们获取到操作记录的源数据都会对上一次的覆盖率数据进行覆盖,这样的话我们只能看到最后一次的操作记录,这明显不是我们想要的,我们的解决方案是每一次获取完操作记录之后,把上一次记录的数据进行归档,在下次获取了操作记录之后使用Merge命令对他们进行合并,避免了操作记录数据的丢失。

下面我们来看一下覆盖率工具的Diff引擎的接入。我们先来看一下JACOCO在工作时重要的三个Maven命令。

Dump:就是通过TCPDUMP方式从远程的被测服务拉去操作记录数据。

Merge:是为了保持多次拉去后覆盖率数据的完整性。

Report:是把前面两部产生的操作记录进行覆盖率的计算和报告的生成,我们要做的是在计算之前,把操作记录数据进行清洗,大致的逻辑是遍历每个以方法为单位的操作记录,与变动方法列表进行比对,抛弃掉没有变化的方法的操作记录数据。

这里的另外一个坑就是要考虑到方法重载的问题,所以这里面需要修改代码除了比对方法名,还要对方法的参数列表进行比较。

图片

报告前后对比

然后一起看一下接入Diff引擎后的效果,首先这两个报告是在相同代码,相同的测试程度下得出的。

图片

大家可以看到覆盖率报告在包维度有两个变化,第一个变化是有一些包被自动隐藏了,是因为这部分包中的代码没有被改动过,被我们在生成报告时自动剔除,第二个变化是有覆盖率的包的覆盖率数据有很大幅度的提升,原因也是在这个包内剔除了未改变部分。项目管理人员可以根据这一全局的覆盖率情况评估当前的测试进度以及上线风险。

图片

类维度也是相同的情况,报告剔除了没有改动的类而被改动的类也有了更高的覆盖率。

图片

再来看方法唯独,方法维度同样也只保存了差异方法的覆盖率,而测试人员可以结合代码维度的增量报告来判断这次测试的目标逻辑,需要覆盖的条件场景以及当前的覆盖情况。

图片

问题解决: 这个在后来的使用中也遇到了一些反馈,比如说当进行主干回归时覆盖率报告总为空,最后分析发现因为主干回归时使用的是Master代码,而跟Master进行比较之后因为没有变化,所以Diff引擎返回了一个没有任何变化的信息,那这个信息就直接导致了我的变动方法列表为空,最后导致了所有的操作记录数据被抛弃掉,其实解决办法就是增加了版本维度的对比,相同分支通过版本号对相同分支的两个版本进行diff,最终生成两个版本之间的增量覆盖率报告。

图片

与持续集成的融合

我们团队一直使用的是基于Jenkins的一套持续集成方案,我们希望保持使用者的习惯,避免增加使用者的操作成本,所以我们当时在做覆盖率选型的时候也考虑到了与持续集成的融合问题,首先是Diff引擎我们使用Python进行编写,这样比较方便通过命令行进行触发,并且能够非常方便的向Diff引擎传入需要的参数,而覆盖率的触发我们选择了Maven插件的方式,也能通过mvn命令非常灵活的在各个节点进行触发

下面我们分析一下精准测试方案是如何融入持续集成的,

第一步:源码的配置这一块主要做的是代码的拉取,并且根据一些测试需要,修改一些项目配置文件的数据。

第二步:把配置好的源码进行打包和远程部署。

第三步:通过Diff引擎的调用获取到当前分支与Master的差异信息。

第四步:调用覆盖率模块的命令计算并生产增量报告,为了便于报告的保存和集中展示,我们还做了一个报告管理中心,每次生成完报告,我们会通过接口把报告上传到报告中心方便相关人员查阅。

图片

图片

更可信的自动化测试:

图片

很多时候自动化测试用例并不是由该功能的业务负责人来录入,这导致了自动化测试用例的质量并不高,很多测试用例只是能反应出即接口可以调通,并对返回的数据进行了简单的校验,而接口内部的逻辑覆盖确常常没人深究,这就导致了自动化测试的覆盖效果不太好,结果的可信度自然也就不高的原因,

另一方面随着代码逻辑的更新,如果自动化测试用例不能及时的更新,即使刚开始一套优秀的自动化测试用例,也会导致自动化测试的效果不佳。

自动化报告只能反馈用例执行的成功与否,并不能说明被测项目的覆盖情况,对决策参考价值不高。

自动化其实是做了很多逻辑的覆盖的,但是因为没有办法向别人把这部分的界限说清楚,导致手工测试人员无法基于自动化测试的结果继续进行工作,导致了一些测试工作的重复性劳动。

图片

方案改进:

我们可以把自动化与精准测试相结合,我们可以根据自动化测试用例的覆盖率反馈不断的对用例进行调优和校准,直到最后获得一套高覆盖率的自动化测试脚本。

我们可以把自动化测试与diff引擎结合,当扫描到接口变动信息时,及时通知相关的自动化用例维护人员,始终保持自动化接口测试很高的有效性。

我们在提供一个自动化测试报告的同时应该还提供一份执行结果的覆盖情况,我们可以告诉报告的阅读者,我们的自动化覆盖了哪些逻辑,而哪些逻辑未被覆盖。

基于上一点,我们可以让测试过程就像一条流水线一样,自动化先进行测试,然后手工测试以接力的形式对自动化未覆盖的部分进行补测,这样不但大大减轻了手工测试的人力消耗,而且让自动化在测试工作中发挥了更大的作用。

图片

图片


继续滑动看下一个
之家技术
向上滑动看下一个