57

假设我有一个使用 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.javasrc/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

5 回答 5

20

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

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

正如 Alan Bateman 所指出的,Maven 编译器插件在编译 src/test/java 树中的代码时使用了模块系统提供的--patch-module 和其他选项,以便在被测模块中增加测试类。这也是在运行测试类时由 Surefire 插件完成的(请参阅支持在命名 Java 9 模块中运行单元测试)。这意味着您不需要将测试代码放在模块中。

于 2017-10-06T21:13:03.320 回答
6

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

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-pluginwhich 将自行修补模块的测试。

于 2017-10-06T21:10:21.367 回答
3

我只想在0.02$这里添加我的一般测试方法,因为似乎没有人在解决gradle并且我们使用它。

首先,需要介绍gradle模块。这是相当微不足道的,通过(这将是“开”,因为gradle-7):

plugins.withType(JavaPlugin).configureEach {
    java {
        modularity.inferModulePath = true
    }
}

一旦你需要测试你的代码,gradle 这样说

module-info.java如果您的测试源集 ( ) 中没有文件,src/test/java则此源集在编译和测试运行时将被视为传统 Java 库。

用简单的英语来说,如果你没有为测试目的定义 a module-info.java- 事情“会正常工作”,在大多数情况下,这正是我们想要的。


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

gradle再次解决了这个问题:

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

所以我们定义一个module-info.javain src/test/java,我可以把:

 provides org.junit.jupiter.api.extension.Extension with zero.x.extensions.ForAllExtension;

我们也需要做--patch-module,就像 maven 插件做的一样。它看起来像这样:

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),但事实并非如此。所有测试都可以从命令行运行良好,并且intellij.

例子在这里

于 2020-08-13T01:24:42.647 回答
2

添加一些细节。

在 Java 9 中,一个 jar 文件(或一个包含类的目录)可以放在类路径(如前所述)或模块路径上。如果它被添加到类路径,它的模块信息将被忽略,并且不会应用与模块相关的限制(什么读取什么,什么导出什么等)。但是,如果将一个 jar 添加到模块路径中,它会被视为一个模块,因此会处理其模块信息,并且将强制执行其他与模块相关的限制。

目前(版本 2.20.1),maven-surefire-plugin 只能以旧方式工作,因此它将正在测试的类放在类路径上,模块路径被忽略。因此,现在,将 module-info 添加到 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. 模块信息只需要添加到主要来源
  2. 目前,surefire 会忽略新的模块相关逻辑(但将来会更改)
  3. (当模块在安全测试下工作时) junit 可能不需要添加到模块信息中
  4. (当模块将在万无一失的测试下工作时)如果测试(并且仅由它们)需要某些模块,则可以按照require static@nullpointer 的建议将其添加为仅编译依赖项(使用)。在这种情况下,Maven 模块将不得不依赖于使用我不太喜欢的编译(而非测试)范围提供该仅测试模块的工件。
于 2017-10-12T19:40:07.133 回答
0

我无法使其与最新的 Maven surefire 插件版本(3.0.0-M5)一起工作。如果主要来源正在使用模块,则使用 Java 11 时的编译器插件似乎也期望引用的包位于模块中。

module-info.java我的解决方案是在测试源中(src/test/java在 Maven 中)为具有以下内容的测试模块放置一个自己的。在我的情况下,我必须使用关键字open(请参阅Allowing runtime-only access to all packages in a module),因为我在测试中使用了 Mockito,这需要反射访问。

// the same module name like for the main module can be used, so the main module has also the name "com.foo.bar"
open module com.foo.bar {
// I use junit4
    requires junit;
// require Mockito here
    requires org.mockito;
// very important, Mockito needs it
    requires net.bytebuddy;
// add here your stuff
    requires org.bouncycastle.provider;
}
于 2021-12-19T02:42:52.440 回答