如何将Java 9私有可执行模块的包暴露给JUnit测试?
我刚好碰到过类似的场景,给你梳理下问题根源和可行的解决方案:
问题本质
你的模块是可执行模块(module-info.java里没有exports任何包),Gradle的org.gradle.java.experimental-jigsaw插件严格遵循Java模块系统(JPMS)的访问规则,测试模块默认无法访问主模块的包私有成员。而IntelliJ在运行测试时,会自动注入--add-opens这类JVM参数,动态开放包的访问权限,所以本地跑测试没问题,但Gradle构建时就会触发权限错误。
解决方案
1. 给主模块添加针对性的开放权限(推荐)
在主模块的module-info.java中,用opens关键字把包含待测试私有方法的包开放给测试模块。比如你的主模块名为com.example.myapp,测试模块默认是com.example.myapp.test,代码示例如下:
module com.example.myapp { // 开放内部包给测试模块,允许反射访问私有成员 opens com.example.myapp.internal to com.example.myapp.test; // 保持可执行模块的核心配置 requires java.base; // 指定主类(可执行模块必备) provides java.util.function.Supplier with com.example.myapp.Main; }
这里opens和exports的区别是:opens仅允许目标模块通过反射访问包内所有成员(包括私有),刚好满足测试需求,同时不会把包暴露给其他非测试模块,符合可执行模块的设计初衷。
2. 处理Gradle的警告
你提到添加配置后会触发警告,大概率是插件对opens语句的兼容性提示。可以通过两种方式处理:
- 在
build.gradle中添加编译参数,忽略模块相关的警告:tasks.withType(JavaCompile) { options.compilerArgs.add("-Xlint:-module") } - 确保测试模块的
module-info.test.java明确声明模块名,和主模块开放的目标完全一致,避免插件的模糊匹配警告:module com.example.myapp.test { requires com.example.myapp; requires org.junit.jupiter.api; // 其他测试依赖声明 }
3. 应急替代方案(不推荐)
如果不想修改主模块的module-info,可以临时把测试代码合并到主模块的源集里,但这会污染主模块的代码结构,只适合快速验证:
sourceSets { main { java { srcDirs += file('src/test/java') } } }
补充说明
IntelliJ之所以能正常运行测试,是因为它在启动测试时自动添加了类似--add-opens com.example.myapp.internal/com.example.myapp.test=ALL-UNNAMED的JVM参数,绕过了模块系统的限制。但Gradle的JPMS插件默认不会做这个操作,所以必须手动配置模块的开放权限。
内容的提问来源于stack exchange,提问作者Renato

