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

Mutationstesten: eine vernachlässigte Testtechnik

Mutationstesten: eine vernachlässigte Testtechnik

Diese Session gibt eine Einführung in das Mutationstesten, ein Konzept, das erstmals 1971 beschrieben wurde, und untersucht, warum es heute bei der Entwicklung von Software für die verschiedensten Domänen wie Banken oder bei der Steuerung des Large Hadron Colliders am CERN eingesetzt wird. Es wird einen Überblick über die Theorie, die hinter dem Mutationstesten steckt, gegeben und wie man diese mit dem Open-Source-Tool Infection anwendet. Des Weiteren wird gezeigt, wie Mutationstesten einen Einblick in der Qualität von Unit-Tests ermöglicht und hilft diese zu verbessern.

Avatar for Tobias Stadler

Tobias Stadler

October 09, 2018

Other Decks in Programming

Transcript

  1. Über mich • Softwareentwicklung > 15 Jahre • Java-Entwickler bei

    petaFuel GmbH in Freising • Mastercard-Processor • VIMpay-App
  2. Testabdeckung • Welche? • Anweisung, Verzweigung • Pfad, Bedingung, …

    • Zeigt aber nicht welcher Teil der Software getestet wurde
  3. 2. public class AddNumbersBelow10 { 4. private int result; 6.

    public void add(int number) { 7. if (number <= 10) { 8. result += number; 9. } 10. } 12. public void reset() { 13. result = 0; 14. } 16. public int getResult() { 17. return result; 18. } 19.}
  4. @Test public void shouldStartWith0() { assertEquals(0, adder.getResult()); } @Test public

    void shouldReturn1When1IsAddedTo0() { adder.add(1); assertEquals(1, adder.getResult()); } @Test public void shouldReturn0When100IsAddedTo0() { adder.add(100); assertEquals(0, adder.getResult()); }
  5. public class AddNumbersBelow10 { private int result; public void add(int

    number) { if (number <= 10) { // hier ist ein Bug result += number; } } public void reset() { result = 0; } public int getResult() { return result; } }
  6. Mutationstesten • Um zu prüfen, ob eine Test-Suite die Software

    testet, bauen wir eine Fehler in die Software ein • Die Test-Suite sollte den Fehler finden
  7. public void add(int number) { if (number < 10) {

    // <= zu < geändert result += number; } }
  8. Mutationsoperator • Eine Änderung wie <= durch < zu ersetzen

    heißt Mutationsoperator • Viele Mutationsoperatoren sind möglich • Änderung von Vergleichsoperatoren (<= -> <) • Änderung von math. Operatoren (+ -> -) • Änderung von konstanten Werten (1 -> 0) • Ersetzung von Methodenrumpf durch Konstante • Methodenaufrufe entfernen (o.foo() -> //o.foo()) • …
  9. Mutant • Das Anwenden eines Mutationsoperators auf ein Programm erzeugt

    einen Mutanten • Wenn durch eine Mutanten ein Test fehlschlägt wurde der Mutant getötet, wenn nicht dann hat er überlebt
  10. public void foo(int input) { if(input <= 100) { throw

    new IllegalArgumentException(); } if(input >= 100) { bar(); } }
  11. public void foo(int input) { if(input <= 100) { throw

    new IllegalArgumentException(); } if(input > 100) { // >= zu > geändert bar(); } }
  12. Äquivalente Mutanten • Ändert ein Mutant nicht die Semantik des

    Programms, dann ist der Mutant ein äquivalenter Mutant
  13. public void foo(int input) { if(input <= 100) { throw

    new IllegalArgumentException(); } bar(); }
  14. public int bar(int value) { do (!isFinished(value)) { value =

    calculateNewValue(value); } return value; } private boolean isFinished(int value) { return false; // „Berechnung“ durch false ersetzt }
  15. public void foobar(Path path) throws IOException { if (!pathIsSave(path)) {

    return; } removeDirectory(path); } private boolean pathIsSave(int value) { return true; // „Berechnung“ durch true ersetzt } @Test public void shouldNotDeleteRoot() throws IOException { foobar(Paths.get(„/")); }
  16. Coupling Effect • Test data that distinguishes all programs differing

    from a correct one by only simple errors is so sensitive that it also implicitly distinguishes more complex errors
  17. Mutationstests • zeigt an welcher Code getestet wurde • Kann

    Bugs finden • Kann redundanten Code entdecken
  18. Warum kein Mutationstesten? • Äquivalente Mutanten (in der Praxis aber

    eher selten) • Problematische Mutationen • Verwenden von Timeouts • Ausführung in einer Sandbox • Zu langsam
  19. Apache Commons Codec • 56 Dateien mit ca. 6.000 LOC

    • 53 Test-Dateien mit ca. 10.000 LOC (662 Tests) • Kompilierung: ca. 4 s • Ausführung der Test: ca. 13 s • => 900 Mutanten: 900 * (4 + 13) s = 15.300 s = 4,25 h
  20. Auswahl der Tests • Per Namenskonvention (Foo -> FooTest) •

    Per statischer Codeanalyse • Per Testabdeckung
  21. 2. public class AddNumbersBelow10 { 4. private int result; 6.

    public void add(int number) { 7. if (number <= 10) { 8. result += number; 9. } 10. } 12. public void reset() { 13. result = 0; 14. } 16. public int getResult() { 17. return result; 18. } 19.} @Test public void shouldStartWith0() { assertEquals(0, adder.getResult()); } @Test public void shouldReturn1When1IsAddedTo0() { adder.add(1); assertEquals(1, adder.getResult()); } @Test public void shouldReturn0When100IsAddedTo0() { adder.add(100); assertEquals(0, adder.getResult()); }
  22. Priorisierung der Tests • Schnelle Test zuerst • Abbruch nach

    dem ersten fehlgeschlagenen Test • Per Namenskonvention
  23. Apache Commons Codec • Mit pitest (Standard-Mutationsoperatoren): • 2894 Mutanten

    generiert • 7646 Test ausgeführt (2,64 Tests pro Mutation) • Dauer: ca. 5 min (statt 4,25 h für 900 Mutationen)
  24. Es geht noch schneller • Reduzierung • der zu mutierenden

    Klassen • der Mutationsoperatoren • der Tests • Inkrementelle Analyse • Nur geänderte Programmteile mutieren (SCM)
  25. Zusammenfassung • Mutationstesten • Kann die Qualität einer Test-Suite beurteilen

    • Kann Bugs finden • Ist (für den praktischen Gebrauch) schnell genug