Successfully reported this slideshow.
Your SlideShare is downloading. ×

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

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

Download to read offline

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.

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.

More Related Content

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

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

Editor's Notes

  • Bonjour à tous,
    Bienvenu à mon talk sur le mutation testing.
  • Je me présente, Loïc Knuchel, je suis développeur Scala chez Criteo, j’organise les HumanTalks et j’essaie de faire du bon code…
    Je m’intéresse notamment à tous ces sujets, et maintenant aussi au mutation testing
  • En tant que développeurs, on essaie tous de faire du bon code
    Mais on a pas forcément tous la même définition du “bon code”
    A minima on peut probablement s’entendre sur le fait que le code doit :
    avoir un minimum de bugs
    rester lisible pour les autres développeurs
    ne pas demander des efforts disproportionnés pour être modifié
  • Et bien sûr, pour le premier point, on va écrire des tests
  • Plein de tests
    Des tests unitaires qui vont vérifier que chaque morceau de code fait bien ce qu’on attend de lui
    Des tests d’intégration qui vont s’assurer que les composants n’aient pas d’incompatibilités
    Des tests de bout en bout pour vérifier que l’application est capable d’exécuter les actions voulues par l’utilisateur
    Parfois des tests de charge pour prévenir les indisponibilités liées au nombre de requêtes
    Voire même des chaos monkey tests pour être certain que même en cas de problème, on puisse conserver un certain service
  • Et tout ça dans un seul but: avoir une application qui fonctionne au maximum comme attendu

    Mais comment savoir si nos tests garantissent vraiment ça ?
  • On va devoir tester nos tests => testception
  • Solution 1: le feeling
    On connaît bien notre application
    On a écrit plein de tests
    On a peu de problèmes en prod
    Bref, on se dit que ça ne doit pas être mal
    Et si on est sérieux avec ça, c’est déjà un très bon point
  • Solution 2: la couverture de code
    Qui connait ?
    Ici on lance nos tests et on regarde quels sont les lignes qui ont été exécutées.
  • ça donne une première idée de la qualité de nos tests
    clairement, avec une couverture de code faible, on comprends vite que les parties non couvertes sont à risque car pas du tout exécutées lors des tests
    mais la réciproque n’est pas vraie, une bonne couverture de code ne garantit absolument pas des bon tests.
  • Lorsque le code est exécuté, il n’est pas forcément vérifié => assert manquant ou assert que sur une partie des résultats ou assert non pertinent
    c’est pourquoi quand on écrit un test, on doit toujours le faire échouer avant de le faire réussir, mais ça, c’est possible uniquement lorsqu’on le crée, après impossible de s’en assurer
    on peut aussi avoir un effet difficilement testable voire invisible du point de vue des tests => ex: logger
    dans ce cas il est exécuté du coup il compte dans la couverture de code, mais pas vérifié
  • Lorsque le code est exécuté, il n’est pas forcément vérifié => assert manquant ou assert que sur une partie des résultats ou assert non pertinent
    c’est pourquoi quand on écrit un test, on doit toujours le faire échouer avant de le faire réussir, mais ça, c’est possible uniquement lorsqu’on le crée, après impossible de s’en assurer
    on peut aussi avoir un effet difficilement testable voire invisible du point de vue des tests => ex: logger
    dans ce cas il est exécuté du coup il compte dans la couverture de code, mais pas vérifié
  • Lorsque le code est exécuté, il n’est pas forcément vérifié => assert manquant ou assert que sur une partie des résultats ou assert non pertinent
    c’est pourquoi quand on écrit un test, on doit toujours le faire échouer avant de le faire réussir, mais ça, c’est possible uniquement lorsqu’on le crée, après impossible de s’en assurer
    on peut aussi avoir un effet difficilement testable voire invisible du point de vue des tests => ex: logger
    dans ce cas il est exécuté du coup il compte dans la couverture de code, mais pas vérifié

    On peut aussi oublier de tester une valeur particulière importante => condition aux limites
  • Comme toute métrique, elle peut être détournée => introspection pour tout exécuter en un test
  • et enfin, toutes les lignes de code ne se valent pas, certaines sont beaucoup plus importantes que d’autres et on veut s’assurer que les parties importantes sont très bien testées
    la couverture de code donne principalement un chiffre global pas forcément pertinent

    La couverture de code, c’est bien mais loin d’être suffisant.
  • Heureusement, aujourd’hui je vous présente la solution ultime !!!
    Ou presque…
    Enfin, une meilleure solution que les précédentes en tout cas ;)

    Le mutation testing !
    Contrairement à ce que son nom pourrait faire penser, c’est pas une nouvelle méthode de test mais une méthode pour évaluer la qualité des tests, un peu comme la couverture de code
    D’ailleurs, comme la couverture de code, c’est une technique non-intrusive pour le code de l’application
    Il suffit simplement de configurer un outil, qui peut être totalement extérieur au code !

    Le mutation testing peut être utilisé dans n’importe quel langage mais chaque langage a son outil spécifique.
  • Par exemple en Java il y a PIT
    Et pour l’utiliser il suffit de l’ajouter au build et de le lancer
    C’est aussi simple que ça ! (bien sûr il y a pas mal d’options si on souhaite mais la première intégration est triviale)
  • En scala il y a scalamu et de la même manière, il suffit de l’ajouter comme plugin et de le lancer
  • Et enfin, en JavaScript on a stryker
  • Mais on a toujours pas parlé de ce que fait le mutation testing…
  • Le principe est très simple
    Le framework de mutation testing va créer des mutants et vérifier s’ils sont détectés pour les tests
  • bien sûr, tout ça est assez coûteux en terme de performances…
    Il est important que les tests s’exécutent rapidement et n’aient pas une portée trop large
    Et bien sûr il y a quelques optimisations possibles pour améliorer ça

×