Java 9 + maven + junit:测试代码需要自己的module-info.java吗?放在哪里?

flvtvl50  于 12个月前  发布在  Maven
关注(0)|答案(7)|浏览(134)

假设我有一个使用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似乎将主源代码和测试源代码视为不同的模块,所以我必须将包从main模块导出到test模块,并要求test模块中的包,类似于这样:
主模块(在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等)没有它?

6yoyoihd

6yoyoihd1#

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

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

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

qybjjes1

qybjjes12#

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

  • 一个模块及其对应的测试应该只有一个**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来实现这一点,maven-surefire-plugin将负责将测试修补到模块本身。

gwbalxhn

gwbalxhn3#

补充一些细节。
在Java 9中,jar文件(或包含类的目录)可以放在类路径上如果它被添加到classpath中,它的module-info将被忽略,并且没有与模块相关的限制(什么读取什么,什么导出什么,等等)。然而,如果一个jar被添加到模块路径,它被视为一个模块,所以它的模块信息被处理,并且将强制执行附加的与模块相关的限制。
目前(版本2.20.1),maven-surefire-plugin只能以旧的方式工作,所以它将正在测试的类放在classpath上,而module-path被忽略。所以,现在,向Maven项目添加module-info不应该改变使用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插件将测试中的代码放在模块路径上。当它完成并发布时,会考虑一个module-info**。但是如果他们会让测试中的模块自动读取junit模块,(如SUREFIRE-1420所建议的),module-info(这是一个主模块描述符)将不必包含对junit的引用(这只在测试中需要)。
简历:

  1. module-info只需要添加到主源代码中
    1.目前,surefire会忽略新的模块相关逻辑(但将来会改变)
    1.* (当模块在surefire测试下工作时)* junit可能不需要添加到module-info中
    1.* (当模块在万无一失的测试下工作时)* 如果某个模块是测试所需要的(并且只有测试所需要),它可以被添加为一个仅限编译的依赖(使用require static),正如@nullpointer所建议的那样。在这种情况下,Maven模块将不得不依赖于一个工件,该工件使用编译(而不是测试)范围提供该仅限测试的模块,我不太喜欢这种情况。
tpgth1q7

tpgth1q74#

我只是想在这里添加我的0.02$ * 关于一般测试方法 *,因为似乎没有人解决gradle,我们使用它。
首先,我们需要告诉gradle关于模块的事情。这是相当琐碎的,通过(这将是“on”,因为gradle-7):

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

字符串
当你需要测试你的代码时,gradle是这样说的:
如果您的测试源代码集(src/test/java)中没有module-info.java文件,则在编译和测试运行时,此源代码集将被视为传统的Java库。
简单地说,如果你没有为测试目的定义module-info.java-事情“就会正常工作”,在大多数情况下,这正是我们想要的。
但是,这并不是故事的结束。如果我确实想通过ServiceLocator定义JUnit5 Extension,那意味着我需要从测试中进入 * module-info.java;我还没有这样做。
gradle再次解决了这个问题:
白盒测试的另一种方法是通过将测试修补到被测模块中来留在模块世界中。这样,模块边界保持不变,但测试本身成为被测模块的一部分,然后可以访问模块的内部。
所以我们在src/test/java中定义一个module-info.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运行。
示例为here

n9vozmp4

n9vozmp45#

还要注意,maven-surefire-plugin现在有 *useModulePath false * 作为配置选项。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M6</version>
  <configuration>
    <useModulePath>false</useModulePath>  <!-- tests use classpath -->
  </configuration>
</plugin>

字符串
这是一个选项,项目使用模块路径作为main,而使用类路径作为测试和测试。如果“修补”模块路径变得痛苦,那么使用这种方法可能是一个不错的选择。
编辑:我们也可以通过属性-surefire.useModulePath设置,例如:

<properties>
  <surefire.useModulePath>false</surefire.useModulePath>
</properties>

ubbxdtey

ubbxdtey6#

我也无法使用最新的Maven surefire插件版本(3.0.0-M5)来使其工作。似乎如果主要源代码使用模块,则使用Java 11时编译器插件也期望引用的包位于模块中。
我的解决方案是将自己的module-info.java放置在测试源代码中(Maven中的src/test/java),用于测试模块的以下内容。我的情况是我必须使用关键字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;
}

字符串

qacovj5a

qacovj5a7#

您不需要辅助module-info.java,您可以在同一个文件中指定所有内容:
对于任何仅用于测试的依赖项,
1.在项目module-info.java的src/main/java下添加一个条目requires static <module-name>
1.将Maven依赖范围(在pom.xml中)设置为<scope>provided</scope>
为什么会这样?
require static向编译器表明这些依赖项在运行时是不需要的。它们也是不可传递的,因此它们不会出现在其他项目中
<scope>provided</scope>类似于compile + runtime + test,但不是可传递的,所以它也不会出现在其他项目中。
这种方法在Eclipse中也能很好地工作。
如果它对你不起作用,请尝试更新你的Maven插件依赖项(maven-compiler-plugin,maven-surefire-plugin)和/或你的IDE。

相关问题