写点什么

干货 | 质量保障新手段,携程回归测试平台实践

  • 2021-01-25
  • 本文字数:5460 字

    阅读完需:约 18 分钟

干货 | 质量保障新手段,携程回归测试平台实践

一、系统回归问题

回归测试是软件生命周期一个十分重要的环节,但项目在随着版本的逐步迭代,功能日益增多,系统愈加复杂,在测试过程中测试人员常常需要回归稳定版本的功能以保证不被待发布版本需求所影响。若要对系统进行全方位回归,这个测试的工作量将会非常庞大,而且可能几百上千用例中才会发现一个甚至是 0 个问题,测试投入产出不成比例。


而 CPR 则为上述问题提供了较好的解决方案。它通过基于稳定版本的输出,对待发布版本的输出进行比较,同时还将校验两个版本对下游请求的差异。根据比对结果评判待发布版本是否正确,可以大大降低回归的工作量。

二、CPR 目标

大量真实流量确保覆盖率将录制的流量作为用例管理起来进行自动化回归流量回放支持子调用自动化 mock,避免回放产生脏数据流量回放支持子调用结果的验证减少人力资源

三、目标实现基本过程


1)首先将部署了稳定代码的服务器作为流量采集源。测试人员在进行功能、接口测试时,实现测试执行过程中主调用以及子调用的入参和返回值的录制。通过功能和接口测试实现对应用功能的全量覆盖,使得应用中请求流量都会被录制到。


2)将录制到的请求流量复制到 console 平台,由测试人员分析有效的流量归纳为用例。后续即可采用这些有效用例来对待发布版本进行回放和差异比对。


3)回放完成告知到测试人员,由测试人员对回放的差异结果进行确认,快速发现被测系统 bug 并解决。

四、CPR 原理及实现

4.1. CPR 录制的数据抓取点

如上图所示,CPR 采集的数据主要为两方面:待测应用接收到的客户端请求及响应;待测应用接收请求后向外部服务的子调用请求及响应。CPR 目前支持的请求类型如上图橙色框中所示;但 CPR 本身设计时对各类请求采用插件式设计,使其具有较好的扩展性,对于后续其他需要捕获的类型能很好的进行支持。当前 CPR 系统支持的报文协议为 JSON 和 PB(ProtoBuf)。

4.2. CPR 结构简介

CPR 分为两大组件:


1)CPR (CtripPaymentRepeater) 组件,该组件基于开源的 jvm-sandbox 开发,用于录制和回放流量。此组件核心为两部分:


CPRRecord:目标是在稳定代码环境中录制请求调用的入参和返回值,并上送到存储服务。使得 CPR Replay 具备回放流量的数据。


CPRReplay:功能为接收回放服务提供的回放流量,在待测代码环境中进行回放,并将回放结果上送至对比服务,让其实现正常系统和待测系统返回结果比对差异的能力。


2)CPRConsole 组件,该组件主要录制/回放的配置管理;数据存储/数据对比等具备多种能力。主要包含三部分:


存储服务:对接收到的录制流量数据,将其持久化保存,待后续用户筛选有效流量。


回放服务:功能为将录制流量进行还原,然后对入口调用做一次流量的发起,使得 CPR Replay。


对比服务:对接收到的回放数据与录制数据进行差异比对。

4.3 CPR 处理流程

如上图所示,通过这两大组件的协同工作将稳定代码的流量自动捕获并持久化存储,然后由测试人员分析流量收藏为有效用例,对于后续待正式发布的代码,可以用有效用例来进行回放和差异比对,根据差异比对结果发现待测系统存在的问题。


该系统在处理比对时有两个与其他方案较大不同的特性:


1)对子调用 MOCK 处理:本方案采用的是记录下原始子调用内容及子调用返回内容;在系统内部完成对子调用返回的处理,不再需要实际的子系统或 mock 系统进行测试支撑。以此减小了环境、数据测试处理的难度和准确性。


2)子调用比对数据的处理:本方案采用的是比对子调用请求。例如一个 DB 操作,若请求内容是无误的,则认定请求正常;不再去比对具体的数据库存储值。这样的处理方式同样简化了测试环境搭建、测试数据准备,同时还解决了测试环境中数据共享变更可能带来的数据差异问题。

4.4 流量复制实现

在整个录制过程中,CPR 进行录制的处理流程主要是在 DefaultEventListener 类中实现。


1)DefaultEventListener 类


DefaultEventListener 是用来处理触发事件的类。不同的插件 (soa,mysql,http,redis 等) 可以自定义自己的 EventListener 来进行各自特殊的事件处理。目前插件默认使用 DefaultEventListener。


在 DefaultEventListener 中,承接的是所有事件的处理,也就是说录制/回放操作都是集中在这个类中实现的,只是根据不同的条件来区分是录制流量还是回放流量,从而判断该执行录制还是该执行回放。


当事件第一次过滤时,会进行一次初始化跟踪器。


其中 traceId 实际上是这个跟踪器的独立标识。所以无论是录制和回放都会有其独立的 traceId。


2)Before 事件处理


对应的方法是 DefaultEventListener.doBefore。

根据 traceID 判断当前流量是否为回放流量,若为回放流量则调用 processor.doMock 方法执行 Mock,执行完成后直接返回,不再执行后续操作。


若当前流量为录制流量,则基于当前获取到的信息初始化 Invocation 实例,其中会调用插件中 processor#assembleIdentity、processor#assembleRequest、processor#assembleResponse、processor#assembleThrowable 四个方法,分别处理调用标识、请求参数、返回结果、抛出异常。最后将当前事件的 Invocation 信息存放到录制缓存中。


3)Return 事件处理/ Throw 事件处理


Return/ Throw 事件的处理逻辑基本一致。

判断当前流量是否为回放流量,若为回放流量则直接返回,不执行后续操作。


若录制流量则从录制缓存中获取对应的 Invocation 实例,调用插件调用处理器的 assembleResponse/assembleThrowable 方法,并将 reponst/throwable 结果设置到 Invocation 实例中。


回调调用监听器 InvocationListener#onInvocation 方法,判断 Invocation 是入口调用还是子调用,如果是子调用则保存到录制缓存中,如果不是则调用消息投递器的 broadcastRecord 方法将录制记录序列化后上传给 cpr-console。

4.5 流量回放实现

回放流程,是由测试人员选择有效用例,在待测系统中执行回放。


回放服务接受到回放记录,对记录进行反序列化,还原到原来录制的对象,保证 classloader 与录制时候一致。CPR 在执行回放任务的过程中,首先会根据录制记录的信息,构造相同的请求,对被挂载的任务进行请求,并跟踪回放请求的处理流程,以便记录回放结果以及执行 mock 动作。整个回放过程比较复杂,主要包括以下几个个流程:


1)回放请求的处理


这里以 http 回放入口为例,RepeaterModule#repeat 方法是回放请求最初处理入口。添加 @Command 注解,使得外部可以使用 http 协议方式调用入口。

该方法在接收外部的回放请求后,对请求参数进行校验,校验通过后会将参数实例化,形成一个回放事件 RepeatEvent,发布到 EventBusInner 中被 RepeatSubscribeSupporter#onSubscribe 方法进行处理。

处理过程为首先会将 RepeatEvent 的参数进行反序列化,获取回放相关的录制记录的信息,然后通过这些信息从 prepeater-console 拉取对应的录制记录详情(RecordModel),最后用默认流量分发器 DefaultFlowDispatcher 进行分发。


2)回放任务的分发


回放任务的分发由 DefaultFlowDispatcher#dispatch 方法实现。

主要是针对回放任务的信息进行校验,校验失败会抛出错误到上层。通过校验的回放任务,则会初始化回放上下文信息,并存放到回放缓存中。根据回放任务的入口插件类型,获取对应插件的 repeater 进行回放处理。


3)回放结果执行与记录


回放任务执行、回放结果记录的实现逻辑在 AbstractRepeater#repeat 中实现。

通过 executeRepeat 方法交由各个插件的回放器实例来调用。当插件开始执行回放任务时,会根据回放上下文以及当前 repeater 的应用信息初始化回放结果记录 RepeaterModel 的实例。


初始化回放结果记录后,初始化回放线程跟踪并触发回放请求并获取回放请求返回的结果。


获取回放请求返回的结果后,停止线程跟踪,将回放结果以及当前的一些状态信息保存到回放结果记录的实例中。


最后通过调用消息投递器的 AbstractBroadcaster#broadcastRepeat 方法将回放结果记录序列化后上传到 crp-console 保存。


4)回放过程中的 Mock 处理


在回放过程中,直接在 DefaultEventListner#Before 事件处理中执行。

processor#doMock 方法,交给插件调用处理器执行 mock,并且只对子调用事件执行 mock。



public void doMock(BeforeEvent event, Boolean entrance, InvokeType type) throws ProcessControlException { /* * 获取回放上下文 */ RepeatContext context = RepeatCache.getRepeatContext(Tracer.getTraceId()); /* * mock执行条件 */ if (!skipMock(event, entrance, context) && context != null && context.getMeta().isMock()) { try { /* * 构建mock请求 */ final MockRequest request = MockRequest.builder() .argumentArray(this.assembleRequest(event)) .event(event) .identity(this.assembleIdentity(event)) .meta(context.getMeta()) .recordModel(context.getRecordModel()) .traceId(context.getTraceId()) .repeatId(context.getMeta().getRepeatId()) .index(SequenceGenerator.generate(context.getTraceId())) .type(type) .build(); /* * 执行mock动作 */
final MockResponse mr = StrategyProvider.instance().provide(context.getMeta().getStrategyType()).execute(request); /* * 处理策略推荐结果 */ switch (mr.action) { case SKIP_IMMEDIATELY: break; case THROWS_IMMEDIATELY: ProcessControlException.throwThrowsImmediately(mr.throwable); break; case RETURN_IMMEDIATELY: // if(!type.equals(InvokeType.MYSQL) &&! type.equals(InvokeType.MYBATIS)){ ProcessControlException.throwReturnImmediately(assembleMockResponse(event, mr.invocation)); // } break; default: ProcessControlException.throwThrowsImmediately(new RepeatException("invalid action")); break; }
} catch (ProcessControlException pce) { throw pce; } catch (Throwable throwable) { ProcessControlException.throwThrowsImmediately(new RepeatException("unexpected code snippet here.", throwable)); } }}
复制代码


AbstractInvocationProcessor#doMock 方法的实现,与 AbstractRepeater#repeat 相似,实际调用时是通过插件中各自的 Processor 的实例进行调用的。


当开始执行回放 mock 时,会先获取回放上下文的信息。根据回放上下文的信息,判断是否跳过 mock 的执行。当需要执行 mock 时,会根据回放上下文中的信息初始化 MockRequest 实例,通过 mock 策略计算获取 MockResponse。


根据 MockResponse 返回状态进行不同的操作:


  1. 当 MockResponse 执行结果是返回正常内容时,就会抛出一个终止当前调用操作并返回当前的返回结果,用 ProcessControlException 异常来阻挡需要 mock 的方法的实际调用。

  2. 当 MockResponse 执行结果是返回一个异常时,就会抛出一个终止当前调用操作并抛出 ProcessControlException 异常,此时需要 mock 的方法不会被真实调用。

  3. 只有当 MockResponse 返回状态是 skip 时,则不对这个调用事件做任何处理,调用会真实发生,其他任何状态都会通过抛出 ProcessControlException 异常来阻挡这个事件的实际调用。

4.6. 缺陷判定简析

1)对于读操作,我们主要关注在相同请求下正常系统和待测系统的返回结果的差异,读接口也提倡对所有对外请求进行 mock,这样回放时能保持当时的一个现场环境,保证验证的准确性。


2)对于写操作,只验证接口返回结果是不够的,需要验证它具体写入的数据是否正确。例如创建支付订单会调用 PPI 的写订单 PPI.Bill.CreatePaymentBill 接口,那么我们需要验证回放时调用 PPI 的参数和录制时调用 PPI 的参数是否一致。


3)对比默认是全对象字段逐一对比。由于录制环境和回放环境所处环境不同,有一些必然不一致的信息,例如随机数、时间,以及系统 ip 等等,这些内容系统做了默认不比对处理。系统对于子调用是默认对比所有子调用,也可以通过平台配置排除一些不关心的子调用的对比。

4.7 数据安全保护

系统录制与回放过程中基于对通讯、数据的安全保护要求,系统实现了对公司神盾安全系统的接入,使得传输过程、落地数据及日志等涉及安全要求的数据内容都符合了信息安全要求。也确保了使用者不会接触到敏感数据等限制性内容。

五、小结

本文系统的介绍了 CtripPaymentRepeater 的目标以及实现原理以及流量复制、流量回放的技术方案,希望给因频繁进行回归测试而困扰的朋友一些帮助和借鉴。


在系统成熟的前提下,可以扩展本平台进行生产数据的采集比对。当这一步实现后,可以想见回归将真正不再是一件难事,尤其是生产回归需要面对的数据污染等问题都将不再是问题。同时该平台还可演化为 MOCK 平台,这样的 MOCK 将不再依赖任何系统,只需在待测应用的服务器上增加一个 Agent 即可完成。


作者简介:

Sedro,携程资深测试工程师,专注于测试技术探索及测试工具研发。


本文转载自:携程技术(ID:ctriptech)

原文链接:干货 | 质量保障新手段,携程回归测试平台实践

2021-01-25 13:001678

评论

发布
暂无评论
发现更多内容

ShareSDK ios端 扩展功能业务设置

MobTech袤博科技

Java 开发者 产品动态

天猫商品详情数据接口 | 天猫商品数据采集 | 天猫API接口指南

tbapi

天猫商品详情数据接口 天猫API 天猫商品数据采集 天猫商品详情采集

蓝易云 - Ubuntu搭建NFS服务

百度搜索:蓝易云

VMware vCenter Server 6.7 U3u (安全更新) - ESXi 集中管理软件

sysin

vSphere vmware vcenter esxi

第三届中国 PM&PMO 前沿大会即将开幕!

新消费日报

全能数据分析工具:ableau Desktop 2019 for Mac 中文激活版

你的猪会飞吗

Mac软件 mac软件下载

LeetCode题解:1233. 删除子文件夹,排序,JavaScript,详细注释

Lee Chen

LeetCode题解:2073. 买票需要的时间,模拟,JavaScript,详细注释

Lee Chen

MobPush Android常见问题

MobTech袤博科技

开发者 产品动态

蓝易云 - Linux CentOS7 awk的反转功能

百度搜索:蓝易云

蓝易云 - Unity中pivot和center的区别

百度搜索:蓝易云

【YashanDB知识库】汇聚库23.1环境发生coredump

YashanDB

yashandb 崖山数据库 崖山DB

技术干货丨InspirePolyFoam 高级应用:发泡仿真

Altair RapidMiner

制造业 仿真 智能制造 新材料 altair

LeetCode题解:290. 单词规律,哈希表,JavaScript,详细注释

Lee Chen

【YashanDB知识库】离线升级一章22.2不支持直接升级到23.1

YashanDB

yashandb 崖山数据库 崖山DB

淘宝店铺商品API返回值分析:优化商品展示与推荐

技术冰糖葫芦

API Explorer API 编排 API 文档 pinduoduo API

淘宝天猫商品详情API:商品描述与图片的获取方法

技术冰糖葫芦

API Explorer api 货币化 API 文档 pinduoduo API

Zilliz 推出 Spark Connector:简化非结构化数据处理流程

Zilliz

程序员 AI Milvus Zilliz 向量数据库

实战教程:利用淘宝API接口批量抓取商品列表数据

tbapi

淘宝商品列表数据接口 淘宝商品数据采集 淘宝商品列表数据采集 淘宝商品列表接口 淘宝商品API

冒烟测试与宇宙飞船

FunTester

天谋科技成为中国工业大数据创新发展联盟专业委员会副主任单位

Apache IoTDB

古画新韵——李逸弘国画作品赏析

科技热闻

解密可观测行业中的语义规范 — 代码世界中的“语言艺术”

Greptime 格睿科技

数据库 可观测性 代码 系统可观测性 语义规范

蓝易云 - Linux系统gdb调试常用命令

百度搜索:蓝易云

安吉尔:净水科技的“自转”革命,守护每一滴纯净

科技热闻

淘宝商品详情数据接口| 淘宝API接口

tbapi

淘宝商品详情接口 淘宝商品API接口 淘宝API 淘宝商品详情数据

PIRF-404

EchoZhou

English

蓝易云 - wininet,winhttp,xmlhttprequest,各版本区别

百度搜索:蓝易云

《Programming from the Ground Up》阅读笔记:p75-p87

codists

assembly 编程人

数业智能心大陆:数字化心理健康的未来

心大陆多智能体

智能体 AI大模型 心理健康 数字心理

干货 | 质量保障新手段,携程回归测试平台实践_文化 & 方法_携程技术_InfoQ精选文章