SlideShare une entreprise Scribd logo
1  sur  43
Télécharger pour lire hors ligne
Mutation testing
Gotta Kill ’Em All !
Loïc Knuchel
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
Loïc Knuchel - @loicknuchel
● Peu de bugs
● Lisible par un autre
développeur
● Pas trop dur à faire
évoluer
Loïc Knuchel - @loicknuchel
Loïc Knuchel - @loicknuchel
Stratégies de test
Loïc Knuchel - @loicknuchel
Du code
robuste grâce
aux tests
Loïc Knuchel - @loicknuchel
Tester les
tests
Loïc Knuchel - @loicknuchel
Etape 1: l’intuition
Loïc Knuchel - @loicknuchel
Etape 2: couverture de code
Loïc Knuchel - @loicknuchel
Solution 2: couverture de code
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")
}
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
}
}
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
}
Loïc Knuchel - @loicknuchel
Code exécuté par des tests != code testé
Loïc Knuchel - @loicknuchel
Tout le code ne se vaut pas
Loïc Knuchel - @loicknuchel
Etape 3
Mutation testing
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
Loïc Knuchel - @loicknuchel
Mise en place: Scala
// project/plugins.sbt
addSbtPlugin("io.github.sugakandrey" % "sbt-scalamu" % "0.1.1")
$ sbt
mutationTest
Scalamu
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
Loïc Knuchel - @loicknuchel
Et plein
d’autres...
NinjaTurtles pour C#
Mutmut pour Python
mutant pour Ruby
Infection pour PHP
...
Loïc Knuchel - @loicknuchel
Mutation testing
Loïc Knuchel - @loicknuchel
Génère un mutant
Lance les tests
Vérifie le résultat
Recommence
Loïc Knuchel - @loicknuchel
Mutant tué
Si les tests échouent
Le code muté a été détecté
Il est donc correctement testé
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
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
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);
}
Loïc Knuchel - @loicknuchel
Mutations: opération mathématique
Opérations :
x++ ⇔ x--
+ ⇔ -
* ⇔ /
% ⇔ *
Opérations binaires :
& ⇔ |
^ ⇔ &
>> ⇔ <<
return total - discount; return total + discount;
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;
Loïc Knuchel - @loicknuchel
Mutations: supprimer une fonction
println(s"item add: $item")
Loïc Knuchel - @loicknuchel
Mutations
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
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;
}
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);
}
Loïc Knuchel - @loicknuchel
Test 1
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);
}
Loïc Knuchel - @loicknuchel
Test 1 + 2
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);
}
Loïc Knuchel - @loicknuchel
Test 1 + 2 + 3
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);
}
Loïc Knuchel - @loicknuchel
Test 1 + 2 + 3 + 4
Loïc Knuchel - @loicknuchel
Code source
https://github.com/loicknuchel/mutation-testing-sample
Java / Scala / JavaScript / PR acceptées ;)
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
Loïc Knuchel - @loicknuchel
Questions ?
Slides:
http://bit.ly/
rivieradev-2018-mutation-testing

Contenu connexe

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

Formation Gratuite Total Tests par les experts Java Ippon
Formation Gratuite Total Tests par les experts Java Ippon Formation Gratuite Total Tests par les experts Java Ippon
Formation Gratuite Total Tests par les experts Java Ippon Ippon
 
Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016
Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016
Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016Fabien Arcellier
 
testUnitaire (1).pptx
testUnitaire (1).pptxtestUnitaire (1).pptx
testUnitaire (1).pptxManalAg
 
Tester unitairement une application java
Tester unitairement une application javaTester unitairement une application java
Tester unitairement une application javaAntoine Rey
 
Integration continue - Introduction
Integration continue - IntroductionIntegration continue - Introduction
Integration continue - IntroductionOlivier ETIENNE
 
PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...
PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...
PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...Cyrille Grandval
 
Les générateurs de code, pour se simplifier la vie au quotidien
Les générateurs de code, pour se simplifier la vie au quotidienLes générateurs de code, pour se simplifier la vie au quotidien
Les générateurs de code, pour se simplifier la vie au quotidienNicolas Carlo
 
l'Industrialisation (avec PHP) @MMIConnect
l'Industrialisation (avec PHP) @MMIConnectl'Industrialisation (avec PHP) @MMIConnect
l'Industrialisation (avec PHP) @MMIConnectFlorent DENIS
 
Automatisation des tests - objectifs et concepts - partie 2
Automatisation des tests  - objectifs et concepts - partie 2Automatisation des tests  - objectifs et concepts - partie 2
Automatisation des tests - objectifs et concepts - partie 2Christophe Rochefolle
 
Automatisation des tests - objectifs et concepts - partie 1
Automatisation des tests  - objectifs et concepts - partie 1Automatisation des tests  - objectifs et concepts - partie 1
Automatisation des tests - objectifs et concepts - partie 1Christophe Rochefolle
 
Plpython et Triggers
Plpython et TriggersPlpython et Triggers
Plpython et TriggersAffinitic
 
Design Pattern introduction
Design Pattern introductionDesign Pattern introduction
Design Pattern introductionneuros
 
Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...
Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...
Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...FactoVia
 
Plop : un micro-générateur pour se simplifier la vie au quotidien
Plop : un micro-générateur pour se simplifier la vie au quotidienPlop : un micro-générateur pour se simplifier la vie au quotidien
Plop : un micro-générateur pour se simplifier la vie au quotidienNicolas Carlo
 
Industrialiser le contrat dans un projet PHP
Industrialiser le contrat dans un projet PHPIndustrialiser le contrat dans un projet PHP
Industrialiser le contrat dans un projet PHPhalleck45
 
PyConFR - testons en python
PyConFR - testons en pythonPyConFR - testons en python
PyConFR - testons en pythongburet
 
Python For Data Science - French Course
Python For Data Science - French CoursePython For Data Science - French Course
Python For Data Science - French CourseHaytam EL YOUSSFI
 

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

Formation Gratuite Total Tests par les experts Java Ippon
Formation Gratuite Total Tests par les experts Java Ippon Formation Gratuite Total Tests par les experts Java Ippon
Formation Gratuite Total Tests par les experts Java Ippon
 
Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016
Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016
Infra as Code, choisissez vous la pilule rouge ou la pilule bleue - Devoxx 2016
 
Bbl sur les tests
Bbl sur les testsBbl sur les tests
Bbl sur les tests
 
testUnitaire (1).pptx
testUnitaire (1).pptxtestUnitaire (1).pptx
testUnitaire (1).pptx
 
Tester unitairement une application java
Tester unitairement une application javaTester unitairement une application java
Tester unitairement une application java
 
Integration continue - Introduction
Integration continue - IntroductionIntegration continue - Introduction
Integration continue - Introduction
 
PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...
PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...
PHPTour Lyon 2014 - Conférence - Tests unitaires Je veux mes 80% de couvertur...
 
Les générateurs de code, pour se simplifier la vie au quotidien
Les générateurs de code, pour se simplifier la vie au quotidienLes générateurs de code, pour se simplifier la vie au quotidien
Les générateurs de code, pour se simplifier la vie au quotidien
 
l'Industrialisation (avec PHP) @MMIConnect
l'Industrialisation (avec PHP) @MMIConnectl'Industrialisation (avec PHP) @MMIConnect
l'Industrialisation (avec PHP) @MMIConnect
 
Automatisation des tests - objectifs et concepts - partie 2
Automatisation des tests  - objectifs et concepts - partie 2Automatisation des tests  - objectifs et concepts - partie 2
Automatisation des tests - objectifs et concepts - partie 2
 
Automatisation des tests - objectifs et concepts - partie 1
Automatisation des tests  - objectifs et concepts - partie 1Automatisation des tests  - objectifs et concepts - partie 1
Automatisation des tests - objectifs et concepts - partie 1
 
Plpython et Triggers
Plpython et TriggersPlpython et Triggers
Plpython et Triggers
 
Design Pattern introduction
Design Pattern introductionDesign Pattern introduction
Design Pattern introduction
 
Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...
Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...
Cracking RSA key - Quantum Computing - #GlobalAzure Bootcamp - Louis Charavne...
 
Des tests modernes pour Drupal
Des tests modernes pour DrupalDes tests modernes pour Drupal
Des tests modernes pour Drupal
 
Tour d'horizon des tests
Tour d'horizon des testsTour d'horizon des tests
Tour d'horizon des tests
 
Plop : un micro-générateur pour se simplifier la vie au quotidien
Plop : un micro-générateur pour se simplifier la vie au quotidienPlop : un micro-générateur pour se simplifier la vie au quotidien
Plop : un micro-générateur pour se simplifier la vie au quotidien
 
Industrialiser le contrat dans un projet PHP
Industrialiser le contrat dans un projet PHPIndustrialiser le contrat dans un projet PHP
Industrialiser le contrat dans un projet PHP
 
PyConFR - testons en python
PyConFR - testons en pythonPyConFR - testons en python
PyConFR - testons en python
 
Python For Data Science - French Course
Python For Data Science - French CoursePython For Data Science - French Course
Python For Data Science - French Course
 

Plus de Loïc Knuchel

Scala bad practices, scala.io 2019
Scala bad practices, scala.io 2019Scala bad practices, scala.io 2019
Scala bad practices, scala.io 2019Loïc Knuchel
 
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016Loïc Knuchel
 
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016Loïc Knuchel
 
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016Loïc Knuchel
 
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016FP is coming... le 19/05/2016
FP is coming... le 19/05/2016Loïc Knuchel
 
Programmation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptProgrammation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptLoïc Knuchel
 
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015Loïc Knuchel
 
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015Loïc Knuchel
 
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015Loïc Knuchel
 
Devoxx 2015, Atelier Ionic - 09/04/2015
Devoxx 2015, Atelier Ionic - 09/04/2015Devoxx 2015, Atelier Ionic - 09/04/2015
Devoxx 2015, Atelier Ionic - 09/04/2015Loïc Knuchel
 
Devoxx 2015, ionic chat
Devoxx 2015, ionic chatDevoxx 2015, ionic chat
Devoxx 2015, ionic chatLoïc Knuchel
 
Ionic HumanTalks - 11/03/2015
Ionic HumanTalks - 11/03/2015Ionic HumanTalks - 11/03/2015
Ionic HumanTalks - 11/03/2015Loïc Knuchel
 
Ionic bbl le 19 février 2015
Ionic bbl le 19 février 2015Ionic bbl le 19 février 2015
Ionic bbl le 19 février 2015Loïc Knuchel
 
Des maths et des recommandations - Devoxx 2014
Des maths et des recommandations - Devoxx 2014Des maths et des recommandations - Devoxx 2014
Des maths et des recommandations - Devoxx 2014Loïc Knuchel
 

Plus de Loïc Knuchel (14)

Scala bad practices, scala.io 2019
Scala bad practices, scala.io 2019Scala bad practices, scala.io 2019
Scala bad practices, scala.io 2019
 
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
Comprendre la programmation fonctionnelle, Blend Web Mix le 02/11/2016
 
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
Ionic2, les développeurs web à l'assaut du mobile, BDX I/O le 21/10/2016
 
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
Ionic2 - the raise of web developer, Riviera DEV le 17/06/2016
 
FP is coming... le 19/05/2016
FP is coming... le 19/05/2016FP is coming... le 19/05/2016
FP is coming... le 19/05/2016
 
Programmation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScriptProgrammation fonctionnelle en JavaScript
Programmation fonctionnelle en JavaScript
 
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
Ionic Framework, L'avenir du mobile sera hybride, bdx.io le 16-10-2015
 
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
Ionic, ce n'est pas que de l'UI, meetup PhoneGap le 25-05-2015
 
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
Le développement mobile hybride sort du bois, Ch'ti JUG le 15-04-2015
 
Devoxx 2015, Atelier Ionic - 09/04/2015
Devoxx 2015, Atelier Ionic - 09/04/2015Devoxx 2015, Atelier Ionic - 09/04/2015
Devoxx 2015, Atelier Ionic - 09/04/2015
 
Devoxx 2015, ionic chat
Devoxx 2015, ionic chatDevoxx 2015, ionic chat
Devoxx 2015, ionic chat
 
Ionic HumanTalks - 11/03/2015
Ionic HumanTalks - 11/03/2015Ionic HumanTalks - 11/03/2015
Ionic HumanTalks - 11/03/2015
 
Ionic bbl le 19 février 2015
Ionic bbl le 19 février 2015Ionic bbl le 19 février 2015
Ionic bbl le 19 février 2015
 
Des maths et des recommandations - Devoxx 2014
Des maths et des recommandations - Devoxx 2014Des maths et des recommandations - Devoxx 2014
Des maths et des recommandations - Devoxx 2014
 

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

  • 1. Mutation testing Gotta Kill ’Em All ! Loïc Knuchel
  • 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. Loïc Knuchel - @loicknuchel ● Peu de bugs ● Lisible par un autre développeur ● Pas trop dur à faire évoluer
  • 4. Loïc Knuchel - @loicknuchel
  • 5. Loïc Knuchel - @loicknuchel Stratégies de test
  • 6. Loïc Knuchel - @loicknuchel Du code robuste grâce aux tests
  • 7. Loïc Knuchel - @loicknuchel Tester les tests
  • 8. Loïc Knuchel - @loicknuchel Etape 1: l’intuition
  • 9. Loïc Knuchel - @loicknuchel Etape 2: couverture de code
  • 10. Loïc Knuchel - @loicknuchel Solution 2: couverture de code
  • 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. 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. 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. Loïc Knuchel - @loicknuchel Code exécuté par des tests != code testé
  • 15. Loïc Knuchel - @loicknuchel Tout le code ne se vaut pas
  • 16. Loïc Knuchel - @loicknuchel Etape 3 Mutation testing
  • 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. Loïc Knuchel - @loicknuchel Mise en place: Scala // project/plugins.sbt addSbtPlugin("io.github.sugakandrey" % "sbt-scalamu" % "0.1.1") $ sbt mutationTest Scalamu
  • 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. Loïc Knuchel - @loicknuchel Et plein d’autres... NinjaTurtles pour C# Mutmut pour Python mutant pour Ruby Infection pour PHP ...
  • 21. Loïc Knuchel - @loicknuchel Mutation testing
  • 22. Loïc Knuchel - @loicknuchel Génère un mutant Lance les tests Vérifie le résultat Recommence
  • 23. Loïc Knuchel - @loicknuchel Mutant tué Si les tests échouent Le code muté a été détecté Il est donc correctement testé
  • 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. 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. 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. Loïc Knuchel - @loicknuchel Mutations: opération mathématique Opérations : x++ ⇔ x-- + ⇔ - * ⇔ / % ⇔ * Opérations binaires : & ⇔ | ^ ⇔ & >> ⇔ << return total - discount; return total + discount;
  • 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. Loïc Knuchel - @loicknuchel Mutations: supprimer une fonction println(s"item add: $item")
  • 30. Loïc Knuchel - @loicknuchel Mutations
  • 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. 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. 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. Loïc Knuchel - @loicknuchel Test 1
  • 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. Loïc Knuchel - @loicknuchel Test 1 + 2
  • 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. Loïc Knuchel - @loicknuchel Test 1 + 2 + 3
  • 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. Loïc Knuchel - @loicknuchel Test 1 + 2 + 3 + 4
  • 41. Loïc Knuchel - @loicknuchel Code source https://github.com/loicknuchel/mutation-testing-sample Java / Scala / JavaScript / PR acceptées ;)
  • 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. Loïc Knuchel - @loicknuchel Questions ? Slides: http://bit.ly/ rivieradev-2018-mutation-testing

Notes de l'éditeur

  1. Bonjour à tous, Bienvenu à mon talk sur le mutation testing.
  2. 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
  3. 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é
  4. Et bien sûr, pour le premier point, on va écrire des tests
  5. 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
  6. 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 ?
  7. On va devoir tester nos tests => testception
  8. 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
  9. 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.
  10. ç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.
  11. 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é
  12. 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é
  13. 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
  14. Comme toute métrique, elle peut être détournée => introspection pour tout exécuter en un test
  15. 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.
  16. 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.
  17. 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)
  18. En scala il y a scalamu et de la même manière, il suffit de l’ajouter comme plugin et de le lancer
  19. Et enfin, en JavaScript on a stryker
  20. Mais on a toujours pas parlé de ce que fait le mutation testing…
  21. 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
  22. 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