问题
Let's say I have a Java project using Maven 3 and junit. There are src/main/java
and src/test/java
directories which contain main sources and test sources, respectively (everything is standard).
Now I want to migrate the project to Java 9. src/main/java
content represents Java 9 module; there is com/acme/project/module-info.java
looking approximately like this:
module com.acme.project {
require module1;
require module2;
...
}
What if test code needs module-info.java
of its own? For example, to add a dependence on some module that is only needed for tests, not for production code. In such a case, I have to put module-info.java
to src/test/java/com/acme/project/
giving the module a different name. This way Maven seems to treat main sources and test sources as different modules, so I have to export packages from the main module to the test module, and require packages in the test module, something like this:
main module (in src/main/java/com/acme/project
):
module prod.module {
exports com.acme.project to test.module;
}
test module (in src/test/java/com/acme/project
):
module test.module {
requires junit;
requires prod.module;
}
This produces
[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
because one package is defined in two modules. So now I have to have different projects in main module and test module, which is not convenient.
I feel I follow wrong path, it all starts looking very ugly. How can I have module-info.java
of its own in test code, or how do I achieve the same effects (require
, etc) without it?
回答1:
The module system does not distinguish between production code and test code, so if you choose to modularize test code, the prod.module
and the test.module
cannot share the same package com.acme.project
, as described in the specs:
Non-interference — The Java compiler, virtual machine, and run-time system must ensure that modules that contain packages of the same name do not interfere with each other. If two distinct modules contain packages of the same name then, from the perspective of each module, all of the types and members in that package are defined only by that module. Code in that package in one module must not be able to access package-private types or members in that package in the other module.
As indicated by Alan Bateman, the Maven compiler plugin uses --patch-module and other options provided by the module system when compiling code in the src/test/java tree, so that the module under test is augmented with the test classes. And this is also done by the Surefire plugin when running the test classes (see Support running unit tests in named Java 9 modules). This means you don't need to place your test code in a module.
回答2:
You might want to rethink the project design you're trying to implement. Since you are implementing a module and its test into a project, you shall refrain from using different modules for each of them individually.
There should just be one single module-info.java
for a module and its corresponding tests.
Your relevant project structure might look like this:-
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
where the module-info.java
could further be:-
module com.acme.project {
requires module1;
requires module2;
// requires junit; not required using Maven
}
Just to sum all of the above as per your questions --
I feel I follow wrong path, it all starts looking very ugly. How can I have module-info.java of its own in test code, or how do I achieve the same effects (require, etc) without it?
Yes, you should not consider managing different modules for test code making it complex.
You can achieve similar effect by treating junit
as a compile-time dependency using the directives as follows-
requires static junit;
Using Maven you can achieve this following the above-stated structure and using maven-surefire-plugin
which would take care of patching the tests to the module by itself.
回答3:
Adding some details.
In Java since 9, a jar file (or a directory with classes) may be put on classpath (as earlier), or on module path. If it is added to classpath, its module-info is ignored, and no module-related restrictions (what reads what, what exports what, etc) are applied. If, however, a jar is added to module path, it is treated as a module, so its module-info is processed, and additional module-related restrictions will be enforced.
Currently (version 2.20.1), maven-surefire-plugin can only work in the old way, so it puts the classes being tested on classpath, and module-path is ignored. So, right now, adding module-info to a Maven project should not change anything with tests being run using Maven (with surefire plugin).
In my case, the command line is like the following:
/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
The classes under test is not added as a module, so they are on classpath.
Currently, a work is under way in https://issues.apache.org/jira/browse/SUREFIRE-1262 (SUREFIRE-1420 is marked as a duplicate of SUREFIRE-1262) to teach surefire plugin to put code under test on module path. When it is finished and released, a module-info will be considered. But if they will make the module under test to read junit module automatically (as SUREFIRE-1420 suggests), module-info (which is a main module descriptor) will not have to include a reference to junit (which is only needed for tests).
A resume:
- module-info just needs to be added to the main sources
- for the time being, surefire ignores new module-related logic (but this will be changed in the future)
- (when modules will work under surefire tests) junit will probably not need to be added to the module-info
- (when modules will work under surefire tests) if some module is required by tests (and only by them), it may be added as a compile-only dependence (using
require static
), as suggested by @nullpointer. In this case, the Maven module will have to depend on an artifact supplying that test-only module using compile (not test) scope which I don't like much.
来源:https://stackoverflow.com/questions/46613214/java-9-maven-junit-does-test-code-need-module-info-java-of-its-own-and-wher