我有一个使用Maven和Java的多模块项目。我现在正在尝试迁移到Java 9/10/11并实现模块(如JSR 376: Java Platform Module System,JPMS)。由于项目已经由Maven模块组成,并且依赖项是直接的,因此为项目创建模块描述符非常简单。
现在,每个Maven模块在module-info.java
文件夹中都有自己的模块描述符(src/main/java
)。测试类没有模块描述符。
但是,我偶然发现了一个我无法解决的问题,并没有找到任何关于如何解决的说明:
如何使用Maven和Java模块进行模块间测试依赖?
在我的例子中,我有一个“通用”Maven模块,它包含一些接口和/或抽象类(但没有具体的实现)。在同一个Maven模块中,我有抽象测试来确保这些接口/抽象类的实现的正确行为。然后,有一个或多个子模块,接口/抽象类的实现和扩展抽象测试的测试。
但是,当尝试执行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(主分支还没有模块化),但原理是一样的。
PS:我认为代码本身没有任何问题,因为所有内容都使用普通的类路径进行编译和运行(即在IntelliJ中,或者在没有Java模块描述符的情况下使用Maven)。 Java模块和模块路径引入了该问题。
根据您的演示项目,我能够复制您的错误。也就是说,这是我在第一次尝试失败后所做的修改后的更改,以便能够构建项目:
maven-compiler-plugin
版本3.8.0。您需要3.7或更高版本才能使用Maven编译模块 - 至少这是NetBeans所显示的警告。由于没有任何危害,我在常用模块和实现模块的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
文件 - iemy-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仓库中更新了我的示例代码。我唯一挥之不去的问题,我相信你也这么做,这就是为什么当我将实现模块定义为常规的jar
项目而不是模块时它是否有效。但那,我将在其他日子玩。希望我提供的解决了你的问题。
我尝试做同样的事情,不可能在项目结构中同时使用whitebox测试和模块测试依赖项,但我认为我找到了一个替代结构,它可以完成90%你想做的事情:
1 / whitebox测试的问题在于它适用于模块修补,因为JPMS不像Maven那样没有测试VS main的概念。因此,这会引发一些问题,例如不使用测试依赖项,或者不得不使用测试依赖项污染模块信息。
2 /那么,为什么不继续进行白盒测试,但是使用黑盒测试的maven结构,即:将每个模块X分成X和X测试。只有X有一个module-info.java,测试在类路径中运行,因此您可以跳过所有这些问题。
我能想到的唯一缺点是(按重要性递增的顺序):
顺便说一下(如果这就是你正在做的事情)我怀疑它的模块化是值得的。一个弹簧启动应用程序作为弹簧本身尚未模块化,所以你将支付成本,但收获一些好处(但如果你正在编写一个不同的故事库)。
你可以在这里找到一个例子:
a /获取示例:
git clone https://github.com/vandekeiser/ddd-metamodel.git
git checkout stackoverflow
b /看看这个例子: