测试覆盖率 JVM 字节码测试运用 - 远程调试、测试覆盖、影子数据库

uniquetruth · June 25, 2022 · Last by uniquetruth replied at May 15, 2023 · 42885 hits
本帖已被设为精华帖!

一直想找一个技术社区开源一个自己个人的项目,希望能被更多人看到、使用这个东西,在测试上帮助到大家。

简介

一个专为 JVM 系语言 web 应用设计的,专注于集成测试阶段的后端测试工具。本质功能是监控代码执行,做远程调试使用。比如可以让你实时的了解到在前端点击某个按钮后,后端执行的代码细节,包括每一个方法的名称、参数返回值、执行的代码行号,调用的 sql 语句等信息。

当然可方便的扩展功能,实现测试覆盖率统计、影子数据库等实用功能。

基础使用方式介绍

项目地址:https://github.com/uniquetruth/remote-debug-agent

项目使用 gradle 构建,下载源码后,使用gradle agentTest命令,可编译出一个 java 探针,并且与所有需要的二进制文件出现在 build/lib 目录下。之后就可以将所有 jar 包放到服务器的任意目录中,然后将-javaagent:${你的目录}/remote-debug-agent.jar=includes=com.foo.bar,apiport=8098配置到 web 应用启动参数的 java_opts 中(例如使用 tomcat 的话,可修改 catalina.sh 来添加该参数)。被测应用启动后,探针会启动一个内置的 jetty 服务器,并在 8098 端口上提供一组 api 供测试使用。

假设你的被测应用在前端有一个按钮,点击按钮后会调用到后端 com.foo.bar.MyClass 类中的某个方法,那么在测试这个按钮前,可先发送一个请求http://ip:8098/trace/start(用 jmeter、curl 甚至浏览器发都行,只要与测试操作源自同一台机器即可),之后点击按钮后,再发送请求http://ip:8098/trace/list,探针即会返回刚才执行代码的细节。你可以看到类似这样的数据

[{
    "method": "java.lang.String com.foo.bar.MyClass.handle()",  //执行的方法签名
    "coverage": "[11,13][16,16]",  //执行了哪几行代码
    "cost time": 2,  //执行耗时
    "calls": [{  //该方法调用的底层方法
        "coverage": "[21,21][24,24]",
        "cost time": 1,
        "method": "boolean foo.bar.MyClass.largeThanHalf(double)",
        "parameters": ["0.24444334899195885"],
        "return value": "0"
    }],
    "return value": "random number( 0.24444334899195885 ) is little than half",  //该方法的返回值
    "sql": "select 1 from dual"  //该方法执行的sql语句
}]

以上是一个远程调试的示例,应用场景应该是集成测试阶段灰盒测试,当然白盒、黑盒,甚至开发调试都可以用。另外也提供 dump 代码行覆盖情况的接口,用过 jacoco 的应该比较熟悉,利用这些数据可以做测试覆盖率的统计。

工具特性

  • 线程隔离: 代码执行情况是分线程记录的,且能通过一些信息标识调用者身份,因此可在集成环境中多人同时使用,互不影响
  • 自然染色: 调用者身份使用其发起的请求中的天然信息进行染色,无需给测试人员增加额外的客户端
  • 调用链传递: 调用者身份信息可在多个都使用了探针的应用间传递(如果这些应用间使用 http 协议通信的话),也就是说非常适合微服务架构使用(特意支持了 Feign-Hystrix 组件)
  • Servlet 框架支持: 工具支持大部分使用 HttpServlet 的框架或中间件,比如 spring-web、tomcat 等,也提供了非常方便的接口扩展支持其它同类框架
  • 支持 Struts2
  • 支持 Dubbo
  • 支持 Spring RabbitMQ
  • 支持 JVM 系语言: 探针工作在字节码层,所以不仅支持 Java,也支持 Groovy、Scala 等语言(特意支持了 Play 框架)
  • SQL 语句监控: 探针默认支持 oracle 和 mysql 数据库,也提供了非常便利的接口扩展支持其它关系型数据库
  • 支持热插拔: 不用改应用的启动文件也可使用,对线上环境临时使用提供了可能

更多功能持续开发完善中

Github 空间中我补充了一个 wiki 页面,比这里介绍得稍微详细一些,有兴趣欢迎前往。另外以后会争取时间在社区新写一个帖子,详细说一下大家比较关心的测试覆盖率。

可能没有特别适合的标签,就选了测试覆盖率这个, 如果有对测试覆盖率或者影子数据库等技术感兴趣的朋友也欢迎交流。
个人 Github 空间也有其它更多开源分享欢迎参观,近期也在寻找合适的工作机会,欢迎勾搭😆

共收到 19 条回复 时间 点赞

有几个疑问:
1、基于什么使用场景开发的这个工具?
2、跟 jacoco 的 javaagent 有哪些差异?
3、为什么要用这个工具而不直接用 jacoco?

不错的工具,看样子主要针对开发调试,用于优化服务器性能。我们目前的项目使用 skynet(c+lua),服务器就是实现了类似系统,功能更加完善,再配合测试自动化工具,尽早暴露服务器性能问题。

挺不错的工具,思路上我觉得很认可,通过工具协助大家熟悉代码细节,挺不错的,而且解决了 jacoco 无法区分调用者,以及远程调试加断点会影响其他人使用的问题。

有几个疑问,想确认下:
1、标题提到有 影子数据库 ,这个正文好像没见到,具体大概是怎么用,原理是怎样的?
2、目前提供的数据,如果不进行二次加工(比如结合代码编辑器着色),其实用起来体验会比较差。楼主有计划后续进一步完善查看数据这部分体验么?
3、楼主在实际项目中应该有进行实践落地吧,后续是否可以分享下这方面的一些案例,方便大家学习下?

陈恒捷 将本帖设为了精华贴 27 Jun 23:30

感谢回复。

jacoco 主要适用于单元测试阶段,我这个工具主要用于集成测试阶段,这是最大的区别。具体来说 jacoco 只记录 “jvm 里运行了什么”,而我的工具记录的是 “谁在 jvm 里运行了什么”。不管你是开发还是测试,只要在集成阶段需要知道这个信息的话,就适合用本工具。

另外一些小的区别:jacoco 只记录代码运行的痕迹,本工具会记录更多的信息以助于调试,比如方法运行时长,参数和返回值,sql 语句等。对于测试覆盖率的话,因为我会 dump 类的行号表下来,所以生成覆盖率时不需要 class 文件,jacoco 我记得是需要的。另外我为方便调试提供的 api,jacoco 也是没有的。

最后 jacoco 是个非常好的工具,我从它的源代码里学到了很多,当然最终是为了突破它的一些局限性才做出的这个工具。

陈恒捷 回复

感谢大佬点评。

1、影子数据库目前是我的一个设想,还未实现。大概就是对 jdbc 驱动进行注入,将一些 dml 语句作用到影子表中,保护原表数据,这样可以用生产环境硬件的算力来做性能测试。其实影子数据库跟测试覆盖率一样,只是可以用这个 agent 做的更多的事情中的一个吧。原谅我初来乍到,不是很清楚应该把这个帖子放哪个分类里,就选了个测试覆盖率。

2、代码着色确实我在实际项目中有运用,但是这个功能跟 javaagent 是完全解耦的,就没有放在一起展示。其实从我给出的 json 例子应该能看出,只要再能获得被测系统源代码,并且辅以一些算法的话,做出代码染色效果来应该不难。不过实际项目的经验告诉我,这个实现起来跟具体源代码的管理方式有较大的关系,比如我们用的是 svn,那我就得用 svn 的客户端工具写一些接口交互算法,另外某些项目有多个集成分支,也得写算法去获得与发到集成环境的 class 版本对应的源代码。感觉都是非常定制化的操作,目前我也暂时没办法整理出一个可独立开源出来的模块,所以这里就没有细说。

3、接 2,确实可能后续我再整理一下,贴个思路和效果图出来比较好

uniquetruth 回复

OK,理解。确实要做完整平台还有比较多配套的东西,而且不见得通用。

建议可以先按 3 的,分享下案例,这样后续大家使用的时候可以少走弯路。

怎么标识调用者身份的呀?方便介绍一下吗

Ouroboros 回复

默认是用的客户端 IP 作为身份标识。

以 servlet 架构的应用为例,探针会注入一些逻辑到 HttpServlet.service 方法内,在方法进入时从 HttpServletRequest 对象中提取客户端 IP,然后将其与当前线程绑定,在方法退出前解绑,这样在一个线程有绑定身份的期间,其执行的代码就能记录到该身份下。

因此在测试客户端能固定 IP 的环境下是比较好用的,如果不能固定 IP 的话,也可以用请求 header 中的信息标识调用者身份,需要继承我准备的一个接口做一些开发,详细可以看 github 里的介绍。

uniquetruth 回复

感谢分享!

是否可以统计一段时间内不同接口请求覆盖的代码,比如一段时间内有请求 A.B,C....,这些请求的 Header 里有 id 标识, 可以分别统计出 A 执行了哪些方法,B 执行了哪些方法吗?

felix1982 回复

本工具从设计上来说,是以调用者为记录的粒度的,不是以某一个请求为粒度,所以可能无法以你描述的视角来做这个事情。或者说我的工具侧重的是调试或测试,你说的场景有点侧重于运行监控。

当然从我的视角出发也可以实现你所说的效果。前面我介绍得有点简单,实际上探针提供了一组灵活的 api 供使用。如果访问http://ip:8098/trace/地址可以看到我实际提供了 start、stop、list、clean 4 个 api,你可以先调用 start 接口,然后发送请求 A,然后调用 list 得到 A 执行的方法,之后调用 clean 接口,可清空你这个调用者之前记录的数据,再发送请求 B,再调 list 即可得到 B 执行的方法。

我应该写个类似 wiki 之类的东西的,但是一直没找到时间。我以后会慢慢补充文档的。

试着部署了一下,缺少类 Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.ClassVisitor

1、请确保将 build/libs 下的所有 jar 都放到了你指定的目录中,除了 remote-debug-agent.jar 外,应该还有十几个 jar,你所述的 Exception 所描述的类是在 asm 开头的几个 jar 包里。

2、可以描述一下你的环境,或者贴出完整的报错吗(到我的 github 提 issue 也行)?有多种情况可能造成 ClassNotFoundException,可能跟 jvm 本身的类加载规则有关。比如一些使用-jar 参数启动的情况下,或者环境不使用 jdk 自带的 AppClassLoader 时(比如 Scala)。可以试一下将 remote-debug-agent.jar 加入到 bootclasspath 中(java 命令行带上参数 -Xbootclasspath/a:${你的目录}/remote-debug-agent.jar)

这篇文章确实写得比较简陋😅 ,近期我也打算写一下更详细的使用说明,或者 wiki。欢迎持续关注本工具。

uniquetruth 回复

可能是部署的有问题,有时间我再试试

收藏一下,改天体验一把

Author only

体验了,非常不错的一款工具,对于日常问题排查非常有用。includes 中对于前缀定义尽量精准,对于调用链较长的应用,前缀限制过于宽松,会 hang 住功能,我猜应该是生成 trace 太多导致。

体验了下,在多个项目里使用;对于有多个子 module 的项目中,使用时 /trace/start 等 API,访问都是 404,这种情况如何排查?

uniquetruth #21 · May 15, 2023 Author
aligeduo00 回复

感谢使用,github 有邮件通知,有问题欢迎到空间里提 issue,一般都会看的

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up