接口测试系列——AutoDiff流量回放在集成测试中的实践应用

满堂花醉三千客,一剑霜寒十四州。这篇文章主要讲述接口测试系列——AutoDiff流量回放在集成测试中的实践应用相关的知识,希望能为你提供帮助。
01—接口回归测试面临的问题1.1  迭代形态及组织变化
迭代加快
在当今的互联网行业的形态背景下,产品需求迭代都愈加地追求快速交付流动,大部分公司都采用精益产品研发流程。那么在汽车之家经销商bu内部,我们近2年的平均需求交付时间≈4天。而潜在的问题是,由于原自动化接口测试的开发/维护成本较高已追不上迭代,我们大部分的回归测试是被跳过的,更多要依赖开发人员水平+测试人员经验,所以一旦常规的节奏被打破(紧急需求插入、人员状态、人员变化等),回归缺失的问题就会暴露出来,而这种外来变化其实也越来越频繁。
项目/人员变更
随着公司内部布局的各种变化,也会产生一些项目交接/重组,或人员转组的情况。一般在业务上会有一波新的改版,而后端接口技术上也会进行重构。但由于刚刚交接,产品、测试、开发都不会非常熟悉原有系统,传统回归测试手段,由于无法确定预期很难进行。

1.2  技术演进
另外随着技术的不断演进,给日常接口回归测试也带来了多元角度的影响:

  • 架构或应用内部代码的演进重构的过程本身(容器化、接口合并/拆分/优化、数据源迁表/迁ES......),需要回归验证支撑;
  • 服务节点多,只改了1个服务几行代码,可能需要验证所有调用方;
  • 服务链条长,跨BU/服务调用多,通过Mock单服务测试不能独善其身,集成/联调测试阶段问题多;
  • 服务功能类型增多,技术类型需求增多,使用传统回归验证方式效率低,效果差。

02—解决方案分析2.1  接口自动化vs流量回放
在前述背景下,我们引入了新的流量回放方式来进行接口回归来解决上述问题,使用上一个版本的响应情况作为基准,默认是正确的,然后与新版本的情况做对比,检查差异项是否为预期内。相比原有的接口自动化测试,这种测试方式的收益比要更高。

整体方案
开源方案
当时的流量回放方案大概有 2 种,原理可以简述为:
  • 基于 HTTP 接口纯黑盒的回放,如Diffy。
拉取线上流量请求信息后,在部署了不同版本代码的 2 套测试环境进行 Http
接口的调用回放,实时或离线的比对接口的 response响应的区别是否符合预期,来进行测试。代表的有 twitter 的 diffy工具

  • 基于代码方法级别的回放及验证,如 Repeater
回放思路相同,但基于代码方法级别的录制和回放验证,一般将查询数据库等操作,在录制时进行记录,回放时进行mock,从而增加测试数据的稳定。java 中代表的有阿里的 Repeater

方案分析
而结合我们实际诉求,以及方案的优缺点,进行匹配:
1.部门内.net 转 java、接口合并等技术任务,需要这套方案能进行跨语言、平台的回归
2.需要能够兼并读+写接口的回归,即需要Mock 或数据还原
3.需要能支持集成联调阶段,即对外真实的外部调用)

基于分析,我们发现单独基于黑盒的方案,对写接口验证能力较弱(写接口对外只返回一个状态ok),测试数据不稳定 case 类型偏移(如线上有数据的商家,在测试环境回放结果空)。而单独基于Repeater 的方案,又只能支持单语言平台(不能支持.Net 转 java 的实际诉求),Mock 功能不能兼容有IO 变化的迭代(只能匹配到已记录的 db 查询)。
没有完美解决所有问题的银弹。
经过综合考虑,最终确定:
  • 对于读接口(80%):自研 AutoDiff进行黑盒流量回放测试。
  • 对于写接口:使用 Reapeater 进行回放测试。而其他仍不能支持的写接口场景(如有 io 变更、跨语言等情景)再进行常规的接口自动化测试或手工测试。
整体方案:

在应用层,可以支持各开发阶段及各种回归需求场景,而在底层,读写接口分别由2 套基于不同实现的方案进行支撑,同时按照流量的子调用情况,进行读写接口方案的适配:

底层的读接口方案,整体架构如下,数据层支持从多种数据源收集并整形流量,经过一定的清洗、筛选、解析,精准规则解析并进行用例计算。测试过程采用实时比对方式,比对算法支持不同接口、进行噪音处理等处理,最终汇总生成报告及结果分析。

底层写接口方案,是基于 Repeater 进行二开和包装,录制数据上报 kafka;经 flink 消费后,加密后存储到 hbase;并提前进行分词后,再存储到 ES 索引用来检索。录制支持组件进行了落地适配,丰富了部门内常用组件的若干插件。并提供了应用层的环境、配置管理等:

03—核心问题解决3.1  写接口(Repeater)回放原理
Repeater 是阿里基于 JVM SANDBOX 开发的一套开源框架,核心功能是提供了通用录制回放能力,其官方介绍:
“无侵入式录制 HTTP/Java/Dubbo入参/返回值录制能力(业务系统无感知)。
基于 TTL 提供多线程子调用追踪,完整追踪一次请求的调用路径。
入口请求(HTTP/Dubbo/Java)流量回放、子调用(Java/Dubbo)返回值 Mock 能力。”
也就是说 Repeater基于 sandbox 提供的能力,可以记录到 Java 方法级调用的入参、返回值、调用顺序等信息,并把外部调用的入口请求,定义为入口调用(比如 http 接口默认将 Httpservlet 的拦截作为入口调用),而将该次请求所产生的下游调用,称为子调用,流量的录制过程就是把入口调用和子调用绑定成一次完整的记录。
具体哪些下游方法调用会被记录下来,可以由埋点插件自定义,应用于业务时,一般会将Dao 外部 IO 层调用的 Java 方法进行埋点,拦截录制,比如数据库查询或外部接口调用的返回结果实体。

像使用 retrofit 的接口调用,录制后的内容是这样的:

录制下流量后,在回放时,会重放入口调用,并在运行时从录制的子调用中根据MockStrategy 搜索匹配的子调用,如果有调用到已录制的子调用,则进行 throwReturnImmediately返回录制时记录的返回值,没有匹配结果则抛出异常阻断流程。
而针对需要录制的下游方法调用类型,官方已提供了若干常用的插件,在落地过程中针对我们部门内部常用的组件,补充了若干插件的开发,截至21 年中旬支持的插件大概有:

3.2  降噪处理
在流量回放测试(可以说是所有接口自动化测试)落地中,误报率一直是生命线一般的指标值,同时影响着稳定性、结果可信度、排查时间等,是至关重要的一项指标。
读接口-智能去噪
对于黑盒的读接口方案,误报最大的因素是噪音,在实际测试中,大部分的接口 response 都会包含一些时间戳或者广告 pvid 等字段,每次请求生成的都不一样,导致比对不一致报错,但其实业务上并没有问题,有的项目甚至所有100% 的 case 都会因此报错。比如下面这几种

可以看到噪音字段的种类、字段名多种多样,不同项目光时间戳的字段就有cdntime、timespan、timestamp等,只提供人工配置忽略的话,非常繁琐耗时不可接受。
针对噪音字段的特性是,这些字段的变更并非本次变更所导致,也就是说即使本次代码没有变更,同样的环境,多次刷新下,这个字段值也会变。
由此我们将黑盒回放的过程进行了优化,由原来的新旧代码各请求1 次,优化为会对旧代码环境请求 2 次,将差异的字段记录下来,就可以认为是我们想要忽略的噪音,在和新代码环境响应进行比对时过滤掉,剩余的差异才是真实代码差异所导致的变更。同时也支持人工配置的指定字段。 

为了进一步优化这个机制的效果,减少出现 “请求 2 次旧代码的时候这个字段没变,但请求新代码的时候正好变了,比如秒级时间戳”的偶发事件,请求顺序需要固定为:请求旧代码--> 请求新代码--> 请求旧代码。
这样,如果 2 次请求旧代码时间戳变了那它会被识别为噪音没问题;而如果 2次请求旧代码时间戳没变,那么时间是流逝的,夹在中间请求的新代码时间戳必然也是一致的,这样该字段就不在差异范围。
而对于对相同代码环境请求2 次时,哪些类型的 response 差异可以被标识为噪音,主要考虑了以下几种:
  • json 节点value 不同:是噪音;
    此类差异被识别为噪音的差异项,将记录这些节点jsonpath,并在新、老代码报文比对时进行忽略。
  • json 节点key 不同:不视为噪音;
    虽然我们的大前提是连续 2次请求线上代码环境,但可有可能因环境或第三方问题造成某次请求报文结构不同,我们的目的是识别噪音用于后期忽略,为了防止误加,此类差异,不会视为噪音。
  • json 数组个数不同:不视为噪音;
    理由同上,因为不是叶子节点,而是数组某个元素的话有可能比较大的节点,直接作为噪音去忽略的话,很有可能造成最终漏报。但仍有一些情况是推荐类接口,有可能第一次推荐3 条内容、第二次推荐 4 条内容,这种情况下数组个数在业务上是应该被视为噪音的,但目前没有对此进行特殊识别。
  • json 数组顺序不同:是噪音;
    主要出现于部分推荐接口,或不关心列表排序的接口。
  • http 层响应码、响应头不同:是噪音。
    由于大部分项目功能测试不关心头信息,所以 AutoDiff中,默认不比对头,只有配置了要比对头的项目,才会提供这部分的检查及去噪。
读接口方案引入这套去噪机制后,效果显著,如果是带有时间戳的项目,报错率可以由> 90%降到< 10%。
而仍存在的误报,主要是推荐类业务的List 接口,目前无法解决,即接口业务上每次会推荐数据不同的数据,这种类型的接口可以设置根据 schema比对,但为了不漏报,故没有进行固定忽略。
写接口-完善 Mock
对于基于 Repeater 的写接口方案,由于其自带的Mock 支持,出现噪音的场景与原因与黑盒并不相同。比如上面提到的推荐数据变更导致的误报,在Repeater 中开启 Mock 后并不存在(查询时会返回录制的数据,并不会真实重复查询)。
但 Repeater 方案中,因 response内包含的时间戳等字段不一致导致的误报,也同样存在,主要是由于这些字段是由Date、随机数、native 原生方法等生成的,这些方法 jvmsandbox并不能进行拦截录制,回放时还是真实调用了。

解决方式是,将这些无法拦截的native 方法用可以拦截的 java 代码封装一层方法,在项目中引入,并在编译阶段,将代码中包含这些方法的部分替换为引用我们自己定义的方法,这样构造方法、原生方法就变成了普通的java 方法。然后提供插件对这些普通 java 方法感知和干预,即可以成功的录制到时间值,用于后续mock:

除此外,我们还不同问题进行针对优化,如下移原 mybatis 插件的 insert埋点,用于解决 response 内回显插入数据主键 id 场景下,回放值为 null 的误报问题等。

而目前还存在的误报,主要是会因新老版本代码逻辑变化,子查询无法匹配到,导致的误报,比如原来查 2 次库,现在只查 1 次,或者直接改成查 solr 了等。以及因部分查询类型未完全埋点,未被录制下来导致 case 失败误报的情况等。
3.3  用例计算
那么在提高用例质量方面,我们设置了一套组合的测试用例生成规则,生成时可以按照以下几个规则,分别提取指定数量的 case,合并成一份 case,兼顾时效、与覆盖等维度,相互弥补:
A. 近期日志测试(保证时效+随机性)
接入各个来源记录的流量,拉取最近期的随机case。可以进行人工指定配置,在业务上支持使用者的不同测试需求,对整个站点所有接口,各测试部分 case,或是对某个单接口进行的专项测试,可以灵活设置。

B. 人工录入(支持重要/打底场景)
直接添加录入,或导入 txt,用于创建部分打底的case。
C. 精准提取(高覆盖低冗余)
提取日志时,按照一定规则对同一接口的流量,进行分类,根据配置的每个接口所需条数,尽量返回更多不同类的。分类依据有如下:
1)url 参数
根据 url的参数组合及值进行处理,清洗掉无关的参数等后,进行类似如图的分组后平均提取,后续会继续补充一些针对参数值的定制化规则。
2)response
根据历史录制/回放过的流量 response,按照是否 json,$.result 节点的类型进行一层大类的分类,然后不同大类选择不同的特征项,对流量进行标注,并具体计算出具体的特征坐标值。
在生成流量时,每大类都按照 Kmean 算法聚类,对于异常的大类,类簇 k最多 2 类,然后每个聚类平均提取。
3)方法调用链覆盖情况
计划可以结合 jacoco,对流量覆盖的方法进行记录,并将覆盖路径相同的 case标识为同类的 case。目前还在计划中。
D.参数化生成(满足部分单接口专项)
录入某条种子请求,将需要参数化的值以$形式标识,如:
GET -- /apiprice/api/XXX?seriesId=$series& withEqid=true& $cidAnddid 
POST--

并分别录入每个参数(如series、cidAnddid)的数据文件,执行时将按照各参数值,生成笛卡尔积的case 集合。
用以支撑线上数据量覆盖不全或希望全量测试,需根据 db 内现有数据构造生成 case等情况。
3.4  跨平台、跨接口比对
在前述提到如.net迁 java 项目接口、接口优化等的各类需要做回归验证的技术任务中,常常伴随着新老版本的接口不能直接比对的情况,比如下图的2 个新旧版本 json 响应:

由于 2 套新旧代码模板定义的接口 protocol不同,导致结果内容存在了不同节点。如果直接比对一定是会报错,所以我们在对比时进行了提取配置,分别将$.Data和$.result.list 进行提取,然后再进行比对就满足了这些有变化的场景。
跨接口场景(接口合并/拆分/优化等类型的变更)
那么如果上面的场景支持了,其实完全不同的2 个接口,也是可以通过这个思路,可以将相同业务逻辑的字段,进行自定义映射关系的配置,进行提取,将2 个响应都转化为相同的结构,然后即可进行后续的比对以及可视化的结果展示。
比如我们有一个活动车型列表接口 A,与另一个业务线的接口B,返回的车型应该是同步的,只是附加的其他字段有区别,想要在线上进行定时的比对核验,json 结构如图:

可以使用 jsonpath的映射关系作为 key,各自按路径提取的值作为对应的 value,分别转化组合形成新的 json 结构文本如图:

转化后的 json 结构是相同的,可以清晰的从2 个完全不同的接口中,只提取展示看到我们关心的差异,然后将 2 份新的样本(具有相同结构)进行比对,相比针对每个需求单独编写接口自动化测试代码更为直接简单。
3.5  闭环功能
除了上面一些核心问题的解决,也需要提供配套的闭环辅助功能,以及各功能的易用优化,使工具达到更加快速、高覆盖的目标。
如与流水线的集成;对报错进行标注,要定期收集,持续解决优化误报的情况;以及分别按照接口、代码维度统计展示覆盖情况,便于对测试以及用例质量情况的一个反馈等。



而对于报错排查,为了缩短实际落地时错误排查的时间,我们持续基于各方反馈,对报告进行了持续大大小小的优化。

比如写接口的报告可以清晰的切换查看response,以及子调用的详情。读接口的报告将所有 case 集成在一个报告中,可视化的 diff 信息、便捷的设置入口、支持持键盘↑↓键切换查看、切换查看原始/处理后的 response,等等。目前下一步计划还可以从报错聚合分析和展示方面,进行一些优化。



04—效果评估方式对于整个方案的效果评估,我们从3 个角度进行了分析,基于这些指标进行定时的观察,以便于后续的计划与跟踪:
4.1业务价值
实际落地后,为部门解决的问题,或者创造的业务价值。对于测试工具,更多的是结合我们当前部门业务特点、技术、以及质保体系的情况,痛点情况来评估引入Autodiff 流量回放后创建的价值:
1. 解决弥补了部门内原来回归测试随机开展的问题。为 90+个后端服务提供需求交付的回归测试保障;
2.为原先因无法全量测试而不敢进行的技术任务,提供了有力支撑。
全年共计支持15000 +次回归测试,50+次技术专项任务。共计回归约440 万 条用例 , 在各阶段拦截 100+ 个问题,节省人力约 4 人年。
4.2  运营推广情况
工具的落地推广运营部分,我们主要考虑统计了:常用项目接入率(数)、常用接口接入率、项目平均case 数、已接入项目日常发版执行率、代码覆盖率。
4.3  工具本身
在工具本身的能力情况方面,根据我们对这套回归测试工具核心的期许——
能够快速、高覆盖的完成集成回归测试——对相应的观察指标进行拆解。 

快速部分的指标可以拆解出:接入时间、执行时间、维护时间(用例拉取/维护)、排查时间(千条 case 时间); 
高覆盖部分可以拆解为:能覆盖的诉求场景以及测试类型、代码覆盖率高、实际业务场景覆盖率高;
以上过程指标有的可以直接统计,有的不能直接观察统计,可以采取定期访谈的方式,或者替换拆解为其他二级指标,如排查时间更多的用误报率代替。
05—效果评估方式基于前述几个方向的指标持续观察,我们下一步会继续优化功能:
1.用例的精准化生成,提高覆盖率,减少维护成本;
2.报错聚合,减少排查时间;
【接口测试系列——AutoDiff流量回放在集成测试中的实践应用】3.尽量能覆盖更多的诉求场景或测试类型;

    推荐阅读