前言
在开发Springboot 项目 实现一些功能时, 习惯性的Generate Test…
然后简单测试一下代码能否执行成功, 有无bug, 以及一些逻辑是否与需求一致…balabala
然后遇到了这个问题…
遇到的问题…
像往常一样, 创建Test去调试某个方法, 传参漏几个, 参数不存在等条件都验证通过了.
然后因为这个功能是作为异步执行. 需要给它加上@Async (以及启动类上加@EnableAsync) 注解, 在加上之后, 发现这个方法每次都执行到mapper查数据哪一行, 就莫名其妙退出了, 因为在方法结尾有打印输出一些乱七八糟的标识符, 但console中没有打印. 而且也没有任何报错or异常…在debug下发现每次都执行到mapper查sql那条语句时就退出…
在离谱的路上越走越远…
一开始以为是mapper没有成功注入? 但debug时显示mapper是有实例的, 不是null
-
在mapper前面写上一句从ApplicationContext中获取DataSource, 显示DataSource也是有的.
-
Idea Debug 时, 对当前断点行 Evaluate Expression, 也是能拿到结果的…
-
难道是注解的问题? (之前是直接用线程池调, 没用过注解) 然后网上搜了一波教程, 看了一下感觉也没啥问题.
-
去掉注解再次执行. 正常执行完成, 这一下把问题定到注解上了…(当事人表示非常后悔)
-
在去掉注解后, 在调用方法的后面又通过new Thread的方式再次调了一遍方法, 第一遍(直接执行)正常执行完成, 第二遍(新建线程执行)又是执行到查sql那里…
-
因为项目有配异常拦截器. 以为是不是出了什么异常(虽然参数什么的完全一样), 然后被拦截器吞了? 又在 mapper 这里加上 try catch块打印一下, 还是像之前一样, 没有任何异常报错, 又是在mapper 这里, 莫名其妙的退出了…
回到正轨…
-
(这里应该是脑子抽了一下) 把直接调用方法执行的语句放到了线程调用语句的后面, 然后就成功了! 两遍方法成功都把方法最后的标识符打印出来了!
-
再把顺序调回去试试, 又不行了..就大概猜到和线程执行有关
-
然后在线程执行的后面, 主任务上加上一个 Thread.sleep(1000), 试了几次, 有的成功有的失败(有的打印两遍, 有的打印一遍). 调高sleep时间到5000ms 成功.
有(qi)用(guai)的知识增加了…
在上面回到正轨后, 大概猜到了原因, 经搜索相关问题并得到以下内容
JUnit会在主线程直接完成后调用System.exit
退出
猜测
JUnit Test创建了一个新进程执行测试方法, 通过jps
命令可以查看当前Java进程, 很明显可以发现当测试用例执行时, 执行jps 会看到一个新的进程
猜测. 执行测试用例时, 会新建一个进程, 主线程启动Spring容器, 加载程序运行需要的一些环境配置, 实例等信息 (前期准备工作) , 然后会执行测试函数中的内容, 当执行完成时, 主线程会调用
System.exit
退出(具体实现过程待确认)
所以测试方法中, 新建的线程在执行过程中会因为进程退出而结束, 而因为方法中前面是几个简单的处理逻辑, 执行会很快, 可能进程还没结束就执行过去了, 而mapper读取数据会比较耗时, 所以每次进程退出时, 线程都是执行到这个位置, 给人已在这里异常结束的假象.
测试函数主流程加上sleep(1000), 就有玄学的成分了, 相当于两者竞争, 到底是mapper那边读数据快, 还是sleep + 进程结束更快, 也就有了部分成功, 部分失败的情况
Test 的额外知识点
对于一个TestClass中的多个@Test方法. 直接执行TestClass时, 所有的@Test方法会在一个线程中线性执行, 但并没有一个具体顺序, 可以通过@Rule注解 按顺序读取
退出程序的一种可能方法是简单地到达main方法的末尾。但是如果有任何非守护程序线程在运行, 程序就不会关闭, 所以JUnit Test在主方法执行完成后, 调用System.exit()进行退出
System.exit() 会执行数据保存, 资源关闭等工作, 目的是为了在JVM关闭之前执行一些收尾的动作