Atelier 09
Dans cette session de laboratoire, vous pratiquerez deux aspects avancés du développement logiciel : les tests
de mutation et les mock tests.
Pour les exercices de cette dernière session de laboratoire, vous travaillerez avec le
projet PrimeNumbers et le project
MockServerExercise
disponibles sur GitLab.
Tests de mutation
- Les tests de mutation sont un test de stress automatisé pour les tests unitaires et d'intégration existants.
- L'idée principale est d'injecter des erreurs dans un programme supposé correct et de vérifier si au moins un test échoue pour chaque erreur injectée.
- Chaque variante du logiciel original injectée d'une erreur est appelée un mutant.
- Si aucun test n'est capable de détecter la modification artificielle du programme, les tests associés sont considérés comme contenant des zombies, c'est-à-dire des tests inutiles ou insuffisants.
Pitest
- Le plugin pitest est une extension pratique au processus de construction Maven.
-
L'installation est aussi simple que d'ajouter une dépendance et un plugin suivant dans la configuration
pom.xml:- Dépendance:
- Plugin:
<!-- Mutation test report--> <!-- HTML report available at: target/pit-reports/index.html --> <plugin> <groupId>org.pitest</groupId> <artifactId>pitest-maven</artifactId> <version>1.17.1</version> <configuration> <mutationThreshold>65</mutationThreshold> </configuration> <!-- Mutation tests are expensive (slow), and there's no point in executing them if common tests fail. Therefore we execute them later, in the verify phase.--> <executions> <execution> <id>mutation-tests</id> <goals> <goal>mutationCoverage</goal> </goals> <phase>verify</phase> </execution> </executions> </plugin>
-
Ensuite, la création d'un rapport de test de mutation est déclenchée par la phase
test, c'est-à-diremvn clean test - Le rapport est disponible dans
target/pit-reports/index.htmlet peut être consulté avec n'importe quel navigateur web.
À vous de jouer
Exemple de configuration du code :
- Clonez le projet de vérification des nombres premiers (sous-répertoire
l11/PrimeChecker) :
- Ouvrez le projet dans IntelliJ
- Exécutez les tests fournis, en utilisant
mvn clean test
Tous les tests réussiront, mais cela signifie-t-il que vos tests sont bons ? Voyons cela :
- Activez le plugin de test de mutation.
- Exécutez à nouveau les tests et générez un rapport de test de mutation :
mvn clean test - Consultez le rapport de test de mutation dans votre navigateur.
Le rapport vous indiquera une mutation de négation qui peut être ajoutée à votre code sans que les tests ne le remarquent.
- Identifiez quelle mutation il s'agit, c'est-à-dire quelle ligne, quel changement.
- Vérifiez la mutation, en modifiant effectivement le code de la même manière (votre code comptera désormais les nombres non-premiers au lieu des nombres premiers).
- Exécutez à nouveau les tests.
Que devrait-il se passer ?
Les résultats des tests devraient encore être tous réussis. Les résultats du test de mutation indiquent que vos tests ne sont pas capables d'identifier la modification de votre code source.
Enfin, il est temps de corriger le problème et d'améliorer vos tests !
- Revoyez vos tests. Identifiez le problème avec vos tests existants.
- Modifiez les tests existants ou ajoutez-en de nouveaux.
- Exécutez à nouveau les tests de mutation et vérifiez que la couverture des mutations est désormais de 100%.
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 :
testpour l'exécution des tests unitaires.verifypour 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,
1000instances 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.xmlpour 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-pluginci-dessus à votre projetXoxFrontend. - Créez une nouvelle classe de test
MadMaxIT.java. - Dans
MadMaxIT, créez une fonction décorée avec@Testqui 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 testne doit pas exécuter les tests d'intégration. mvn clean verifydoit 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();
}
}
Moquer
- Dans le dernier cours, vous avez vu un exemple de dépendance d'un SUT (base de données), qui devait être remplacée par un Mock pour faciliter les tests.
- Dans ce qui suit, vous allez appliquer les mêmes concepts à une application différente, le
TagCounter. - Le cas d'utilisation est identique : nous avons un SUT (classe à tester), qui fait une seule chose : compter les balises HTML.
Exemple
Cet échantillon HTML :
possède 4 tags:
<html><h1></h1></html>
Dependency illustration
Malheureusement, nous ne pouvons pas tester notre TagCounter de manière isolée. Pour l'instancier, nous avons
également besoin d'un objet ServerFileDownloader,
qui fournit le code HTML en le téléchargeant depuis un vrai
serveur : Wikipédia
---
title: SUT needs a ServerFileDownloader object
---
classDiagram
TagCounter *--> ServerFileDownloader: has a
class ServerFileDownloader {
<<Class>>
+getWebpageContent() void
}
class TagCounter {
<<Class>>
+TagCounter(ServerFileDownloader) TagCounter
+countTags() int
}
À vous de jouer
- Téléchargez le projet ServerMockExercise préparé.
- Exécutez l'application et familiarisez-vous avec le code existant.
- Modifiez uniquement le code de test pour permettre le test avec mock
- Ajoutez
Mockitocomme dépendance de test
dans le fichierpom.xml. - Ouvrez la classe de test préparée :
TagCounterTestet utilisez les annotations vues en cours pour remplacer la
dépendance du serveur par une dépendance mock. - Ajoutez une instruction
when-thenReturnspour préparer une classe de test. Retournez une page web minimale,
par exemple celle listée ci-dessus. - Ajoutez une instruction
assertà votre test pour vérifier que leTagCounterfonctionne correctement. - Utilisez un
Captorpour vérifier que l'objet mockServerFileDownloaderest correctement invoqué.
- Ajoutez