由三个感叹号开启的 debug 篇章 | Java Debug 笔记

简介: !!! JUnit version 3.8 or later expected:如下所示,当我在进行单元测试时,控制台居然抛出了这么诡异的bug!三个感叹号开头此刻的我 ???异常信息如下:java.lang.ClassNotFoundException: junit.framework.ComparisonFailure那么先挖到它的源码看个究竟叭 😝在264行打个断点,然后debug运行起来通过 Alt+F8 来获取这个类加载器 都使用到了哪些类ClassLoader.getClassLoader(caller)效果如下:可以看到这里至于为啥会点开这里,

!!! JUnit version 3.8 or later expected:


如下所示,当我在进行单元测试时,控制台居然抛出了这么诡异的bug!


网络异常,图片无法展示
|


三个感叹号开头


此刻的我 ???


网络异常,图片无法展示
|


异常信息如下:


java.lang.ClassNotFoundException: junit.framework.ComparisonFailure


那么先挖到它的源码看个究竟叭 😝


在264行打个断点,然后debug运行起来


网络异常,图片无法展示
|


通过 Alt+F8 来获取这个类加载器 都使用到了哪些类


ClassLoader.getClassLoader(caller)


效果如下:可以看到这里


网络异常,图片无法展示
|


至于为啥会点开这里,主要时因为它比较突出 哈哈~


可以发现它加载了idea 插件目录 IntelliJ IDEA 2020.1\plugins\junit\lib 中的 junit-rt.jar  文件


网络异常,图片无法展示
|


犹豫了下,还是继续探究下去 哈哈


奇怪的参数


于是我就一路 debug 下来,最后看到这个东东, 运行了 JUnitStartermain 函数~


同时传递了三个变量


  • -ideVersion5


  • -junit3


  • com.java4ye.demo.A,contextLoads (类,测试方法)


如图~


网络异常,图片无法展示
|


这里我们把这个 junit-rt.jar 解压到上面的这个 junit-rt 目录,


网络异常,图片无法展示
|


IDEA 打开 很快就可以找到这个  JUnitStarter  了。


网络异常,图片无法展示
|


!!!的来源


查阅代码,发下有这么一个调用逻辑~


if (!"com.intellij.junit5.JUnit5IdeaTestRunner".equals(agentName) && !canWorkWithJUnitVersion(System.err, agentName)) {
    System.exit(-3);
}
复制代码


Soga , 这个 Process finished with exit code -3 是这么来的


canWorkWithJUnitVersion


网络异常,图片无法展示
|


junitVersionChecks


网络异常,图片无法展示
|


小结


可以发现如果代理名称 agentName  不是


com.intellij.junit5.JUnit5IdeaTestRunner


就会去 check 这个 junit 版本。 然后去加载这个


junit.framework.ComparisonFailure 类。


tipJunit5 中并没有这个类,版本 5 的架构更复杂,JUnit 5 =JUnit Platform + JUnit Jupiter + JUnit Vintage


顺带提下这个 ComparisonFailure 的作用:


当断言equals for Strings失败时抛出


如下 ε=ε=ε=( ̄▽ ̄) 居然还有邮箱 📫


网络异常,图片无法展示
|



为何会出现 Junit3 这个奇怪的参数


这里先解释下,传递的参数乎关系到这个 agentName


网络异常,图片无法展示
|


那么问题来了!


在我的 demo 中,使用的 Springboot 版本是 2.4.5 ,同时在 pom 文件中引入了


spring-boot-starter-test ,它的版本号是5.7 ,如下


网络异常,图片无法展示
|


可以看到明明使用的是 JUnit5


带着疑问来看看项目的结构是啥样子叭~


网络异常,图片无法展示
|


嘿嘿,可以发现这里 test 目录下 和 main 目录中有个 同包同名的类A

test 下的 A


package com.java4ye.demo;
//import org.junit.Test;
import org.junit.jupiter.api.Test;
public class A{
    @Test
    public void contextLoads() {
        System.out.println("hello");
    }
}
复制代码


这时我尝试着将这个 test 下的 A 重命名为 AA ,奇怪的是,它正常跑起来了,哈哈,而且确实是用的 Junit5


网络异常,图片无法展示
|


网络异常,图片无法展示
|


于是我又做了一个实验,导入 Junit4 的包,将 AA 改为 A ,继续测试,结果也是正常的


小结


使用 Junit5 时,如果测试目录 test 下的测试类和 main 目录下的同包同名,会出


现这个奇怪的参数 -Junit3 , 导致抛出异常 !!! JUnit version 3.8 or later


expected:


这里我也很好奇为啥参数变成了 -Junit3  ,可是不懂要怎么 debug 看下了,无奈作罢 🐖


插曲

java.lang.NoClassDefFoundError:


在找到这个 JUnitStarter 类时, 4ye 尝试着用命令 java JUnitStarter 去运行,


结果居然抛出了 java.lang.NoClassDefFoundError:


java JUnitStarter 命令去运行,结果居然抛出了


java.lang.NoClassDefFoundError:


网络异常,图片无法展示
|


区别


不知道小伙伴们对这个 Error 熟不熟悉 哈哈,平时看到的都是


ClassNotFoundException


这两者最大的区别就是:


一个是 Error ,一个是 Exception 哈哈


详细点点说:


ClassNotFoundException  是非运行时异常,在编译期间就检测出来可能会发生的异常,需要你 try catch


而这个  java.lang.NoClassDefFoundError: 是属于 error ,是 JVM 处理不了的错误。


这里还有一点点小细节~


就是这个原因是在 JDK11 下才显示出来的,之前用 JDK8 只有错误一行~ 小伙伴们可以自己尝试下


网络异常,图片无法展示
|


解决办法


咳咳,那这个 错误 怎么解决呢 ?


其实这个也是最原始的解决办法 哈哈


可以在上面 IDEA 中反编译出来的代码看到我们这个 JUnitStarter 是属于


package com.intellij.rt.junit; 包的 。


那么我们正确的运行方式就是跑到 com同级目录下去运行 ,如下~


网络异常,图片无法展示
|


注意这里运行时要带上包名(先不带上那三个参数试试~)


java com.intellij.rt.junit.JUnitStarter
复制代码


可以看到这里已经出现了 !!! JUnit version 3.8 or later expected


也就是我们文章最开始的那段异常信息了!


后面手动将需要的包放到这个目录下,也可以正常运行啦~


其他小实验和感悟就写在下面的总结里啦~


总结


一. 单元测试的命名要规范


二. 不要引入不同版本的单元测试包


如果项目中使用到这个 Junit5 ,此时又直接根据上面  !!! JUnit version 3.8


or later expected 这个异常,引入 Junit4 , 会出现新的异常


java.lang.Exception: No runnable methods ,此时需要你将 @Test 注解修改为 junit4 的版本~ 🐷


三. 扩展包解惑


比如我在 pom 文件中引入了这个  spring-boot-starter-test ,此时它会帮我导入


相应版本junit 包 ,而我也不知道它引入了什么版本的测试包,这时可以在 IDEA


的扩展包中搜索,就可以查找到 junit 的版本了


网络异常,图片无法展示
|


四. junit3 是使用继承的方式, Junit4 开始才使用注解的形式


所以,如果你想试试继承的写法的话✍,可以试试 哈哈


五. 单元测试很重要,主要是为了证明你的逻辑在这个测试范围是对的😝




目录
相关文章
|
1月前
|
设计模式 Java
Java基础—笔记—多态、final、抽象类、接口篇
该文介绍了编程中的多态、final和抽象类、接口相关概念。多态允许子类重写父类方法,通过父类引用调用子类方法,实现解耦和提高代码灵活性,但也可能导致无法使用子类特有功能,需通过强制类型转换解决。final用于修饰不可变的类、方法或变量,防止继承、重写和多次赋值。抽象类是一种包含抽象方法的类,用于强制子类重写特定方法,实现多态,适用于模板方法设计模式,解决代码重复问题。
20 0
|
1月前
|
Java
Java基础—笔记—static篇
`static`关键字用于声明静态变量和方法,在类加载时初始化,只有一份共享内存。静态变量可通过类名或对象访问,但推荐使用类名。静态方法无`this`,不能访问实例成员,常用于工具类。静态代码块在类加载时执行一次,用于初始化静态成员。
12 0
|
1月前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
16 0
|
2月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--希尔排序
数据结构与算法(Java篇)笔记--希尔排序
|
3月前
|
监控 负载均衡 Dubbo
|
1月前
|
Java API
Java基础—笔记—内部类、枚举、泛型篇
本文介绍了Java编程中的内部类、枚举和泛型概念。匿名内部类用于简化类的创建,常作为方法参数,其原理是生成一个隐含的子类。枚举用于表示有限的固定数量的值,常用于系统配置或switch语句中。泛型则用来在编译时增强类型安全性,接收特定数据类型,包括泛型类、泛型接口和泛型方法。
12 0
|
2月前
|
Java
Java8 Stream流 使用笔记
Java8 Stream流 使用笔记
28 1
|
2月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--快速排序
数据结构与算法(Java篇)笔记--快速排序
|
2月前
|
机器学习/深度学习 算法 搜索推荐
数据结构与算法(Java篇)笔记--归并排序
数据结构与算法(Java篇)笔记--归并排序
|
2月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--插入排序
数据结构与算法(Java篇)笔记--插入排序