Java 9 + maven + junit: нужен ли тестовый код собственный module-info.java и где его разместить?

Скажем, у меня есть проект Java с использованием Maven 3 и junit. Есть каталоги src/main/java и src/test/java, в которых находятся основные исходники и исходники тестов соответственно (все стандартно).

Теперь я хочу перенести проект на Java 9. src/main/java content представляет модуль 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, кажется, рассматривает основные источники и тестовые источники как разные модули, поэтому мне нужно экспортировать пакеты из основного модуля в тестовый модуль и требовать пакеты в тестовом модуле, примерно так:

основной модуль (в 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 и т. Д.) Без него?


person Roman Puchkovskiy    schedule 06.10.2017    source источник
comment
Сначала забудьте о Maven 2 ... используйте Maven 3 + ... module-info в тесте разве с моей точки зрения не имеет смысла? За этим стоит особое требование / достижение?   -  person khmarbaise    schedule 07.10.2017
comment
Конечно, это Maven 3   -  person Roman Puchkovskiy    schedule 07.10.2017


Ответы (4)


Модульная система не делает различий между производственным кодом и тестовым кодом, поэтому, если вы решите разделить тестовый код на модули, prod.module и test.module не могут использовать один и тот же пакет com.acme.project, как описано в specs:

Невмешательство - компилятор Java, виртуальная машина и система времени выполнения должны гарантировать, что модули, содержащие пакеты с одинаковыми именами, не мешают друг другу. Если два разных модуля содержат пакеты с одинаковыми именами, то с точки зрения каждого модуля все типы и члены в этом пакете определяются только этим модулем. Код в этом пакете в одном модуле не должен иметь доступ к частным типам пакета или членам этого пакета в другом модуле.

Как указал Алан Бейтман, плагин компилятора Maven использует --patch-module и другие параметры, предоставляемые модульной системой при компиляции кода в дереве src / test / java, так что тестируемый модуль дополняется тестовыми классами. И это также делается подключаемым модулем Surefire при запуске тестовых классов (см. Поддержка запуска модульных тестов в названные модули Java 9). Это означает, что вам не нужно размещать тестовый код в модуле.

person M A    schedule 06.10.2017
comment
Изменение пакета для тестирования имеет обратную сторону: ваши тесты больше не будут использовать модификаторы по умолчанию и защищенные модификаторы. - person Absurd-Mind; 07.10.2017
comment
Модуль предоставляет --patch-module и другие параметры для поддержки компиляции и выполнения тестов, которые находятся в том же пакете / модуле, что и тестируемый модуль. Плагин maven-compiler-plugin использует эти параметры при компиляции кода в дереве src / test. То же самое и для плагина surefire. - person Alan Bateman; 07.10.2017
comment
@AlanBateman Спасибо за эту информацию. Я не знал, что Maven делает это. Я дополню ответ вашей информацией. - person M A; 07.10.2017
comment
@AlanBateman Интересно узнать, какой тег в surefire поддерживает это? Мне было бы полезно прочитать любые источники, на которые вы можете ссылаться. - person Naman; 07.10.2017
comment
@nullpointer Кажется, добавлено в issues.apache.org/jira/browse/SUREFIRE-1420 < / а>. - person M A; 07.10.2017
comment
@manouti Я увидел это в конце концов после того, как оставил комментарий. Поскольку argLine уже использовался ранее для уверенности, это была полезная существующая опция, которую можно было расширить. И комбинация с classpathDependencyScopeExclude служит хорошей цели. :) - person Naman; 07.10.2017

Возможно, вы захотите пересмотреть дизайн проекта, который вы пытаетесь реализовать. Поскольку вы внедряете модуль и его тестирование в проект, вам следует воздерживаться от использования разных модулей для каждого из них по отдельности.

Для модуля и соответствующих тестов должен быть только один 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-plugin, который сам позаботится о исправлении тестов для модуля.

person Naman    schedule 06.10.2017
comment
Я бы посоветовал не использовать имя модуля в структуре каталогов, потому что в этом нет преимущества ....? - person khmarbaise; 08.10.2017
comment
@khmarbaise Я думаю, вы имеете в виду com.acme.project/com/acme/project. Просто ознакомился с кратким руководством там. Хотя я согласен, это не дает никаких преимуществ как таковых. - person Naman; 08.10.2017
comment
Требование junit в дескрипторе модуля мне не очень нравится - person ZhekaKozlov; 13.10.2017
comment
Кроме того, IDEA выделяет requires static junit как ошибку (поскольку у junit есть тестовая область в pom.xml) - person ZhekaKozlov; 13.10.2017
comment
@ZhekaKozlov (1) Почему junit в дескрипторе модуля не выглядит хорошо? (2) область видимости в maven можно контролировать и не обязательно test (хотя это хорошая практика) - person Naman; 13.10.2017
comment
@nullpointer (1) Потому что, если мой модуль зависит от junit, кто-то может случайно использовать его классы - person ZhekaKozlov; 13.10.2017
comment
@ZhekaKozlov Я, может быть, нахожусь на другой стороне аудитории, которой предпочтительно не нравится подход исправления модулей. Тем не менее, обновление ответа с помощью подхода Maven. Спасибо за предложение. - person Naman; 13.10.2017
comment
Intellij IDEA требует другой структуры каталога, что, похоже, не позволяет использовать тестовые или основные каталоги. - person JMess; 24.01.2018
comment
этот совет одного module временами даже невозможен. вот пример - person Eugene; 13.08.2020

Я просто хочу добавить сюда свой 0.02$ об общем подходе к тестированию, поскольку кажется, что никто не обращается к gradle, и мы его используем.

Первым делом нужно рассказать gradle о модулях. Это довольно тривиально, через (это будет с gradle-7):

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

Если вам нужно протестировать код, gradle сообщает следующее:

Если у вас нет module-info.java файла в исходном наборе тестов (src/test/java), этот набор источников будет считаться традиционной библиотекой Java во время компиляции и выполнения теста.

Говоря простым языком, если вы не определите module-info.java для целей тестирования - все будет просто работать, и в большинстве случаев это именно то, что мы хотим.


Но это еще не конец истории. Что, если я действительно хочу определить JUnit5 Extension через ServiceLocator. Это означает, что мне нужно перейти в module-info.java после тестов; тот, которого у меня еще нет.

И gradle снова решил эту проблему:

Другой подход к тестированию методом белого ящика - оставаться в мире модулей, вставляя тесты в тестируемый модуль. Таким образом, границы модуля остаются на месте, но сами тесты становятся частью тестируемого модуля и затем могут получить доступ к внутренним компонентам модуля.

Итак, мы определяем module-info.java в 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.

Пример находится здесь

person Eugene    schedule 13.08.2020
comment
Спасибо, что поделились. Я новичок в Gradle и просто имею базовое понимание, так что могу связать несколько концепций с Maven. Что я понимаю с помощью , это будет, поскольку gradle-7 в вашем ответе, заключается в том, что Gradle создаст возможность идентифицировать путь к модулю java по сравнению с осведомленностью о пути к классам и разумно выбирать, какой код находится где сам по себе. Как пользователь, указание источника и цели java в тестовых плагинах должно быть достаточным с моей точки зрения. - person Naman; 13.08.2020
comment
Но да, похоже, что-то вроде provides org.junit.jupiter.api.extension.Extension with __; это форпост. Честно говоря, мне нужно было бы изучить рекомендации Junit5 по определению расширения и согласованию его с системой модулей Java. (пора оглянуться на некоторые ответы из Сормураса здесь, я думаю, в частности, этот!) - person Naman; 13.08.2020

Добавляем детали.

В Java, начиная с 9, файл jar (или каталог с классами) может быть помещен в путь к классам (как раньше) или в путь к модулю. Если он добавлен в путь к классам, его информация о модуле игнорируется, и никакие ограничения, связанные с модулем (что читает, что экспортирует, и т. Д.) Не применяются. Если, однако, jar добавляется к пути к модулю, он рассматривается как модуль, поэтому его информация о модуле обрабатывается, и будут применяться дополнительные ограничения, связанные с модулем.

В настоящее время (версия 2.20.1) maven-surefire-plugin может работать только по-старому, поэтому он помещает тестируемые классы в classpath, а путь к модулю игнорируется. Итак, прямо сейчас добавление информации о модуле в проект 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), информация о модуле (который является дескриптором основного модуля) не должна будет включать ссылку на junit (которая нужна только для тестов) .

Резюме:

  1. информация о модуле просто должна быть добавлена ​​к основным источникам
  2. на данный момент surefire игнорирует новую логику, связанную с модулем (но это будет изменено в будущем)
  3. (когда модули будут работать при надежных тестах), вероятно, не нужно будет добавлять junit в информацию о модуле.
  4. (когда модули будут работать при надежных тестах), если какой-то модуль требуется тестами (и только ими), он может быть добавлен как зависимость только для компиляции (с использованием require static), как предлагает @nullpointer . В этом случае модуль Maven должен будет зависеть от артефакта, поставляющего этот тестовый модуль с использованием области компиляции (не тестирования), что мне не очень нравится.
person Roman Puchkovskiy    schedule 12.10.2017