Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mutation testing : y a-t-il des trous dans votre couverture de test ?

Benjamin Cavy
September 10, 2021

Mutation testing : y a-t-il des trous dans votre couverture de test ?

Benjamin Cavy

September 10, 2021
Tweet

More Decks by Benjamin Cavy

Other Decks in Technology

Transcript

  1. Speaker : Benjamin Cavy - @benjamin_cavy Mutation Testing Y a-t-il

    des trous dans votre couverture de test ?
  2. #JSC2021 @benjamin_cavy 😱 Est-ce que mes tests sont bien ?

    🤔 Comment écrire de bons tests ? ❓ C’est quoi un bon test ?
  3. #JSC2021 @benjamin_cavy Pourquoi testons-nous ? 🕵 Préciser le besoin (TDD)

    🧩 Tester des cas difficiles à avoir en vrai 🚨 Pour assurer la non régression
  4. #JSC2021 @benjamin_cavy Non régression : c’est cool 📡 Vérifie qu’on

    a pas cassé une fonctionnalité existante ⛱ Allège (un peu) la charge de revue de code 👷 Facilite l’arrivé de nouveaux développeurs ⏱ Réduit la charge de tests “manuels”
  5. #JSC2021 @benjamin_cavy Code coverage : le faux-ami ? 😀 Indique

    quelles lignes de codes sont exécutées par les tests… ☹ … mais ne garantit en aucun cas leur bon fonctionnement 😭 est très (trop ? ) aimé, mais mal compris des non-techniciens
  6. #JSC2021 @benjamin_cavy 🤔 Comment s’assurer que nos tests détectent les

    régressions ? 💡En les exécutant sur du code contenant des régressions !
  7. #JSC2021 @benjamin_cavy Mutation testing a.k.a. le collègue qui pète tout

    🧟 Créé des versions “mutantes” du code testé en le modifiant 🔫 Exécute les tests unitaires et vérifie qu’ils échouent 📈 Le taux de mutant ainsi tués indique la sensibilité aux régressions de vos tests
  8. #JSC2021 @benjamin_cavy public double withdraw(String id, double amount) { double

    balance = readBalanceForAccount(id); if(amount > balance) { throw new RuntimeException( "Insufficient balance" ); } double newBalance = balance - amount; updateAccountBalance(id, newBalance); return newBalance; } @Test public void simpleTest() { BankService bankService = new BankService(); bankService.open("benjamin", 100); assertThat(bankService.withdraw("benjamin", 25)) .isEqualTo(75); } Exemple
  9. #JSC2021 @benjamin_cavy @Test public void simpleTest() { BankService bankService =

    new BankService(); bankService.open("benjamin", 100); assertThat(bankService.withdraw("benjamin", 25)) .isEqualTo(75); } public double withdraw(String id, double amount) { double balance = readBalanceForAccount(id); if(amount > balance) { throw new RuntimeException( "Insufficient balance" ); } double newBalance = balance - amount; updateAccountBalance(id, newBalance); return newBalance; } → double balance = 0; ✅
  10. #JSC2021 @benjamin_cavy @Test public void simpleTest() { BankService bankService =

    new BankService(); bankService.open("benjamin", 100); assertThat(bankService.withdraw("benjamin", 25)) .isEqualTo(75); } public double withdraw(String id, double amount) { double balance = readBalanceForAccount(id); if(amount > balance) { throw new RuntimeException( "Insufficient balance" ); } double newBalance = balance - amount; updateAccountBalance(id, newBalance); return newBalance; } if(amount >= balance) { if(amount < balance) { if(false) { if(true) { ❌ ✅ ❌ ✅
  11. #JSC2021 @benjamin_cavy @Test public void simpleTest() { BankService bankService =

    new BankService(); bankService.open("benjamin", 100); assertThat(bankService.withdraw("benjamin", 25)) .isEqualTo(75); } public double withdraw(String id, double amount) { double balance = readBalanceForAccount(id); if(amount > balance) { throw new RuntimeException( "Insufficient balance" ); } double newBalance = balance - amount; updateAccountBalance(id, newBalance); return newBalance; } → double newBalance = balance + amount; ✅
  12. #JSC2021 @benjamin_cavy @Test public void simpleTest() { BankService bankService =

    new BankService(); bankService.open("benjamin", 100); assertThat(bankService.withdraw("benjamin", 25)) .isEqualTo(75); } public double withdraw(String id, double amount) { double balance = readBalanceForAccount(id); if(amount > balance) { throw new RuntimeException( "Insufficient balance" ); } double newBalance = balance - amount; updateAccountBalance(id, newBalance); return newBalance; } ❌
  13. #JSC2021 @benjamin_cavy @Test public void simpleTest() { BankService bankService =

    new BankService(); bankService.open("benjamin", 100); assertThat(bankService.withdraw("benjamin", 25)) .isEqualTo(75); } public double withdraw(String id, double amount) { double balance = readBalanceForAccount(id); if(amount > balance) { throw new RuntimeException( "Insufficient balance" ); } double newBalance = balance - amount; updateAccountBalance(id, newBalance); return newBalance; } → return 0; ✅
  14. #JSC2021 @benjamin_cavy Conclusion ✅ Complète le code coverage ✅ Permet

    de détecter les angles morts dans les tests ✅ Intégrable sans effort ✅ Bien si beaucoup de métier ✅ Très configurable ❌ Lents si les tests sont lents ❌ Attention à l’intégration à la CI ❌ Effort d’analyse sur les résultats
  15. #JSC2021 @benjamin_cavy Pitest : fonctionnalités supplémentaires ⏱ Restrisction de l’analyse

    aux classes modifiées (git) ⏱ ⏱ Analyse incrémentale ✨D’autres mutations disponibles 🏹 Possibilité d’affiner les classes mutées
  16. #JSC2021 @benjamin_cavy Faut-il tuer tous les mutants ? Les sournoises

    mutations équivalentes int i = 2; if(i >= 1) { //... } int i = 2; if(i > 1) { //... }
  17. #JSC2021 @benjamin_cavy Exemple sur un gros projet ================================================================================ - Timings

    ================================================================================ > scan classpath : < 1 second > coverage and dependency analysis : 56 seconds > build mutation tests : 5 seconds > run mutation analysis : 4 hours, 7 minutes and 25 seconds -------------------------------------------------------------------------------- > Total : 4 hours, 8 minutes and 27 seconds -------------------------------------------------------------------------------- ================================================================================ - Statistics ================================================================================ >> Generated 15727 mutations Killed 5257 (33%) >> Ran 17646 tests (1.12 tests per mutation)
  18. #JSC2021 @benjamin_cavy Exemple avec analyse incrémentale ================================================================================ - Timings ================================================================================

    > scan classpath : < 1 second > coverage and dependency analysis : 1 minutes and 5 seconds > build mutation tests : 26 seconds > run mutation analysis : 38 seconds -------------------------------------------------------------------------------- > Total : 2 minutes and 11 seconds -------------------------------------------------------------------------------- ================================================================================ - Statistics ================================================================================ >> Generated 15727 mutations Killed 5484 (35%) >> Ran 0 tests (0 tests per mutation)
  19. #JSC2021 @benjamin_cavy Pitest : mise en place <build> <plugins> <plugin>

    <!-- mvn org.pitest:pitest-maven:mutationCoverage --> <groupId>org.pitest</groupId> <artifactId>pitest-maven</artifactId> <version>1.6.1</version> <configuration> <mutators> <mutator>DEFAULTS</mutator> <mutator>REMOVE_CONDITIONALS</mutator> </mutators> </configuration> </plugin> </plugins> </build>
  20. #JSC2021 @benjamin_cavy Pour les autres langages ✅ JavaScript, C# &

    Scala : Stryker ✅ PHP : infection ✅ Python : mutmut … et plein d’autres