Java 8升级至Java 11后JUnit 4行为异常原因咨询
问题:Java 11升级后JUnit 4误将测试工具类识别为测试类
环境信息
- Gradle版本:4.10.3
> gradlew -version ------------------------------------------------------------ Gradle 4.10.3 ------------------------------------------------------------ Build time: 2018-12-05 00:50:54 UTC Revision: e76905e3a1034e6f724566aeb985621347ff43bc Kotlin DSL: 1.0-rc-6 Kotlin: 1.2.61 Groovy: 2.4.15 Ant: Apache Ant(TM) version 1.9.11 compiled on March 23 2018 JVM: 11.0.20 (Azul Systems, Inc. 11.0.20+8-LTS) OS: Windows 10 10.0 amd64
- JUnit版本:4.13.2
问题背景
仅将JDK/JRE从Java 8升级至Java 11,JUnit与Gradle版本未做任何改动,此前在Java 8环境下运行完全正常。
项目包含两类测试:
- 随构建流程自动运行的单元测试
- 通过可选Gradle任务触发的集成测试
项目中存在一批仅用于测试场景的工具类,具体情况:
- 多数为私有内部类,部分还实现了私有接口
- 部分是供多个测试类调用的公共类,但本身并非测试类
- 部分类依赖重载构造方法实现核心功能
升级后出现的异常行为
升级到Java 11后,JUnit开始将所有这类工具类(甚至包括枚举和接口)错误识别为测试类,并抛出三类错误:
- 私有类不符合测试类需为公共类的要求
- 类中不存在可运行的
@Test方法 - 部分类未满足测试类需有唯一构造方法的规则
为所有工具类(包括枚举、接口)添加@Ignore注解可以规避该问题,但无法理解为何仅升级Java版本就会导致JUnit的行为发生如此明显的变化。
错误示例(私有接口触发的集成测试失败)
org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.XXX.YYY.integration.support.UserManager$UserManagerOperations': 1. The class com.XXX.YYY.integration.support.UserManager$UserManagerOperations is not public. 2. Test class should have exactly one public constructor 3. No runnable methods at org.junit.runners.ParentRunner.validate(ParentRunner.java:525) at org.junit.runners.ParentRunner.<init>(ParentRunner.java:102) at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:84) at org.junit.runners.JUnit4.<init>(JUnit4.java:23) at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:10) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:37) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70) at org.junit.internal.requests.ClassRequest.createRunner(ClassRequest.java:28) at org.junit.internal.requests.MemoizingRequest.getRunner(MemoizingRequest.java:19) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:78) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38) at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:66) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) at jdk.internal.reflect.GeneratedMethodAccessor14.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) at com.sun.proxy.$Proxy2.processTestClass(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:117) at jdk.internal.reflect.GeneratedMethodAccessor13.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:155) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:137) at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) at java.base/java.lang.Thread.run(Thread.java:829)
核心疑问
已有临时解决方案,现寻求以下问题的答案:
- 为何未改动JUnit版本,其行为却随Java版本升级发生变化?
- 这种行为变更的逻辑依据是什么?
(注:未来计划升级Gradle版本并迁移至JUnit 5,但由于当前代码库规模较大,需采用小步迭代的方式推进,因此优先需要理解该问题背后的原因)
内容的提问来源于stack exchange,提问作者franklyspeaking
相关产品推荐
相关产品推荐

