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

时间:2017-10-06 20:05:05

标签: java maven junit java-9 java-module

假设我有一个使用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等)?

4 个答案:

答案 0 :(得分:8)

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

  

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

如Alan Bateman所示,在编译src / test / java树中的代码时,Maven编译器插件使用模块系统提供的--patch-module and other options,以便测试模块使用测试类进行扩充。在运行测试类时,这也是由Surefire插件完成的(参见Support running unit tests in named Java 9 modules)。这意味着您不需要将测试代码放在模块中。

答案 1 :(得分:5)

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

模块及其相应的测试应该只有一个 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视为compile-time dependency来实现类似效果 -

requires static junit;

<击>

使用Maven,您可以按照上述结构并使用maven-surefire-plugin来实现这一点,class A { class B { public: B(); ~B(); }; public: B* a, b, c; A(); ~A(); void foo(); }; A::foo() { a = b = c; } 将自己将测试修补到模块。

答案 2 :(得分:3)

我只是想在一般测试方法中在这里添加0.02$ ,因为似乎没有人在使用gradle,我们正在使用它。

首先,第一件事是要告诉gradle有关模块的信息。通过(这是很简单的)(通过{{1}开始,它将处于“打开”状态):

gradle-7

一旦您需要测试代码,plugins.withType(JavaPlugin).configureEach { java { modularity.inferModulePath = true } } says this

如果您的测试源集中(gradle中没有module-info.java文件,则在编译和测试运行期间,该源集将被视为传统Java库。

用简单的英语说,如果您为测试目的而定义src/test/java-事情“将起作用”,并且在大多数情况下,这正是我们想要的。

>

但是,这还不是故事的结局。如果我确实想通过module-info.java定义JUnit5 Extension怎么办?这意味着我需要从测试中进入 ServiceLocator。我还没有的那一个。

module-info.java又解决了这个问题:

白盒测试的另一种方法是通过将测试修补到被测模块中来保留在模块世界中。这样,模块边界保持不变,但是测试本身成为被测试模块的一部分,然后可以访问模块的内部。

因此我们在gradle中定义了一个module-info.java,我可以在其中放置:

src/test/java

我们还需要做 provides org.junit.jupiter.api.extension.Extension with zero.x.extensions.ForAllExtension; ,就像maven插件一样。看起来像这样:

--patch-module

唯一的问题是def moduleName = "zero.x" def patchArgs = ["--patch-module", "$moduleName=${tasks.compileJava.destinationDirectory.asFile.get().path}"] tasks.compileTestJava { options.compilerArgs += patchArgs } tasks.test { jvmArgs += patchArgs } 没有“看到”此补丁,并认为我们还需要一个intellij指令(requires),但实际上并非如此。在命令行和requires zero.x.services上,所有测试都可以正常运行。

示例为here

答案 3 :(得分:2)

添加一些细节。

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

目前(版本2.20.1),maven-surefire-plugin只能以旧方式工作,因此它将被测试的类放在类路径上,并忽略module-path。所以,现在,将模块信息添加到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模块必须依赖于使用我不太喜欢的编译(非测试)范围提供仅测试模块的工件。