为什么在junit中进行多线程测试的时候,输出结果与预期结果不一致

Junit单元测试不支持多线程

1. “缘”起

在回顾synchronized的用法时,写了一个小程序,就是使用synchronized修饰一个代码块。核心代码如下所示:

然后,使用Junit进行单元测试,测试代码如下:

运行结果如下:

显然,这和预期的结果是不一样的。然后,我又在java的main方法里写了同样的测试语句,运行结果如下:

那么,为什么使用Junit运行出来的结果和main方法里面运行的结果不一样呢?我试着进行调试,中间具体的代码也没太细看,就关注了一下结果,发现有时候Junit也能输出完整的预期结果,这是为什么呢?

2. 浅知

上网搜查了一下,看到的解释是Junit单元测试不支持多线程。很多都说是通过看TestRunner源码知道,测试主程序结束之后,会调用System.exit()方法关闭程序,结束当前正在运行的虚拟机。群里的伙伴说,没有守护线程,应该是说Junit没有守护线程吧。但是main方法有守护线程吗?怎么知道有没有守护线程的呢?

继续搜索,在一篇博客(http://www.voidcn.com/article/p-brbjzhux-np.html)中写道“Junit会把第一个启动的线程作为守护线程,当守护线程结束后程序也就终止了,而main方法启动的时候本身线程会作为守护线程存在,等待其他线程执行结束”。但是,感觉这解释也是有点自相矛盾。

后来,群里伙伴又提到使用线程池的话,可以在单元测试里运行完。这个后面再说吧。

3. 解惑

在很多博客中看到,使用Junit的@Test注解的方法,最终是由TestRunner里面的main方法执行的。Main方法里面的参数args就是被注解的方法,参数如下图所示:

然后,我们看一下TestRunner里的main方法,被注解的方法是由TestRunner里面的start方法执行。一旦被注解的方法执行完(只关注当前方法的执行语句,不关注方法中其他线程是否执行完,其实想想异步也就清楚了),无论方法执行成功或是失败,都会调用System.exit()方法,结束当前正在运行的虚拟机。因此,就会导致在Junit方法执行的测试测试语句达不到预期的效果。其实,如果你在main方法里面加上System.exit()语句后,也达不到预期的效果,不信可以试一试。

所以,问题的根本原因不是说没有守护线程,因为都是main这个用户线程在执行。只是一个执行System.exit()语句关闭了虚拟机,一个没有关闭虚拟机。同理,使用线程池的话,也是不能执行出预期的结果,在此就不在赘述了。

重点来了……

虽然找到了TestRunner方法,但是,却找不到程序从什么地方进入的。然后,看了汪洋师兄(他的主页:https://blog.csdn.net/w605283073/article/details/92016433)的博客后,我觉得还是要好好看一下调用栈。截图如下:

如红色方框所示,程序进入是从idea的启动程序中进入的,之前我以为是从idea启动,然后中间会通过某种方法执行到TestRunner,后来发现还真不是。然后,从idea的安装目录中(我的是在D:\development\idea\plugins\junit\lib),找到了junit-rt.jar,然后查看该jar包发现,程序的流程是下面这样的:

(1)首先,进入到com.intellij.rt.execution.junit.JUnitStart中的main方法,由prepareStreamsAndStart函数来执行。

(2)再看一下prepareStreamsAndStart函数,如下:

程序的返回是从这里开始的,最终返回的结果是由我们添加的Junit的jar包程序JunitCore.run决定的,代码如下,而不是由TestRunner决定的。

别的代码在此就不细讲了,可以自己看一下调用栈就清楚了。虽然Junit不支持多线程是由于程序提前退出虚拟机决定的,但是,测试的方法不是由Junit里的TestRunner的main方法执行的。

4. 使用

如果项目中必须要在单元测试里执行多线程方法的话,怎么办呢,可参考:

https://blog.csdn.net/fcs_learner/article/details/81056868

(1)thread.join()

(2)CountDownLatch

(3)CyclicBarrier

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值