在平时的测试、研发工作当中经常会遇到一些问题:
为解决以上测试工作中的痛点、盲点,我们基于现有的自动化测试平台、覆盖率报告服务、调用链监控平台、用例管理平台和统一项目管理平台开发了精准测试功能,作为应用上线质量的参考维度之一。
精准测试作为传统测试的补充,在测试进行中采集测试用例与代码的关系,建立测试用例代码关系库,从而可实现测试用例到代码的双向追溯能力。基于用例代码关系库我们可以:
使测试用例到代码的关系可视化,帮助测试或开发同学确定回归范围。
与覆盖率报告相结合,帮助测试和开发同学有针对性的完成测试和补充用例。
与监控程序间链路追踪系统结合,分析变动代码对其他应用的影响范围,避免出现跨应用的用例回归盲区。
与标记高危代码,高危接口功能相结合,进行有针对性的回归测试。
精准测试的实现主要分三步:
在用例执行过程中采集用例和代码的映射关系,建立测试用例代码关系库。
获取变动代码信息。
通过变动代码,查询测试用例代码关系库,推荐相关测试用例。
精准测试基本架构图如下图所示
架构和原理
在执行测试用例的过程中,我们通过一些技术手段采集全量代码覆盖率、增量代码覆盖率、测试用例代码关系来为精准推荐建立数据基础。
通过对jacoco的二次开发,使jacoco具有获取增量代码覆盖率的能力,同时可以检测到本次变动的代码所属的类和方法并记录下来。
在使用自动化测试平台执行测试用例时会在请求header中添加测试用例id字段,帮助我们将整个测试用例调用链串联起来。同时对skywalking进行二次开发,使用拦截器获取request的header中传进来的测试用例id并放入到上下文中(ContextManager.getRuntimeContext())。通过这样的方式我们可以将分散在各个地方的信息整合起来,如测试用例代码关系、程序内方法调用链等,下边是一些核心代码
/**
* 通过skywalking提供的tomcat插件来增强处理逻辑
*/
public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor {
private static final String TEST_CASE_ID = "TEST_CASE_ID";
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
Request request = (Request) allArguments[0];
//从请求头中获取测试用例id
String testCaseId = request.getHeader(TEST_CASE_ID);
//Skywalking为我们提供了ContextManager.getRuntimeContext(),它使用ThreadLocal的方式来保存获取上下文信息
ContextManager.getRuntimeContext().put(TEST_CASE_ID, testCaseId);
}
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
clearThird();
return ret;
}
private void clearThird(){
ThirdReq.clear();
}
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
AbstractSpan span = ContextManager.activeSpan();
span.log(t);
}
}
同时我们还采用了javaagent技术对方法进行增强。具体方式是实现ClassFileTransformer接口来改变运行时字节码,jvm加载这个类之前会调用ClassFileTransformer的transform方法,那么我们就可以在transform方法中通过自定义ClassVisitor子类的方式进行字节码修改对方法进行功能增强,下边是简单的示例代码:
public class MethodTraceTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
if (className != null && className.length() > 0) {
className = className.replaceAll("/", ".");
//读取类的字节码流
ClassReader reader = new ClassReader(classfileBuffer);
//创建操作字节流值对象
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
//接受一个ClassVisitor子类进行字节码修改,对方法进行增强
reader.accept(new MtraceClassVisitor(writer, className), 8);
//返回修改后的字节码流
return writer.toByteArray();
}
} catch (Throwable e) {
LOGGER.error("", e);
}
return classfileBuffer;
}
}
当然我们不可能对所有的方法进行增强,期间需要做降噪处理,排除一些不必要的类,以免造成性能浪费。
//方法增强
public class MtraceClassVisitor extends ClassVisitor {
private final String className;
//扫描到每个方法都会调用
public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
{
if (!name.equals("<init>") && mv != null) {
mv = new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
public void onMethodEnter() {
//向栈中压入类名称
this.visitLdcInsn(className);
//向栈中压入方法名
this.visitLdcInsn(name);
//调用记录className,methodname到上下文中的方法(通过ContextManager.getRuntimeContext())
this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/asm/Trace", "rc", "(Ljava/lang/String;Ljava/lang/String;)V", false);
}
public void onMethodExit(int opcode) {
}
};
}
}
return mv;
}
}
我们可以通过上下文获取到测试用例调用过的每个类和方法的信息,最后发送到消息队列中,由信息收集服务消费这些数据并进行一系列的分析降噪处理,最后存入到数据库中。
在覆盖率报告中同时增加了方法级别的用例推荐,通过该功可以带来以下好处:
通过变动的代码,推荐出本次代码变更影响到的测试用例,这里还显示了相关类和方法的覆盖情况;同时提供了执行测试用例的功能,我们可以通过执行未被覆盖方法推荐到的测试用例,完成对变更代码的测试,如果执行后没有完全覆盖,那么就考虑补充测试用例了。
一些场景中,一应用变动会使多个应用受到影响,可能出现测试用例回归盲区,如下图所示,当D项目发生变动,测试同学会针对D项目进行一系列的回归测试,保证D项目的功能正常,但是对于存在上下游调用关系的其他应用会出现没有回归测试或者回归测试不充分的风险,跨业务线的用例推荐就可以帮助我们发现这些处于测试盲区的测试用例。
如下图所示,我们会在测试用例管理平台中的各个项目下的集成回归测试目录中推荐出本次代码变动影响到的本项目和其他项目的测试用例,或者是其他项目下应用的变动影响到的本项目下的用例。这些推荐用例的执行状态共享,每个团队都只需关注本项目下推荐的用例即可,当该发布日下所有的团队都完成了推荐用例的回归测试,那么本次项目变动所影响到的所有相关用例就完成了回归测试。
精准测试作为传统测试的补充,通过测试过程积累测试用例代码关系库,帮助确定代码接口变更后的影响范围、提高代码覆盖率、发现跨应用回归用例等,提高测试质量有着一定的意义。同时,也有一些不足,比如当修改了较为底层代码或者公共的方法后,可能会推荐出大量的测试用例,在这种情况下怎样缩小推荐范围减少重复测试,也是需要改进的地方。后续我们会继续改进精准测试功能,使之成为全流程回归测试的一项指标,为测试和开发团队提高效率和测试质量。
alger,信也科技后端研发专家,从事过自动化运维平台、用例管理平台、分布式压测平台、精准测试平台的开发,目前主要专注精准测试相关功能开发
招聘信息
Java、大数据、前端、测试等各种技术岗位热招中,欢迎扫码了解~
更多福利请关注官方订阅号“拍码场”
好内容不要独享!快告诉小伙伴们吧!