Java 9 + maven + junit:测试代码是否需要自己的module-info.java以及放置它的位置?

假设我有一个使用Maven 3和junit的Java项目。 有src/main/javasrc/test/java目录,分别包含主要源和测试源(一切都是标准的)。

现在我想将项目迁移到Java 9. src/main/java内容代表Java 9模块; com/acme/project/module-info.java看起来大概是这样的:

 module com.acme.project { require module1; require module2; ... } 

如果测试代码需要自己的module-info.java怎么办? 例如,添加对某些模块的依赖性,该模块仅用于测试,而不是生产代码。 在这种情况下,我必须将module-info.java放到src/test/java/com/acme/project/给模块一个不同的名称。 这样Maven似乎将主要源和测试源视为不同的模块,因此我必须将包从主模块导出到测试模块,并且需要测试模块中的包,如下所示:

主模块(在src/main/java/com/acme/project ):

 module prod.module { exports com.acme.project to test.module; } 

测试模块(在src/test/java/com/acme/project ):

 module test.module { requires junit; requires prod.module; } 

这产生了

 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:testCompile (default-testCompile) on project test-java9-modules-junit: Compilation failure: Compilation failure: [ERROR] /home/rpuch/git/my/test-java9-modules-junit/src/test/java/com/acme/project/GreeterTest.java:[1,1] package exists in another module: prod.module 

因为一个包在两个模块中定义。 所以现在我必须在主模块和测试模块中有不同的项目,这是不方便的。

我觉得我走错了道路,一切都开始看起来非常难看。 我如何在测试代码中拥有自己的module-info.java ,或者如何在没有测试代码的情况下实现相同的效果( require等)?

模块系统不区分生产代码和测试代码,因此如果您选择模块化测试代码,则prod.moduletest.module不能共享相同的包com.acme.project ,如规范中所述:

无干扰 – Java编译器,虚拟机和运行时系统必须确保包含相同名称包的模块不会相互干扰。 如果两个不同的模块包含相同名称的包,那么从每个模块的角度来看,该包中的所有类型和成员仅由该模块定义。 一个模块中该包中的代码必须不能访问另一个模块中该包中的包私有类型或成员。

如Alan Bateman所示,Maven编译器插件在编译src / test / java树中的代码时使用–patch-module和模块系统提供的其他选项 ,以便测试模块使用测试类进行扩充。 在运行测试类时,这也是由Surefire插件完成的(请参阅命名Java 9模块中的支持运行unit testing )。 这意味着您无需将测试代码放在模块中。

您可能想重新考虑您尝试实施的项目设计 。 由于您正在将一个模块及其测试实现到一个项目中,因此您应该避免为每个模块单独使用不同的模块。

模块及其相应的测试应该只有一个module-info.java

您的相关项目结构可能如下所示: –

 Project/ |-- pom.xml/ | |-- src/ | |-- test/ | | |-- com.acme.project | | | |-- com/acme/project | | | | |-- SomeTest.java | | | |-- main/ | | |-- com.acme.project | | | |-- module-info.java | | | |-- com/acme/project | | | | |-- Main.java 

module-info.java可以进一步: –

 module com.acme.project { requires module1; requires module2; // requires junit; not required using Maven } 

只是按照你的问题总结以上所有内容 –

我觉得我走错了道路,一切都开始看起来非常难看。 我如何在测试代码中拥有自己的module-info.java,或者如何在没有测试代码的情况下实现相同的效果(需要等)?

是的 ,您不应该考虑为测试代码管理不同的模块,使其变得复杂。

您可以通过使用以下指令将junit视为编译时依赖项来实现类似的效果-

 requires static junit; 

使用Maven,您可以按照上述结构并使用maven-surefire-plugin来实现这一点,该maven-surefire-plugin可以自行修补测试模块。

添加一些细节。

在Java中,从9开始,jar文件(或带有类的目录)可以放在类路径上(如前所述),或放在模块路径上。 如果将其添加到类路径中,则会忽略其module-info,并且不应用与模块相关的限制(读取什么,导出什么等)。 但是,如果将jar添加到模块路径,则将其视为模块,因此将处理其module-info,并强制执行其他与模块相关的限制。

目前(版本2.20.1),maven-surefire-plugin只能以旧方式工作,因此它将被测试的类放在类路径上,并忽略模块路径。 所以,现在,将模块信息添加到Maven项目不应该改变任何使用Maven运行的测试(使用surefire插件)。

在我的例子中,命令行如下所示:

 /bin/sh -c cd /home/rpuch/git/my/test-java9-modules-junit && /home/rpuch/soft/jdk-9/bin/java --add-modules java.se.ee -jar /home/rpuch/git/my/test-java9-modules-junit/target/surefire/surefirebooter852849097737067355.jar /home/rpuch/git/my/test-java9-modules-junit/target/surefire 2017-10-12T23-09-21_577-jvmRun1 surefire8407763413259855828tmp surefire_05575863484264768860tmp 

被测试的类不会作为模块添加,因此它们位于类路径中。

目前, https ://issues.apache.org/jira/browse/SUREFIRE-1262(SUREFIRE-1420标记为SUREFIRE-1262的副本)正在进行一项工作,以教授surefire插件将模块置于模块上路径。 完成并发布后, 考虑模块信息。 但如果他们将使被测模块自动读取junit模块(如SUREFIRE-1420建议的那样),module-info(主模块描述符)将不必包含对junit的引用(仅用于测试) 。

一份简历:

  1. module-info只需要添加到主要来源
  2. 目前,surefire忽略了与模块相关的新逻辑(但将来会改变)
  3. (当模块在surefire测试下工作时) junit可能不需要添加到module-info中
  4. (当模块在surefire测试下工作时)如果测试需要一些模块(并且只有它们),它可以作为仅编译依赖(使用require static )添加,如@nullpointer所建议的那样。 在这种情况下,Maven模块必须依赖于使用编译(非测试)范围提供该仅测试模块的工件,我不太喜欢。