其他
开源 | AREX-携程无代码侵入的流量回放实践
作者简介
一、背景
对于一个初上线的简单服务,只需通过常规的自动化测试加上人工即可解决,但我们线上核心的业务系统往往比较复杂,通常也会频繁的需求迭代,如何保证被修改后的系统原有业务的正确性就比较重要。常规的自动化测试需要投入大量的人力资源,准备测试数据、脚本等,并且覆盖率通常也不高,难以满足要求。
为了保证一个线上系统的稳定性,开发和测试人员都面临不少的挑战:
开发完成后难以快速本地验证,发现初步的问题,容易陷入提测->发现bug->fix->提测的循环
准备测试数据、自动化脚本编写和维护需要大量的人力成本,而且难以保证覆盖率
写服务难于验证,而且测试会产生脏数据,例如我们的核心交易系统,可能会往数据库、消息队列、Redis等写入数据,这部分数据往往比较难以验证,测试产生的数据也难于清理
线上问题难以本地复现,排查困难
二、AREX介绍
AREX通过复制线上真实流量到测试环境进行自动化回归测试,解决回归测试的难题。
AREX采用java的instrument实现了无代码侵入的数据采集和自动化Mock,智能的Mock机制使测试运行代码集中在待测应用,不会产生真正的外部交互(DB的写入、其它服务的调用),也完美支持了写接口的测试(如核心交易系统、库存系统等)。
原理示例如下:
我们假定生产环境应用会正常的响应用户的请求,通过aop的方式将请求入参及返回结果以及执行过程中的一些快照数据例如访问数据库的入参和返回结果、访问远程服务器的入参及结果保存下来。然后将快照数据发送给测试机器(代码发生变化的机器)完成一次回放过程。通过将落库数据、调用后台请求的数据以及返回结果和线上真实请求发生时的数据进行对比,发现其中的差异,从而识别被测试系统的问题。
xxxTestCase: 采集下来的数据在回放时做为测试CASE
xxxMock:在回放时会使用采集的数据进行Mock,代替真正的数据访问
xxxExpect和xxxReal:在测试结束后会验证对应的数据,发现代码中潜藏的隐患
技术原理:
三、平台优势
1)低成本
无代码侵入,基本无接入成本
无需编写测试用例,海量的线上请求也能保证高覆盖率
插桩代码足够简单,性能损耗低
2)支持写验证,支持数据库、消息队列、Redis数据的验证,甚至支持验证运行时的内存数据,并且测试时不会产生脏数据。
3)测试CASE高稳定,支持各种主流技术框架的自动数据采集和Mock,参见:arex_java ,并且支持了本地时间、缓存,在回放时精准还原生产执行时的数据环境。
4)快速线上问题复现,支持一键本地调试,可以快速本地调试线上问题
5)安全稳定,代码隔离,也实现了健康管理,在系统繁忙时会智能降低或关闭数据采集频率
6)良好的功能测试支持,支持测试脚本,也可对采集的数据进行简单的编辑实现固定测试观点的测试,避免大量的测试数据准备
四、技术实现
我们采用了ByteBuddy 库实现的字节码修改,在实现过程中也遇到了各式的挑战。
4.1 Trace传递
AREX在进行数据采集时,同一个请求,会采集下来多条数据(Request/Response、其它服务调用的请求响应等),我们需要把这些数据串联起来,这样才能完整的做为一个测试用例。而我们的应用往往采用了异步框架,也大量的用到了多线程等,这给数据的串联带来很大的困难。
1)Java Executors
Java和各种框架里提供了众多的线程池实现,我们要保证Trace数据能正确的跨线程传递,首先我们修饰了Runnable/Callable, 如下:
public class RunnableWrapper implements Runnable {
private final Runnable runnable;
private final TraceTransmitter traceTransmitter;
private RunnableWrapper(Runnable runnable) {
this.runnable = runnable;
this.traceTransmitter = TraceTransmitter.create();
}
@Override
public void run() {
try (TraceTransmitter tm = traceTransmitter.transmit()) {
runnable.run();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RunnableWrapper that = (RunnableWrapper) o;
return runnable.equals(that.runnable);
}
@Override
public int hashCode() {
return runnable.hashCode();
}
@Override
public String toString() {
return this.getClass().getName() + " - " + runnable.toString();
}
public static Runnable get(Runnable runnable) {
if (null == runnable || TraceContextManager.get() == null) {
return runnable;
}
if (runnable instanceof RunnableWrapper) {
return runnable;
}
return new RunnableWrapper(runnable);
}
}
携程机票BDD UI Testing框架 - Flybirds
携程机票前端安卓虚拟机测试集群建设实践
质量保障新手段,携程回归测试平台实践
应用SQL性能风险识别与预警,携程金融支付AppTrace落地实践
“携程技术”公众号
分享,交流,成长