我有一个使用 Maven 和 Java 的多模块项目。我现在正尝试迁移到 Java 9/10/11 并实现模块(如 JSR 376: Java Platform Module System ,JPMS)。由于项目已经由 Maven 模块组成,并且依赖关系是直接的,因此为项目创建模块描述符非常简单。
每个 Maven 模块现在在 src/main/java
文件夹中都有自己的模块描述符 (module-info.java
)。测试类没有模块描述符。
然而,我偶然发现了一个我无法解决的问题,而且没有找到任何关于如何解决的描述:
如何使用 Maven 和 Java 模块进行模块间测试依赖?
在我的例子中,我有一个“通用”Maven 模块,它包含一些接口(interface)和/或抽象类(但没有具体实现)。在同一个 Maven 模块中,我进行了抽象 测试以确保这些接口(interface)/抽象类的实现行为正确。然后,有一个或多个子模块,具有接口(interface)/抽象类的实现和扩展抽象测试的测试。
但是,当尝试执行 Maven 构建的 test
阶段时,子模块将失败并显示:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:testCompile (default-testCompile) on project my-impl-module: Compilation failure: Compilation failure:
[ERROR] C:\projects\com.example\my-module-test\my-impl-module\src\test\java\com\example\impl\FooImplTest.java:[4,25] error: cannot find symbol
[ERROR] symbol: class FooAbstractTest
[ERROR] location: package com.example.common
我怀疑发生这种情况是因为测试不是模块的一部分。即使 Maven 做了一些“魔术”让测试在模块范围内执行,它也不适用于我依赖的模块中的测试(出于某种原因)。我该如何解决这个问题?
项目的结构如下所示(full demo project files available here):
├───my-common-module
│ ├───pom.xml
│ └───src
│ ├───main
│ │ └───java
│ │ ├───com
│ │ │ └───example
│ │ │ └───common
│ │ │ ├───AbstractFoo.java (abstract, implements Foo)
│ │ │ └───Foo.java (interface)
│ │ └───module-info.java (my.common.module: exports com.example.common)
│ └───test
│ └───java
│ └───com
│ └───example
│ └───common
│ └───FooAbstractTest.java (abstract class, tests Foo)
├───my-impl-module
│ ├───pom.xml
│ └───src
│ ├───main
│ │ └───java
│ │ ├───com
│ │ │ └───example
│ │ │ └───impl
│ │ │ └───FooImpl.java (extends AbstractFoo)
│ │ └───module-info.java (my.impl.module: requires my.common.module)
│ └───test
│ └───java
│ └───com
│ └───example
│ └───impl
│ └───FooImplTest.java (extends FooAbstractTest)
└───pom.xml
my-impl-module/pom.xml
中的依赖如下:
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common-module</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common-module</artifactId>
<classifier>tests</classifier> <!-- tried type:test-jar instead, same error -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
注意:以上只是我为了演示问题而创建的一个项目。真正的项目要复杂得多,found here (master 分支还没有模块化),但是原理是一样的。
PS:我不认为代码本身有什么问题,因为一切都使用正常的类路径编译和运行(即在 IntelliJ 或没有 Java 模块描述符的 Maven 中)。 Java 模块和模块路径引入了问题。
最佳答案
根据您的演示项目,我能够重现您的错误。也就是说,这是我在第一次尝试失败后为构建项目所做的修订更改:
我将
maven-compiler-plugin
版本 3.8.0 添加到所有模块。您需要 3.7 或更高版本才能使用 Maven 编译模块 - 至少这是 NetBeans 显示的警告。由于没有任何危害,我将插件添加到 common 和 implementation 模块的 POM 文件中:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <executions> <execution> <goals> <goal>compile</goal> </goals> <id>compile</id> </execution> </executions> </plugin>
我将测试类导出到它们自己的
jar
文件中,以便您的实现模块或与此相关的任何人都可以使用它们。为此,您需要将以下内容添加到您的my-common-module/pom.xml
文件中:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>test-jar</id> <phase>package</phase> <goals> <goal>test-jar</goal> </goals> </execution> </executions> </plugin>
这会将
my-common-module
测试类导出到-tests.jar
文件中 - 即my-common-module-1.0-SNAPSHOT-tests。 jar
。请注意,不需要为常规jar
文件添加执行,如 post 中所述.但是,这会引入我接下来要解决的错误。将
my-common-module
中的测试包重命名为com.example.common.test
以便在编译时加载测试类实现测试类。这纠正了当我们导出具有与第一个jar
模块相同的包名称的测试类时引入的类加载问题,在本例中为模块,加载了第二个jar
,测试 jar 文件,被忽略。有趣的是,根据观察,我得出结论,模块路径的优先级高于类路径,因为 Maven 编译参数显示tests.jar
在类路径中首先指定。运行mvn clean validate test -X
,我们看到编译参数:-d /home/testenv/NetBeansProjects/MavenProject/Implementation/target/test-classes -classpath /home/testenv/NetBeansProjects/MavenProject/Implementation/target/test-classes:/home/testenv/.m2/repository/com/example/Declaration/1.0-SNAPSHOT/Declaration-1.0-SNAPSHOT-tests.jar:/home/testenv/.m2/repository/junit/junit/4.12/junit-4.12.jar:/home/testenv/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar: --module-path /home/testenv/NetBeansProjects/MavenProject/Implementation/target/classes:/home/testenv/.m2/repository/com/example/Declaration/1.0-SNAPSHOT/Declaration-1.0-SNAPSHOT.jar: -sourcepath /home/testenv/NetBeansProjects/MavenProject/Implementation/src/test/java:/home/testenv/NetBeansProjects/MavenProject/Implementation/target/generated-test-sources/test-annotations: -s /home/testenv/NetBeansProjects/MavenProject/Implementation/target/generated-test-sources/test-annotations -g -nowarn -target 11 -source 11 -encoding UTF-8 --patch-module example.implementation=/home/testenv/NetBeansProjects/MavenProject/Implementation/target/classes:/home/testenv/NetBeansProjects/MavenProject/Implementation/src/test/java:/home/testenv/NetBeansProjects/MavenProject/Implementation/target/generated-test-sources/test-annotations: --add-reads example.implementation=ALL-UNNAMED
我们需要使导出的测试类对实现模块可用。将此依赖项添加到您的
my-impl-module/pom.xml
:<dependency> <groupId>com.example</groupId> <artifactId>Declaration</artifactId> <version>1.0-SNAPSHOT</version> <type>test-jar</type> <scope>test</scope> </dependency>
最后在
my-impl-module
测试类中,更新导入以指定新的测试包com.example.common.text
,以访问my-common-module
测试类:import com.example.declaration.test.AbstractFooTest; import com.example.declaration.Foo; import org.junit.Test; import static org.junit.Assert.*; /** * Test class inheriting from common module... */ public class FooImplementationTest extends AbstractFooTest { ... }
以下是我对新更改的 mvn clean package
的测试结果:
我在 java-cross-module-testing 中更新了示例代码GitHub repo 。我有一个挥之不去的问题,我相信你也有,为什么当我将实现模块定义为常规 jar
项目而不是模块时它会起作用。但是,改天我会玩。希望我提供的内容可以解决您的问题。
关于java - 在具有模块间测试依赖性的 Maven 构建中正确实现 Java 模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53477690/