写点什么

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

  • 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:001723

评论

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

完美!京东资深架构师爆肝纯手打700页架构进阶宝典我粉了

做梦都在改BUG

Java 架构 亿级流量

如何编写一个健壮的 npm 包 | 京东云技术团队

京东科技开发者

npm npm chalk-next 企业号 5 月 PK 榜

AIGC背后的技术分析 | 图像风格迁移

TiAmo

AIGC 图像风格迁移

MacOS高效iOS代码编写工具|AppCode 2023 激活版v2023.2 兼容M1/M2/intel

Rose

AppCode中文 AppCode 2023破解 AppCode密钥 iOS/macOS开发

阿里逆天级调优方案,内部这套Java性能调优实战宝典,堪称教科书

做梦都在改BUG

Java 性能优化 JVM 性能调优

选择小程序第三方开发框架,你需要知道这些

没有用户名丶

OpenHarmony创新赛 | 赛事宣讲会日期重磅官宣!为你带来超详尽的赛事攻略

OpenHarmony开发者

OpenHarmony

运行Adobe软件遇到“Adobe app is not available”怎么解决

Rose

Adobe软件

字节Java全能手册火了!多线程/网络/性能调优/框架啥都有

做梦都在改BUG

Java 微服务 Spring Cloud socket

易观千帆 | 2023年4月证券APP月活跃用户规模盘点

易观分析

证券

Nautilus Chain开启全球行,普及Layer3概念加速其采用

西柚子

首站中科院!百度商业AI技术创新大赛开启巡回宣讲

百度Geek说

人工智能 百度 企业号 5 月 PK 榜

Photoshop2023硬件要求,PS2023最低配置要求

Rose

PS2023 PS2023系统要求 Photoshop2023硬件要求 神经滤镜Neural Filters

顺丰科技携手飞桨自研“智能外呼机器人”,为客户打造优质服务体验

飞桨PaddlePaddle

nlp 语音识别 百度飞桨

如何做好需求管理?华为云需求管理利器CodeArts Req解读

华为云PaaS服务小智

云计算 产品经理 需求管理 华为云

中国互联网广告市场年度分析2023

易观分析

互联网 广告

ConcurrentHashMap是如何实现的?

javacn.site

恭喜又一名小伙伴上岸大厂

冰河

程序员 互联网 架构师 大厂Offer 上岸大厂

2D CAD设计软件CADintosh X 激活版

真大的脸盆

Mac Mac 软件 CAD绘图 CAD设计 cad

百度营销首创CPQL模式,破解汽车营销线索难题

Geek_2d6073

TDengine 成功“晋级” Percona Live 2023 银牌赞助商,开发者驻足关注

爱倒腾的程序员

从传统 IT 容灾转向“全栈云容灾”|什么是更适合政企的云

云布道师

阿里云

火山引擎DataTester:如何使用A/B测试优化全域营销效果

字节跳动数据平台

AB testing实战 ab测试 A/B测试

优质高效!阿里甩出SpringBoot巅峰之作,进阶不二之选

做梦都在改BUG

Java Spring Boot 框架

iMovie for Mac(专业视频剪辑工具) v10.3.6中文版

Rose

Mac视频剪辑软件 iMovie中文版 iMovie下载 iMovie Mac破解版

名不虚传!字节技术官甩出的"保姆级"数据结构与算法笔记太香了

做梦都在改BUG

Java 数据结构 算法 LeetCode

Github标星67.9k的微服务架构以及架构设计模式笔记我粉了

做梦都在改BUG

Java 架构 微服务 设计模式

如何使用golang实现桥接模式

Jack

C语言编程—循环语句

芯动大师

ps神经滤镜是干什么的,神经滤镜的功能和作用

Rose

Photoshop 2023下载 ps神经滤镜 Neural Filters滤镜

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