Atelier 09
Dans cette séance de laboratoire, vous allez vous entraîner aux concepts avancés des systèmes de construction, tels que les phases du cycle de vie de la construction, le sideloading des artefacts, et l'accès aux dépôts d'artefacts tiers.
Vous travaillerez avec le code de XoxInternals, qui est une implémentation complète du modèle et du contrôleur du jeu TicTacToe (reformulé sous le nom de Xox, pour les X et les O).
Pour cloner les sources, utilisez : git clone https://github.com/m5c/XoxInternals.git
Cycle de vie Maven
Maven possède trois cycles de vie intégrés, chacun composé de phases. Nous nous intéressons principalement au cycle de vie default
, qui permet de construire des logiciels à partir des sources.
Construction en appelant des phases du cycle de vie
- La commande maven attend toujours une phase du cycle de vie comme argument.
- Pour chaque phase fournie, le cycle complet jusqu'à cette phase est exécuté.
- Par exemple, la commande
mvn package
exécutera toutes les phases du cycle de viedefault
jusqu'à (et y compris) la phasepackage
.
À vous de jouer
- Vous allez travailler avec un nouveau projet
XoxFrontend
, qui appellera certaines des fonctionnalités fournies par le projet clonéXoxInternals
.- Il est cependant important de noter que nous voulons connecter ces deux projets à l'aide de Maven, et NON en copiant-collant du code d'un projet à l'autre.
- Pour commencer, initialisez un nouveau dépôt, appelé
XoxFrontend
, en utilisant la commande d'archétype Maven :
mvn archetype:generate \
-DgroupId=ca.uqam.info \
-DartifactId=XoxFrontend \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
If you're on windows, type the command in a single line, and omit the antislashes.
- The command will produce a new maven project of the following structure:
- Ensuite, construisez le projet. Faites-le en appelant la phase du cycle de vie
default
de Maven qui ne traduit que les fichiers Java en fichiers de classe, sans produire de fichier JAR.- Inspectez le dossier
target
. Vérifiez qu'il y a des fichiersclass
, mais pas de fichierjar
.
- Inspectez le dossier
- Construisez le projet en appelant la phase du cycle de vie
default
de Maven qui fait la même chose que précédemment, mais produit également un fichierjar
.- Inspectez le dossier
target
. Vérifiez qu'il y a des fichiersclass
, ainsi qu'un fichierjar
.
- Inspectez le dossier
Scans de vulnérabilité
- Chaque dépendance d'un projet est une responsabilité potentielle. Toute vulnérabilité incluse dans l'une de vos dépendances constitue également un risque pour la sécurité de votre propre projet.
- Heureusement, les fichiers
pom.xml
sont une forme explicite de déclaration des dépendances, ce qui permet de détecter facilement les vulnérabilités potentielles en inspectant le fichierpom.xml
. - La recherche (et la correction) des dépendances vulnérables est appelée scan de vulnérabilité.
Plugin de vulnérabilité IntelliJ
- IntelliJ dispose d'un plugin activé par défaut appelé : Package Checker
- En classe, vous avez vu une courte démo sur l'utilisation de l'outil.
- Lorsque le fichier
pom.xml
est ouvert, les dépendances vulnérables sont surlignées avec un fond différent. - En survolant les zones surlignées, des informations supplémentaires apparaissent sur la cause de la vulnérabilité (et sur la façon de la corriger).
- Lorsque le fichier
À vous de jouer
- Ouvrez votre projet
XoxFrontend
dans IntelliJ.- Double-cliquez sur le fichier
pom.xml
. - Inspectez attentivement la couleur de fond de toutes les lignes. Vous verrez certaines lignes mises en surbrillance par le plugin Package Checker.
- Survolez une ligne surlignée. Attendez une seconde, une fenêtre contextuelle apparaîtra.
- Double-cliquez sur le fichier
- Lisez attentivement le message, il vous expliquera comment corriger la vulnérabilité présente dans votre projet.
- Corrigez la vulnérabilité et assurez-vous que votre projet peut toujours être construit.
Sideloading
- Le sideloading fait référence à "contourner" l'algorithme de résolution des dépendances de Maven en injectant artificiellement un artefact directement dans le dépôt local
.m2
. - Cette technique permet d'utiliser des dépendances qui n'existent pas dans le dépôt officiel Maven en ligne, et qui ne pourraient donc pas être résolues automatiquement par Maven.
- L'astuce repose sur le fait que Maven ne cherchera jamais un artefact en ligne une fois qu'il a été trouvé dans le dépôt local.
Exemple :
XoxFrontend
aura besoin de fonctionnalités provenant du projet clonéXoxInternals
.- Dans ce cas,
XoxFrontend
peut déclarer une<dependency>
dans son fichierpom.xml
: - Si nous sideloadons
XoxInternals
dans le cache local.m2
, la dépendance sera directement : - Maven n'a pas besoin de chercher l'artefact sur le serveur en ligne (nous l'avons injecté nous-mêmes).
XoxFrontend
peut utiliser le code de la bibliothèque sans duplication de code.
- Dans ce cas,
Il existe différentes manières d'ajouter des artefacts au cache local .m2
.
Ne copiez pas le code !
Ne copiez pas de code depuis le projet cloné XoxInternals
. L'objectif est d'utiliser le code de XoxInternals
en tant que dépendance Maven, et non par duplication de code.
Sideloading depuis des projets Maven
- La forme la plus simple de sideloading se produit lorsque la dépendance en question est elle-même un projet Maven.
- Heureusement, c'est le cas.
XoxInternals
possède un fichierpom.xml
, et c'est un projet Maven. - Dans ce cas, la phase
install
peut être utilisée non seulement pour construire un artefact, mais aussi pour l'ajouter au cache local.
À vous de jouer
- Ajoutez le bloc
dependency
ci-dessus dans le fichierpom.xml
deXoxFrontend
.- Il apparaîtra en rouge. La dépendance ne peut pas être résolue, car aucun artefact de ce type n'est disponible sur les serveurs Maven officiels.
- Ensuite : Inspectez votre répertoire local
.m2
. Il peut être caché sur votre système. S'il n'est pas visible dans le gestionnaire de fichiers, ouvrez un terminal et accédez au répertoire caché.m2
dans votre dossier personnel.- Inspectez le contenu et vérifiez qu'il n'y a pas d'artefact
ca/uqam/info/xoxinternals
.
- Inspectez le contenu et vérifiez qu'il n'y a pas d'artefact
- Construisez le projet
XoxInternals
cloné précédemment en utilisant la phaseinstall
. - Inspectez à nouveau votre cache local
.m2
. Vous devriez maintenant voir un nouvel artefact dansca/uqam/info/xoxinternals
. - Revenez au fichier
pom.xml
deXoxFrontend
, la dépendance ne devrait plus apparaître en rouge, mais être correctement résolue.
Ensuite, nous voulons vérifier si nous pouvons réellement utiliser le code de la dépendance correctement résolue :
- Ajoutez un nouveau lanceur au projet
XoxFrontend
qui utilise des fonctionnalités du projetXoxInternals
. - Utilisez le code ci-dessous pour initialiser une nouvelle instance de jeu entre
Alice
etBob
:
Pour les deux appels manquants, vous pouvez utiliser les classes d'aide ci-dessous :
XoxGameInitiator.java
:public class XoxGameInitiator { /** * Creates a new game instance for the given player names. * * @param playerName1 name of first player. * @param playerName2 name of second player. * @return long value of a unique game ID, that can be used to interact with game instance via * manager. */ public static long createNewGame(String playerName1, String playerName2) { Player player1 = new Player(playerName1, "#0000FF"); Player player2 = new Player(playerName2, "#00FF00"); List<Player> playerList = new LinkedList<Player>(); playerList.add(player1); playerList.add(player2); XoxInitSettings xoxSettings = new XoxInitSettings(playerList, playerName1); return XoxManagerImpl.getInstance().addGame(xoxSettings); } }
XoxBoardAndActionPrinter.java
:public class XoxBoardAndActionPrinter { public static void printBoardAndActionsForPlayer(long gameId, String player) { System.out.println(XoxManagerImpl.getInstance().getBoard(gameId)); // Print all possible actions for Alice XoxClaimFieldAction[] actions = XoxManagerImpl.getInstance().getActions(gameId, player); for (int i = 0; i < actions.length; i++) { System.out.println(actions[i]); } } }
Configurez le plugin maven exec dans votre fichier pom.xml
et assurez-vous de pouvoir compiler et exécuter votre XoxFrontend
avec :
mvn clean compile exec:java
Sideloading depuis des fichiers JAR génériques
- Il arrive souvent que la bibliothèque qui vous manque ne soit pas fournie sous forme de code source, mais que vous n'ayez qu'un fichier JAR.
- Il est possible que la bibliothèque ne soit même pas un projet Maven !
- Il est néanmoins possible de sideloader directement à partir d'un fichier JAR, directement dans le cache du dépôt local
.m2
. - Dans le dernier cours, vous avez vu une commande Maven pour faire cela.
À vous de jouer
- Construisez le projet
XoxInternals
avecmvn clean package
. - Copiez le fichier
jar
depuis le répertoiretarget
vers votre bureau. - Supprimez le projet
XoxInternals
de votre disque dur, mais conservez le fichierjar
. - Supprimez l'artefact
XoxInternals
de votre cache local.m2
.
Videz votre dépôt local !
Les étapes ci-dessus sont importantes. À partir de l'exercice précédent, vous avez encore l'artefact XoxInternals
dans votre dépôt local.
À ce stade, le projet
XoxFrontend
ne fonctionnera plus, car nous avons supprimé manuellement les dépendances du projetXoxInternals
.
-
En utilisant uniquement le fichier
jar
sauvegardé, utilisez la commande Maven vue en classe pour installer directement un artefact depuis un fichierjar
. -
Assurez-vous d'utiliser le bon
groupId
,artifactId
etversion
, afin que la dépendance puisse être correctement résolue. -
Vérifiez le contenu de votre répertoire
.m2
local et assurez-vous que vous pouvez désormais reconstruire et exécuter l'applicationXoxFrontend
.
Dépôts tiers
Les dépôts tiers sont des dossiers en ligne fournissant des artefacts Maven non officiels. Il existe plusieurs raisons de placer des artefacts dans des dépôts tiers, notamment les restrictions d'accès et la possibilité de révoquer l'accès.
Obtenir une dépendance depuis un dépôt tiers
- Au lieu de sideloader manuellement l'artefact
XoxInternals
, il est préférable de récupérer l'artefact depuis un dépôt tiers non officiel. - Rappel : l'algorithme de résolution de Maven est :
- D'abord, chercher dans le cache local pour une correspondance (répertoire local
.m2
). - En dernier recours : chercher dans les dépôts tiers (s'ils sont définis).
- En dernier recours : chercher sur les serveurs Maven officiels.
- D'abord, chercher dans le cache local pour une correspondance (répertoire local
- Le sideloading cible la première étape. Les dépôts tiers ciblent la deuxième étape.
Dans l'exercice suivant, vous allez utiliser un dépôt tiers, au lieu de sideloader manuellement un artefact.
À vous de jouer
- Supprimez l'artefact
Xox
sideloadé de votre répertoire local~/.m2/repository/
.
Videz votre dépôt local !
Les étapes ci-dessus sont importantes. À partir de l'exercice précédent, vous avez encore l'artefact XoxInternals
dans votre dépôt local. Si vous ne le supprimez pas, Maven s'arrêtera à l'étape 1
et ne cherchera jamais l'artefact en ligne.
- Essayez de compiler votre projet
XoxFrontend
. Cela échouera, carXoxInternals
ne pourra plus être résolu. - Accédez au fichier
pom.xml
de votre projetXoxFrontend
et ajoutez une référence au dépôtinf2050
(qui est un dépôt Maven non officiel créé spécifiquement pour ce cours) : - Compilez à nouveau votre projet avec :
mvn clean test
. - Inspectez attentivement la sortie.
- Trouvez la ligne de sortie indiquant que l'artefact manquant a été téléchargé depuis le serveur tiers.
- Vérifiez que la construction a à nouveau réussi.
Pourquoi y a-t-il soudainement à nouveau une copie de XoxInternals
dans votre cache local ?
Maven met toujours en cache les artefacts localement, une fois qu'ils ont été récupérés avec succès. Sinon, vous ne pourriez jamais travailler hors ligne.
Profils de construction
- Les profils de construction permettent de développer différentes versions d'une application, en spécifiant simplement un nom de profil avec la commande Maven standard.
- La syntaxe est
-P
(pour profile), directement suivie du nom du profil.- Notez qu'il n'y a pas d'espace entre le switch
-P
et le nom du profil. - Exemple : mvn clean package
-Ppremium-software-version
- Notez qu'il n'y a pas d'espace entre le switch
Lanceurs alternatifs
- Un cas d'utilisation des profils de construction est la spécification de lanceurs alternatifs.
- De cette façon, différentes fonctionnalités d'un logiciel peuvent être lancées, ou différents paramètres codés en dur peuvent être utilisés.
À vous de jouer
- Revisez la syntaxe des profils de construction présentée lors du dernier cours et modifiez votre projet
XoxFrontend
pour contenir deux classes de lancement :- Pour le premier lanceur, vous pouvez réutiliser celui que vous avez codé pour Alice et Bob, plus tôt dans cette session de laboratoire.
- Un deuxième lanceur, pour initialiser une partie entre les joueurs
Eve
etBob
.
- Modifiez le fichier
pom.xml
du projetXoxFrontend
afin que deux nouveaux profils de construction soient définis :alice-and-bob
comme profil par défaut.eve-and-bob
comme profil alternatif.
- Pour chaque profil de construction, ajoutez une configuration du plugin
exec
, en vous assurant que :- Le profil
alice-and-bob
lance la classe Java originaleXoxAliceAndBob
. - Le profil
eve-and-bob
lance la nouvelle classe JavaXoxEveAndBob
créée.
- Le profil
- Construisez et lancez les deux variantes en utilisant l'une des commandes suivantes :
mvn clean compile exec:java
mvn clean compile exec:java -Palice-and-bob
mvn clean compile exec:java -Peve-and-bob
- Vérifiez que toutes les commandes affichent un tableau vide du jeu initialisé, mais que seul le profil
alice-and-bob
/par défaut
affiche également une liste des actions des joueurs.
Tests d'intégration
- Lors du dernier cours, vous avez appris que le cycle de vie par défaut de Maven définit deux phases de tests différentes :
test
pour l'exécution des tests unitaires.verify
pour l'exécution des tests d'intégration.
Tests unitaires vs Tests d'intégration
Les tests unitaires vérifient le comportement des classes ou des méthodes individuelles. Les tests d'intégration vérifient le comportement de l'interaction entre les composants, s'assurant que l'application dans son ensemble remplit correctement son objectif.
Mad Max
Une façon de tester l'intégration des implémentations de jeu, par exemple le projet XoxInternals
, est d'écrire des joueurs robots (ou, si vous voulez utiliser un langage à la mode, des "IA").
- Alors que les tests unitaires vérifient les fonctions individuelles du jeu, les joueurs robots tentent de jouer au jeu et vérifient ainsi s'il fonctionne de manière fiable.
- La façon la plus simple de tester cela est avec le joueur robot "Mad Max".
- Mad Max utilise un générateur aléatoire pour choisir une action au hasard, chaque fois que c'est à son tour de jouer.
- Deux instances concurrentes de Mad Max doivent finir par manœuvrer le jeu jusqu'à un état de fin de partie.
- Le test d'intégration est réussi si la partie se termine sans erreur dans les 5 secondes du processeur.
- Un générateur de nombres aléatoires avec une graine fixée peut être utilisé pour garantir des chemins d'exécution déterministes des joueurs robots.
- Pour des tests avancés,
1000
instances de jeu peuvent être testées, chacune avec une graine individuelle (par exemple, une valeur de graine incrémentée comme1
,2
,3
,...
).
Les tests d'intégration sont également des tests, et doivent donc être placés dans la structure de dossiers src/test/java
.
- Pour distinguer les tests unitaires standards (qui doivent être exécutés lors de la phase
test
) des tests d'intégration (qui doivent être exécutés lors de la phaseverify
), une syntaxe de nom de fichier spéciale peut être utilisée. - Les tests d'intégration doivent se terminer par
...IT.java
. - Une configuration supplémentaire dans le fichier
pom.xml
pour le plugin de test permet de séparer l'exécution en fonction du nom du fichier :<project> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.2</version> <configuration> <excludes> <exclude>**/*IT.java</exclude> </excludes> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>test</goal> </goals> <phase>integration-test</phase> <configuration> <excludes> <exclude>none</exclude> </excludes> <includes> <include>**/*IT.java</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
À vous de jouer
- Ajoutez le plugin
maven-surefire-plugin
ci-dessus à votre projetXoxFrontend
. - Créez une nouvelle classe de test
MadMaxIT.java
. - Dans
MadMaxIT
, créez une fonction décorée avec@Test
qui exécute 1000 parties différentes de Xox avec deux joueurs robots MadMax en compétition. -
Vérifiez que les tests d'intégration passent à travers les 1000 instances de jeu sans planter.
-
mvn clean test
ne doit pas exécuter les tests d'intégration. mvn clean verify
doit exécuter les tests d'intégration.
Vous pouvez utiliser les morceaux de code ci-dessous pour implémenter la fonctionnalité demandée :
/**
* Provides a random number between 0 and the provided value (exclusive).
*
* @param upperBoundExcluded as the maximum value that is guaranteed not to be reached.
*/
public int pickRandomAction(int upperBoundExcluded) {
return Math.abs(randomNumberGenerator.nextInt()) % upperBoundExcluded;
}
/**
* Initialize seeded random number generator. Each game test-run of two competing MadMax players
* should use a different seed.
*/
public void initializeRandomNumberGenerator(int seed) {
// For seed 42, will always generate:
// -1170105035 234785527 -1360544799 205897768 ...
randomNumberGenerator = new Random(seed);
}
/**
* The integration test creates 1000 test games and verifies that each of them is successfully
* finished by two competing MadMax robot players. Every game uses a different random generator
* seed.
*/
@Test
public void madMax() {
// Run 1000 random games. All must conclude in valid game ending
for (int i = 0; i < 1000; i++) {
runSeededRandomGame(i);
}
}
private void runSeededRandomGame(int seed) {
// TODO: implement missing functionality to finish seeded game
long gameId = XoxGameInitiator.createNewGame(playerNames[0], playerNames[1]);
while (!gameOver) {
// select a random move for the current player
XoxClaimFieldAction[] allPossibleActions =
gameManager.getActions(gameId, playerNames[currentPlayer]);
// pick and play the random move
gameManager.performAction(gameId, playerNames[currentPlayer], randomAction);
// advance current player and update game over status
gameOver = gameManager.getRanking(gameId).isGameOver();
}
}