背景
对于一个初上线的简略服务,只需通过惯例的自动化测试加上人工即可解决,但咱们线上外围的业务零碎往往比较复杂,通常也会频繁的需要迭代,如何保障被批改后的零碎原有业务的正确性就比拟重要。惯例的自动化测试须要投入大量的人力资源,筹备测试数据、脚本等,并且覆盖率通常也不高,难以满足要求。
为了保障一个线上零碎的稳定性,开发和测试人员都面临不少的挑战:
- 开发实现后难以疾速本地验证,发现初步的问题,容易陷入提测 → 发现 bug → fix → 提测的循环
- 筹备测试数据、自动化脚本编写和保护须要大量的人力老本,而且难以保障覆盖率
- 写服务难于验证,而且测试会产生脏数据,例如咱们的外围交易系统,可能会往数据库、音讯队列、Redis 等写入数据,这部分数据往往比拟难以验证,测试产生的数据也难于清理
- 线上问题难以本地复现,排查艰难
AREX 介绍
AREX 通过复制线上实在流量到测试环境进行自动化回归测试,解决回归测试的难题。
AREX 采纳 java 的 instrument 实现了无代码侵入的数据采集和自动化 Mock,智能的 Mock 机制使测试运行代码集中在待测利用,不会产生真正的内部交互(DB 的写入、其它服务的调用),也完满反对了写接口的测试(如外围交易系统、库存零碎等)。
原理示例如下:
咱们假设生产环境利用会失常的响应用户的申请,通过 aop 的形式将申请入参及返回后果以及执行过程中的一些快照数据例如拜访数据库的入参和返回后果、拜访近程服务器的入参及后果保留下来。而后将快照数据发送给测试机器(代码发生变化的机器)实现一次回放过程。通过将落库数据、调用后盾申请的数据以及返回后果和线上实在申请产生时的数据进行比照,发现其中的差别,从而辨认被测试零碎的问题。
- xxxTestCase:采集下来的数据在回放时作为测试 CASE
- xxxMock:在回放时会应用采集的数据进行 Mock,代替真正的数据拜访
- xxxExpect 和 xxxReal:在测试完结后会验证对应的数据,发现代码中潜藏的隐患
技术原理:
在 JDK1.5 中,Java 引入了 java.lang.Instrument 包,该包提供了一些工具帮忙开发人员在 Java 程序运行时,动静批改零碎中的 Class,以此实现对原类的性能加强。当初有很多工具都是基于此技术实现的,例如阿里开源的 arthas、监控工具 SkyWalking 等,AREX 的数据采集和主动 Mock 也是基于此技术实现。
平台劣势
- 低成本:
- 无代码侵入,根本无接入老本
- 无需编写测试用例,海量的线上申请也能保障高覆盖率
- 插桩代码足够简略,性能损耗低
- 反对写验证:反对数据库、音讯队列、Redis 数据的验证,甚至反对验证运行时的内存数据,并且测试时不会产生脏数据。
- 测试 CASE 高稳固:反对各种支流技术框架的主动数据采集和 Mock,参见:arex_java,并且反对了本地工夫、缓存,在回放时精准还原生产执行时的数据环境。
- 疾速复现线上问题:反对一键本地调试,能够疾速本地调试线上问题
- 平安稳固,代码隔离,也实现了衰弱治理,在零碎忙碌时会智能升高或敞开数据采集频率
- 良好的功能测试反对,反对测试脚本,也可对采集的数据进行简略的编辑实现固定测试观点的测试,防止大量的测试数据筹备
技术实现
咱们采纳了 ByteBuddy 库实现的字节码批改,在实现过程中也遇到了各式的挑战。
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);
}
}
而后代码润饰各种线程池,把 Runnable/Callable 替换掉,而 Wrapper 外部通过 TraceTransmitter 保障 Trace 的正确传递。
2. ForkJoinPool
CompletableFuture、数据集的并行 stream 解决默认应用的是 ForkJoinPool,重并行计算的利用也常常采纳,这个和惯例的线程池实现有较大的区别,须要独自解决,咱们对 ForkJoinPool 的工作单元 ForkJoinTask 进行了润饰,这个类实现比较复杂,难于像 Runnable 那样简略解决,而且为了不毁坏原有的类构造(Agent on attach 形式也不反对批改),没有在这个类上增加字段实现数据直达,而是采纳了一个 WeakCache 做数据缓冲,能够保障工作生成和执行线程之间的 Trace 传递。
3. 异步
Java 生态中存在很多异步框架(Reactor、RxJava etc),也有很多类库提供了异步实现,如 lettuce 就提供了同 / 异步拜访 Redis 的形式。不同的场景往往须要不同的解决办法。以 Apache AsyncClient 为例,是以固定运行的线程监听响应,并发动 Callback,咱们要保障调用、监听、回调整个流程中多个跨线程的 Trace 传递,具体实现能够参见 Apache AsyncClient。
版本治理
风行的组件往往存在多个版本同时在不同的零碎中应用,不同的版本实现形式差异可能很大,甚至不兼容,AREX 中也有提供多个版本的反对(如 Jedis),咱们就要保障能按正确的版本进行字节码注入,防止运行谬误。字节码注入是在类加载时进行的,这样咱们就必须在这些类加载前辨认出利用依赖的组件版本,从而在类加载时进行版本的匹配,保障正确的代码注入。
工夫治理
很多业务零碎是工夫敏感的,不同的工夫拜访往往会返回不同的后果,因而咱们也实现了工夫的 Mock 性能。因为回放是并行执行的,批改测试机器的机器工夫是不适合的(而且很多服务器也不能批改以后工夫),所以还是在代码层面上实现的工夫的 Mock。在数据采集时,咱们针对每个用例记录了以后工夫,在回放时,会对 System.currentTimeMills(Java 的很多工夫底层都是通过这个办法实现)办法进行代理,而后计算屡次拜访的时间差(System.nanoTime 实现)来保障工夫的准确性,不过依然可能存在毫秒级的差距。
本地缓存
业务利用中可能应用了各式的缓存来晋升运行时的性能,不同的环境这些数据可能存在较大的差别(多个环境的数据同步往往是一个比较复杂的过程),这些数据差别可能会导致齐全不同的执行后果,为了防止这个问题,AREX 也反对了本地缓存数据的采集和 Mock 性能,只须要进行一些简略的配置,即可实现数据的主动采集和 Mock,当然这个性能也可反对各种内存数据的 Mock 性能。
代码隔离、互通
为了零碎的稳定性,AREX agent 的框架代码是在一个独立的 Class loader 中加载,和利用代码并不互通,为了保障注入的代码能够正确在运行时被拜访,咱们也对 ClassLoader 进行了简略的润饰,保障运行时的代码会被正确的 ClassLoader 加载(想想 SpringBoot 的 LaunchedURLClassLoader)。
我的项目现状
AREX 我的项目在携程机票外部发动,通过一年多的倒退,逐步推广到酒店、游览、商旅、平台等多个部门,而且携程外部的多个团队曾经用 AREX 代替了其它自动化工具和手工测试来进行回归测试。目前咱们已将我的项目开源:AREX。鉴于各种技术框架、类库茫茫多,反对的必然还不太够,欢送各位有志之士独特来实现,让咱们独特结构一个简略、高效的研发测试形式(试想针对每次迭代,代码提交后测试主动执行,并反馈测试报告,开发和测试人员只须要关注在新业务的研发、验证上即可,脱离那些繁琐的数据和脚本,以及测试了无数遍的旧性能)。
AREX 文档:http://arextest.com/zh-Hans/docs/intro/
AREX 官网:http://arextest.com/
AREX GitHub:https://github.com/arextest
AREX 官网 QQ 交换群:656108079