Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDev le 18/05/2018

545 views

Published on

On écrit tous des tests (n’est-ce pas ?), mais comment savoir s’ils sont utiles ?

- Par leur nombre ? Faux, beaucoup de tests ne garantissent pas que l’application fonctionne correctement
- Avec une bonne couverture du code ? Encore faux, mieux mais pas suffisant

L’important est d'être confiant sur la capacité des tests à détecter les problèmes (c’est pourquoi en TDD un test doit échouer au début, pour etre sur qu’il teste bien quelque chose). Laissez-moi donc vous présenter le mutation testing ! Cette technique modifie votre code, lance les tests et s’attend à ce qu’ils échouent. Si non, c’est que cette partie est mal testée… Dans ce talk je détaillerai les principes du mutation testing, expliquerai comment l’utiliser sur un projet scala et montrerai les résultats obtenus sur un projet réel.

Published in: Software
  • Be the first to comment

Mutation testing, enfin une bonne mesure de la qualité des tests ?, RivieraDev le 18/05/2018

  1. 1. Mutation testing Gotta Kill ’Em All ! Loïc Knuchel
  2. 2. Loïc Knuchel@loicknuchel Développeur Scala chez Organisateur des Paris Software craftsman loicknuchel@gmail.com DDD FP CQRS Hexagonal architecture Property based testing Event Sourcing Event Storming TDD Living documentation
  3. 3. Loïc Knuchel - @loicknuchel ● Peu de bugs ● Lisible par un autre développeur ● Pas trop dur à faire évoluer
  4. 4. Loïc Knuchel - @loicknuchel
  5. 5. Loïc Knuchel - @loicknuchel Stratégies de test
  6. 6. Loïc Knuchel - @loicknuchel Du code robuste grâce aux tests
  7. 7. Loïc Knuchel - @loicknuchel Tester les tests
  8. 8. Loïc Knuchel - @loicknuchel Etape 1: l’intuition
  9. 9. Loïc Knuchel - @loicknuchel Etape 2: couverture de code
  10. 10. Loïc Knuchel - @loicknuchel Solution 2: couverture de code
  11. 11. Loïc Knuchel - @loicknuchel Code exécuté par des tests != code testé class Cart(size: Int) { val items = mutable.ArrayBuffer[String]() def add(item: String): Boolean = { println(s"item add: $item") val exists = items.contains(item) if(items.length < size) { items.append(item) } exists } } it("has no assert") { new Cart(3).add("shoes") }
  12. 12. Loïc Knuchel - @loicknuchel Code exécuté par des tests != code testé it("has irrelevant assert") { new Cart(3).add("shoes") shouldBe false } class Cart(size: Int) { val items = mutable.ArrayBuffer[String]() def add(item: String): Boolean = { println(s"item add: $item") val exists = items.contains(item) if(items.length < size) { items.append(item) } exists } }
  13. 13. Loïc Knuchel - @loicknuchel Code exécuté par des tests != code testé class Cart(size: Int) { val items = mutable.ArrayBuffer[String]() def add(item: String): Boolean = { println(s"item add: $item") val exists = items.contains(item) if(items.length < size) { items.append(item) } exists } } Non testé : ● les effets de bords ● la condition limite ● l’ajout dans la liste it("asserts few things") { val cart = new Cart(3) cart.add("shoes") cart.items.length shouldBe 1 }
  14. 14. Loïc Knuchel - @loicknuchel Code exécuté par des tests != code testé
  15. 15. Loïc Knuchel - @loicknuchel Tout le code ne se vaut pas
  16. 16. Loïc Knuchel - @loicknuchel Etape 3 Mutation testing
  17. 17. Loïc Knuchel - @loicknuchel Mise en place: Java // pom.xml <build> <plugins> <plugin> <groupId>org.pitest</groupId> <artifactId>pitest-maven</artifactId> <version>1.2.4</version> </plugin> </plugins> </build> $ mvn org.pitest:pitest-maven:mutationCoverage
  18. 18. Loïc Knuchel - @loicknuchel Mise en place: Scala // project/plugins.sbt addSbtPlugin("io.github.sugakandrey" % "sbt-scalamu" % "0.1.1") $ sbt mutationTest Scalamu
  19. 19. Loïc Knuchel - @loicknuchel Mise en place: JavaScript $ ./node_modules/.bin/stryker run // stryker.conf.js module.exports = function(config) { config.set({ testRunner: "jest", mutator: "javascript", transpilers: [], reporter: ["html", "progress"], coverageAnalysis: "all", mutate: ["src/**/*.js"] }); }; $ npm install stryker stryker-api stryker-html-reporter stryker-javascript-mutator stryker-jest-runner --save- dev Stryker
  20. 20. Loïc Knuchel - @loicknuchel Et plein d’autres... NinjaTurtles pour C# Mutmut pour Python mutant pour Ruby Infection pour PHP ...
  21. 21. Loïc Knuchel - @loicknuchel Mutation testing
  22. 22. Loïc Knuchel - @loicknuchel Génère un mutant Lance les tests Vérifie le résultat Recommence
  23. 23. Loïc Knuchel - @loicknuchel Mutant tué Si les tests échouent Le code muté a été détecté Il est donc correctement testé
  24. 24. Loïc Knuchel - @loicknuchel Mutant vivant Si les tests réussissent Le code muté n’a pas été détecté Les tests sont donc insuffisants
  25. 25. Loïc Knuchel - @loicknuchel Qu’est-ce qu’un mutant ? def add(item: String): Boolean = { println(s"item add: $item") val exists = items.contains(item) if (items.length < size) { items.append(item) } exists } Code original def add(item: String): Boolean = { () val exists = items.contains(item) if (items.length < size) { items.append(item) } exists } Supprime un appel de fonction def add(item: String): Boolean = { println(s"item add: $item") val exists = items.contains(item) if (true) { items.append(item) } exists } Condition toujours vraie
  26. 26. Loïc Knuchel - @loicknuchel Mutations: conditions Modification : < ⇔ <= > ⇔ >= && ⇔ || Constante : true false Inversion : == ⇔ != < ⇔ >= > ⇔ <= cond ⇔ !cond if (items.size() < size) { items.add(item); } if (items.size() <= size) { items.add(item); }
  27. 27. Loïc Knuchel - @loicknuchel Mutations: opération mathématique Opérations : x++ ⇔ x-- + ⇔ - * ⇔ / % ⇔ * Opérations binaires : & ⇔ | ^ ⇔ & >> ⇔ << return total - discount; return total + discount;
  28. 28. Loïc Knuchel - @loicknuchel Mutations: constantes Change une constante : true ⇔ false 0 ⇔ 1 x ⇔ x + 1 x ⇔ null Remplace une variable par une constante : x ⇔ true / false x ⇔ 0 / 1 / 2 return exists; if(exists == null) throw new RuntimeException(); else return null;
  29. 29. Loïc Knuchel - @loicknuchel Mutations: supprimer une fonction println(s"item add: $item")
  30. 30. Loïc Knuchel - @loicknuchel Mutations
  31. 31. Loïc Knuchel - @loicknuchel En pratique ● mutants uniquement pour le code couvert par les tests ● lance uniquement les tests qui couvrent le code muté ● fonctionne en mode itératif ● à mettre en priorité pour le code critique ● activer que les mutations intéressantes Brute force
  32. 32. Loïc Knuchel - @loicknuchel Exemple /** * Take a list of item prices and calculate the bill : * - if total is higher than 50, apply 10% overall discount * - if more than 5 items, apply 100% discount on cheapest one * - if many discount apply, use the higher one */ public static Double getPrice(List<Double> prices) { Double total = sum(prices); Double discount = 0.0; if (total >= 50) { discount = total * 0.1; } if (prices.size() >= 5) { Double minPrice = min(prices); if (minPrice > discount) { discount = minPrice; } } return total - discount; }
  33. 33. Loïc Knuchel - @loicknuchel Test 1 @Test public void getPrice_should_be_normal_price_with_few_and_cheap_items() { assertEquals(24, Demo.getPrice(Arrays.asList(4, 7, 1, 12), 0.001); }
  34. 34. Loïc Knuchel - @loicknuchel Test 1
  35. 35. Loïc Knuchel - @loicknuchel Test 1 + 2 @Test public void getPrice_should_be_get_10pc_discound_on_expensive_items() { assertEquals(54, Demo.getPrice(Arrays.asList(10, 20, 30)), 0.001); }
  36. 36. Loïc Knuchel - @loicknuchel Test 1 + 2
  37. 37. Loïc Knuchel - @loicknuchel Test 1 + 2 + 3 @Test public void getPrice_should_be_get_one_free_item_when_buy_many() { assertEquals(22, Demo.getPrice(Arrays.asList(3, 5, 2, 8, 1, 4)), 0.001); }
  38. 38. Loïc Knuchel - @loicknuchel Test 1 + 2 + 3
  39. 39. Loïc Knuchel - @loicknuchel Test 1 + 2 + 3 + 4 @Test public void getPrice_should_should_test_boundary_conditions() { // 50 total value boundary assertEquals(45, Demo.getPrice(Arrays.asList(5, 10, 15, 20)), 0.001); // 5 item boundary assertEquals(43, Demo.getPrice(Arrays.asList(7, 8, 15, 10, 10)), 0.001); }
  40. 40. Loïc Knuchel - @loicknuchel Test 1 + 2 + 3 + 4
  41. 41. Loïc Knuchel - @loicknuchel Code source https://github.com/loicknuchel/mutation-testing-sample Java / Scala / JavaScript / PR acceptées ;)
  42. 42. Loïc Knuchel - @loicknuchel Conclusion Impossible à contourner Long à exécuter Couplage code ⇔ tests Impossible à tuer Tester ses tests Meilleure couverture de code ! Facile à mettre en place
  43. 43. Loïc Knuchel - @loicknuchel Questions ? Slides: http://bit.ly/ rivieradev-2018-mutation-testing

×