Test de mutation avec PITest

1. Vue d'ensemble

Les tests logiciels font référence aux techniques utilisées pour évaluer la fonctionnalité d'une application logicielle. Dans cet article, nous allons discuter de certaines des mesures utilisées dans l'industrie des tests logiciels, telles que la couverture de code et les tests de mutation , avec un intérêt particulier sur la façon d'effectuer un test de mutation à l'aide de la bibliothèque PITest .

Dans un souci de simplicité, nous allons baser cette démonstration sur une fonction palindrome de base - Notez qu'un palindrome est une chaîne qui lit la même chose en arrière et en avant.

2. Dépendances de Maven

Comme vous pouvez le voir dans la configuration des dépendances Maven, nous utiliserons JUnit pour exécuter nos tests et la bibliothèque PITest pour introduire des mutants dans notre code - ne vous inquiétez pas, nous verrons dans une seconde ce qu'est un mutant. Vous pouvez toujours rechercher la dernière version des dépendances par rapport au référentiel central maven en suivant ce lien.

 org.pitest pitest-parent 1.1.10 pom  

Pour que la bibliothèque PITest soit opérationnelle, nous devons également inclure le plugin pitest-maven dans notre fichier de configuration pom.xml :

 org.pitest pitest-maven 1.1.10   com.baeldung.testing.mutation.*   com.baeldung.mutation.test.*    

3. Configuration du projet

Maintenant que nos dépendances Maven sont configurées, jetons un coup d'œil à cette fonction palindrome explicite:

public boolean isPalindrome(String inputString) { if (inputString.length() == 0) { return true; } else { char firstChar = inputString.charAt(0); char lastChar = inputString.charAt(inputString.length() - 1); String mid = inputString.substring(1, inputString.length() - 1); return (firstChar == lastChar) && isPalindrome(mid); } } 

Tout ce dont nous avons besoin maintenant, c'est d'un simple test JUnit pour nous assurer que notre implémentation fonctionne de la manière souhaitée:

@Test public void whenPalindrom_thenAccept() { Palindrome palindromeTester = new Palindrome(); assertTrue(palindromeTester.isPalindrome("noon")); } 

Jusqu'ici tout va bien, nous sommes prêts à exécuter notre scénario de test avec succès en tant que test JUnit.

Ensuite, dans cet article, nous allons nous concentrer sur la couverture du code et des mutations à l'aide de la bibliothèque PITest.

4. Couverture du code

La couverture de code a été largement utilisée dans l'industrie du logiciel, pour mesurer le pourcentage des chemins d'exécution qui a été exercé pendant les tests automatisés.

Nous pouvons mesurer la couverture de code efficace en fonction des chemins d'exécution à l'aide d'outils tels que Eclemma disponibles sur Eclipse IDE.

Après avoir exécuté TestPalindrome avec une couverture de code, nous pouvons facilement atteindre un score de couverture de 100% - Notez que isPalindrome est récursif, il est donc assez évident que la vérification de la longueur d'entrée vide sera de toute façon couverte.

Malheureusement, les métriques de couverture de code peuvent parfois être assez inefficaces , car un score de couverture de code de 100% signifie seulement que toutes les lignes ont été exercées au moins une fois, mais cela ne dit rien sur la précision des tests ou l' exhaustivité des cas d'utilisation , et c'est pourquoi le test de mutation est vraiment important.

5. Couverture des mutations

Le test de mutation est une technique de test utilisée pour améliorer l'adéquation des tests et identifier les défauts dans le code. L'idée est de modifier dynamiquement le code de production et de provoquer l'échec des tests.

Les bons tests doivent échouer

Chaque changement dans le code est appelé un mutant , et il en résulte une version modifiée du programme, appelée une mutation .

On dit que la mutation est tuée si elle peut provoquer un échec des tests. Nous disons également que la mutation a survécu si le mutant ne pouvait pas affecter le comportement des tests.

Maintenant, exécutons le test à l'aide de Maven, avec l'option goal définie sur: org.pitest: pitest-maven: mutationCoverage .

Nous pouvons vérifier les rapports au format HTML dans le répertoire target / pit-test / YYYYMMDDHHMI :

  • Couverture de ligne à 100%: 7/7
  • 63% de couverture des mutations: 5/8

De toute évidence, notre test balaie tous les chemins d'exécution, ainsi, le score de couverture de ligne est de 100%. En revanche, la bibliothèque PITest a introduit 8 mutants , 5 d'entre eux ont été tués - A causé un échec - mais 3 ont survécu.

Nous pouvons consulter le rapport com.baeldung.testing.mutation / Palindrome.java.html pour plus de détails sur les mutants créés:



Voici les mutateurs actifs par défaut lors de l'exécution d'un test de couverture de mutation:

  • INCREMENTS_MUTATOR
  • VOID_METHOD_CALL_MUTATOR
  • RETURN_VALS_MUTATOR
  • MATH_MUTATOR
  • NEGATE_CONDITIONALS_MUTATOR
  • INVERT_NEGS_MUTATOR
  • CONDITIONALS_BOUNDARY_MUTATOR

Pour plus de détails sur les mutateurs PITest, vous pouvez consulter le lien de la page de documentation officielle .

Notre score de couverture de mutation reflète le manque de cas de test , car nous ne pouvons pas nous assurer que notre fonction palindrome rejette les entrées de chaîne non palindromiques et quasi palindromiques.

6. Améliorer le score de mutation

Maintenant que nous savons ce qu'est une mutation, nous devons améliorer notre score de mutation en tuant les mutants survivants .

Prenons la première mutation - conditionnelle négée - à la ligne 6 comme exemple. Le mutant a survécu car même si nous modifions l'extrait de code:

if (inputString.length() == 0) { return true; }

À:

if (inputString.length() != 0) { return true; }

Le test passera, et c'est pourquoi la mutation a survécu . L'idée est de mettre en œuvre un nouveau test qui échouera, au cas où le mutant serait introduit . La même chose peut être faite pour les mutants restants.

@Test public void whenNotPalindrom_thanReject() { Palindrome palindromeTester = new Palindrome(); assertFalse(palindromeTester.isPalindrome("box")); } @Test public void whenNearPalindrom_thanReject() { Palindrome palindromeTester = new Palindrome(); assertFalse(palindromeTester.isPalindrome("neon")); }

Nous pouvons maintenant exécuter nos tests en utilisant le plugin de couverture des mutations, pour nous assurer que toutes les mutations ont été tuées , comme nous pouvons le voir dans le rapport PITest généré dans le répertoire cible.

  • Couverture de ligne à 100%: 7/7
  • Couverture de mutation à 100%: 8/8

7. Configuration des tests PITest

Mutation testing may be resources-extensive sometimes, so we need to put proper configuration in place to improve tests effectiveness. We can make use of the targetClasses tag, to define the list of classes to be mutated. Mutation testing cannot be applied to all classes in a real world project, as it will be time-consuming, and resource critical.

It is also important to define the mutators you plan to use during mutation testing, in order to minimize the computing resources needed to perform the tests:

  com.baeldung.testing.mutation.*   com.baeldung.mutation.test.*   CONSTRUCTOR_CALLS VOID_METHOD_CALLS RETURN_VALS NON_VOID_METHOD_CALLS  

Moreover, the PITest library offers a variety of options available to customize your testing strategies, you can specify the maximum number of mutants introduced by class using the maxMutationsPerClass option for example. More details about PITest options in the official Maven quickstart guide.

8. Conclusion

Note that code coverage is still an important metric, but sometimes it is not sufficient enough to guarantee a well-tested code. So in this article we've walked through mutation testing as a more sophisticated way to ensure tests quality and endorse test cases, using the PITest library.

Nous avons également vu comment analyser un rapport PITest de base tout en améliorant le score de couverture des mutations .

Même si le test de mutation révèle des défauts dans le code, il doit être utilisé à bon escient, car il s'agit d'un processus extrêmement coûteux et long .

Vous pouvez consulter les exemples fournis dans cet article dans le projet GitHub lié .