62 votes

Java 9 + maven + junit : le code de test a-t-il besoin de son propre module-info.java et où le mettre ?

Disons que j'ai un projet Java utilisant Maven 3 et junit. Il y a src/main/java y src/test/java qui contiennent les sources principales et les sources de test, respectivement (tout est standard).

Maintenant je veux migrer le projet vers Java 9. src/main/java Le contenu représente le module Java 9 ; il y a com/acme/project/module-info.java ressemble à peu près à ceci :

module com.acme.project {
    require module1;
    require module2;
    ...
}

Et si le code de test a besoin module-info.java de la sienne ? Par exemple, pour ajouter une dépendance sur un module qui n'est nécessaire que pour les tests, et non pour le code de production. Dans un tel cas, je dois mettre module-info.java a src/test/java/com/acme/project/ en donnant au module un nom différent. De cette façon, Maven semble traiter les sources principales et les sources de test comme des modules différents, et je dois donc exporter des paquets du module principal vers le module de test, et exiger des paquets dans le module de test, quelque chose comme ceci :

module principal (en src/main/java/com/acme/project ) :

module prod.module {
    exports com.acme.project to test.module;
}

module de test (en src/test/java/com/acme/project ) :

module test.module {
    requires junit;
    requires prod.module;
}

Cela produit

[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

car un paquet est défini dans deux modules. Je dois donc maintenant avoir des projets différents dans le module principal et le module de test, ce qui n'est pas pratique.

J'ai l'impression de suivre le mauvais chemin, tout commence à être très laid. Comment puis-je avoir module-info.java propre dans le code de test, ou comment puis-je obtenir les mêmes effets ( require etc.) sans lui ?

0 votes

D'abord, oubliez Maven 2... utilisez Maven 3+... a module-info dans le test sont de mon point de vue n'a pas de sens ? Exigence spéciale / réalisation derrière cela ?

1 votes

C'est Maven 3, bien sûr

21voto

manouti Points 10398

Le système de modules ne fait pas de distinction entre le code de production et le code de test. Par conséquent, si vous choisissez de modulariser le code de test, le système de modules ne peut pas être utilisé. prod.module y el test.module ne peuvent pas partager le même paquet com.acme.project comme décrit dans le Spécifications :

Non-interférence - Le compilateur Java, la machine virtuelle et le système d'exécution doivent garantir que les modules qui contiennent des paquets du même nom n'interfèrent pas entre eux. Si deux modules distincts contiennent des paquets du même nom, alors, du point de vue de chaque module, tous les types et membres de ce paquetage sont définis uniquement par ce module. Le code de ce paquetage dans un module ne doit pas être en mesure d'accéder aux types ou aux membres privés du paquetage dans l'autre module.

Comme indiqué par Alan Bateman, le plugin du compilateur Maven utilise --patch-module et autres options fourni par le système de modules lors de la compilation du code dans l'arbre src/test/java, de sorte que le module testé soit complété par les classes de test. C'est également ce que fait le plugin Surefire lors de l'exécution des classes de test (cf. Prise en charge de l'exécution de tests unitaires dans les modules Java 9 nommés ). Cela signifie que vous n'avez pas besoin de placer votre code de test dans un module.

9 votes

Changer le paquet en test a l'inconvénient que vos tests n'atteindront plus les modificateurs par défaut et protégés.

3 votes

Le module fournit --patch-module et d'autres options pour supporter la compilation et l'exécution de tests qui sont dans le même package/module que le module sous test. Le maven-compiler-plugin utilise ces options lors de la compilation du code dans l'arbre src/test. Idem pour le plugin surefire.

0 votes

@AlanBateman Merci pour cette information. Je ne savais pas que Maven faisait cela. Je vais mettre à jour la réponse avec vos informations.

7voto

nullpointer Points 1135

Vous pourriez vouloir repenser la conception du projet que vous essayez de mettre en œuvre . Puisque vous implémentez un module et son test dans un projet, vous devez vous abstenir d'utiliser des modules différents pour chacun d'eux individuellement.

Il ne devrait y avoir qu'un seul module-info.java pour un module et ses tests correspondants.

La structure de votre projet pourrait ressembler à ceci:-

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

où le module-info.java pourrait encore être : -

module com.acme.project {
    requires module1;
    requires module2;
    // requires junit; not required using Maven
}

Pour résumer tout ce qui précède, conformément à vos questions

J'ai l'impression de suivre le mauvais chemin, tout commence à être très laid. Comment puis-je avoir son propre module-info.java dans le code de test, ou comment puis-je obtenir les mêmes effets (require, etc) sans lui ?

Oui il ne faut pas envisager de gérer différents modules pour le code de test, ce qui le rendrait complexe.

Vous pouvez obtenir un effet similaire en traitant junit en tant que dépendance à la compilation en utilisant les directives suivantes-

requires static junit;

En utilisant Maven, vous pouvez y parvenir en suivant la structure décrite ci-dessus et en utilisant les éléments suivants maven-surefire-plugin qui se chargerait de Parcheando les tests au module par lui-même.

1 votes

Je suggère de ne pas utiliser le nom d'un module dans la structure du répertoire car il n'y a aucun avantage à le faire .... ?

0 votes

@khmarbaise Je crois que vous voulez dire la com.acme.project/com/acme/project . J'ai juste suivi le guide de démarrage rapide là. Bien que je sois d'accord, cela n'apporte aucun avantage en tant que tel.

3 votes

Exigeant junit dans le descripteur de module n'est pas très agréable pour moi.

3voto

Roman Puchkovskiy Points 6522

Ajout de quelques détails.

Dans Java depuis 9, un fichier jar (ou un répertoire contenant des classes) peut être placé sur le classpath (comme précédemment), ou sur le module path. S'il est ajouté au classpath, son module-info est ignoré, et aucune restriction liée au module (ce qui lit quoi, ce qui exporte quoi, etc.) n'est appliquée. Si, par contre, un jar est ajouté au chemin du module, il est traité comme un module, donc son info module est traitée, et des restrictions supplémentaires liées au module seront appliquées.

Actuellement (version 2.20.1), maven-surefire-plugin ne peut fonctionner que de l'ancienne manière, c'est-à-dire qu'il place les classes testées sur classpath, et module-path est ignoré. Ainsi, à l'heure actuelle, l'ajout de module-info à un projet Maven ne devrait rien changer aux tests exécutés à l'aide de Maven (avec le plugin surefire).

Dans mon cas, la ligne de commande est la suivante :

/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

Les classes à tester ne sont pas ajoutées en tant que module, elles sont donc sur le classpath.

Actuellement, un travail est en cours dans https://issues.apache.org/jira/browse/SUREFIRE-1262 (SUREFIRE-1420 est marqué comme un duplicata de SUREFIRE-1262) pour apprendre au plugin surefire à mettre le code en test sur le chemin du module. Lorsqu'il est terminé et publié, un module-info sera être pris en considération. Mais s'ils font en sorte que le module testé lise automatiquement le module junit (comme le suggère SUREFIRE-1420), module-info (qui est un descripteur de module principal) ne devra pas inclure une référence à junit (qui n'est nécessaire que pour les tests).

Un CV :

  1. module-info doit juste être ajouté aux sources principales
  2. pour l'instant, surefire ignore la nouvelle logique liée au module (mais cela sera modifié à l'avenir).
  3. (lorsque les modules fonctionneront dans le cadre de tests infaillibles) junit n'aura probablement pas besoin d'être ajouté au module-info
  4. (lorsque les modules fonctionneront dans le cadre de tests infaillibles) si un module est requis par les tests (et uniquement par eux), il peut être ajouté en tant que dépendance de compilation uniquement (en utilisant la commande require static ), comme suggéré par @nullpointer. Dans ce cas, le module Maven devra dépendre d'un artefact fournissant ce module de test uniquement en utilisant la portée de compilation (et non de test), ce que je n'aime pas beaucoup.

3voto

Eugene Points 6271

Je veux juste ajouter mon 0.02$ ici sur l'approche générale des tests puisque personne ne semble s'occuper de gradle et nous l'utilisons.

Tout d'abord, il faut dire gradle sur les modules. C'est assez trivial, via (ce sera "on" puisque gradle-7 ) :

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

Une fois que vous avez besoin de tester votre code, gradle dit ceci :

Si vous n'avez pas de module-info.java dans votre ensemble de sources de test ( src/test/java ), cet ensemble de sources sera considéré comme une bibliothèque Java traditionnelle lors de la compilation et de l'exécution des tests.

En clair, si vous faites no définir un module-info.java à des fins de test - les choses "fonctionneront simplement" et dans la majorité des cas, c'est exactement ce que nous voulons.


Mais, ce n'est pas la fin de l'histoire. Que faire si je veux définir un JUnit5 Extension via ServiceLocator . Cela signifie que je dois aller en module-info.java à partir de tests ; un que je n'ai pas encore.

Et gradle a encore résolu ce problème :

Une autre approche pour les tests whitebox est de rester dans le monde des modules en Parcheando les tests dans le module sous test. De cette façon, les frontières du module restent en place, mais les tests eux-mêmes deviennent une partie du module sous test et peuvent alors accéder aux internes du module.

Nous définissons donc un module-info.java en src/test/java où je peux mettre :

 provides org.junit.jupiter.api.extension.Extension with zero.x.extensions.ForAllExtension;

nous devons aussi faire --patch-module comme le font les plugins maven. Cela ressemble à ceci :

def moduleName = "zero.x"
def patchArgs = ["--patch-module", "$moduleName=${tasks.compileJava.destinationDirectory.asFile.get().path}"]
tasks.compileTestJava {
    options.compilerArgs += patchArgs
}
tasks.test {
    jvmArgs += patchArgs
}

Le seul problème est que intellij ne "voit" pas ce patch et pense que nous avons également besoin d'une requires directive ( requires zero.x.services ), mais ce n'est pas vraiment le cas. Tous les tests s'exécutent parfaitement depuis la ligne de commande et intellij .

L'exemple est le suivant aquí

0voto

user1036 Points 21

Je n'ai pas réussi à le faire fonctionner également avec la dernière version du plugin Maven surefire (3.0.0-M5). Il semble que si les sources principales utilisent un module, le plugin du compilateur, lorsqu'il utilise Java 11, s'attend également à ce que les paquets référencés soient dans un module.

Ma solution a été de placer un propre module-info.java à l'intérieur des sources de test ( src/test/java dans Maven) pour le module de test avec le contenu ci-dessous. Dans mon cas, j'ai dû utiliser le mot-clé open (Voir Autoriser l'accès à tous les paquets d'un module pendant l'exécution seulement ) parce que j'utilise Mockito dans mon test, qui nécessite un accès réfléchi.

// 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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X