Mutationstests mit PITest

1. Übersicht

Softwaretests beziehen sich auf die Techniken, mit denen die Funktionalität einer Softwareanwendung bewertet wird. In diesem Artikel werden einige der in der Softwaretestbranche verwendeten Metriken wie Codeabdeckung und Mutationstests erläutert , wobei besonderes Interesse daran besteht, wie ein Mutationstest mithilfe der PITest-Bibliothek durchgeführt wird .

Der Einfachheit halber werden wir diese Demonstration auf eine grundlegende Palindromfunktion stützen - Beachten Sie, dass ein Palindrom eine Zeichenfolge ist, die das gleiche vorwärts und rückwärts liest.

2. Maven-Abhängigkeiten

Wie Sie in der Konfiguration der Maven-Abhängigkeiten sehen können, werden wir JUnit verwenden, um unsere Tests auszuführen, und die PITest- Bibliothek, um Mutanten in unseren Code einzuführen - keine Sorge, wir werden in einer Sekunde sehen, was eine Mutante ist. Sie können jederzeit die neueste Abhängigkeitsversion für das zentrale Maven-Repository nachschlagen, indem Sie diesem Link folgen.

 org.pitest pitest-parent 1.1.10 pom  

Damit die PITest-Bibliothek funktioniert, müssen wir auch das Pitest-Maven- Plugin in unsere Konfigurationsdatei pom.xml aufnehmen :

 org.pitest pitest-maven 1.1.10   com.baeldung.testing.mutation.*   com.baeldung.mutation.test.*    

3. Projekteinrichtung

Nachdem wir unsere Maven-Abhängigkeiten konfiguriert haben, schauen wir uns diese selbsterklärende Palindrom-Funktion an:

public boolean isPalindrome(String inputString) { if (inputString.length() == 0) { return true; } else { char firstChar = inputString.charAt(0); char lastChar = inputString.charAt(inputString.length() - 1); String mid = inputString.substring(1, inputString.length() - 1); return (firstChar == lastChar) && isPalindrome(mid); } } 

Jetzt brauchen wir nur noch einen einfachen JUnit-Test, um sicherzustellen, dass unsere Implementierung auf die gewünschte Weise funktioniert:

@Test public void whenPalindrom_thenAccept() { Palindrome palindromeTester = new Palindrome(); assertTrue(palindromeTester.isPalindrome("noon")); } 

So weit so gut, wir sind bereit, unseren Testfall erfolgreich als JUnit-Test auszuführen.

Als nächstes konzentrieren wir uns in diesem Artikel auf die Abdeckung von Code und Mutationen mithilfe der PITest-Bibliothek.

4. Code-Abdeckung

Die Codeabdeckung wurde in der Softwareindustrie häufig verwendet, um zu messen, wie viel Prozent der Ausführungspfade während automatisierter Tests ausgeführt wurden.

Wir können die effektive Codeabdeckung basierend auf Ausführungspfaden mit Tools wie Eclemma messen, die in der Eclipse-IDE verfügbar sind.

Nach dem Ausführen von TestPalindrome mit Codeabdeckung können wir leicht eine 100% ige Abdeckungsbewertung erzielen. Beachten Sie, dass isPalindrome rekursiv ist, sodass es ziemlich offensichtlich ist, dass die Prüfung der leeren Eingabelänge ohnehin abgedeckt wird.

Leider können Codeabdeckungsmetriken manchmal ziemlich ineffektiv sein , da eine 100% ige Codeabdeckungsbewertung nur bedeutet, dass alle Zeilen mindestens einmal ausgeführt wurden, aber nichts über die Genauigkeit der Tests oder die Vollständigkeit der Anwendungsfälle aussagt , und deshalb sind Mutationstests tatsächlich wichtig.

5. Mutationsabdeckung

Mutationstests sind Testtechniken, mit denen die Angemessenheit von Tests verbessert und Codefehler identifiziert werden können. Die Idee ist, den Produktionscode dynamisch zu ändern und die Tests fehlschlagen zu lassen.

Gute Tests werden fehlschlagen

Jede Änderung im Code wird als Mutante bezeichnet und führt zu einer geänderten Version des Programms, die als Mutation bezeichnet wird .

Wir sagen, dass die Mutation getötet wird, wenn sie bei den Tests fehlschlagen kann. Wir sagen auch, dass die Mutation überlebt hat, wenn die Mutante das Verhalten der Tests nicht beeinflussen konnte.

Lassen Sie uns nun den Test mit Maven ausführen, wobei die Zieloption auf org.pitest: pitest-maven: mutationsCoverage festgelegt ist .

Wir können die Berichte im HTML-Format im Verzeichnis target / pit-test / YYYYMMDDHHMI überprüfen :

  • 100% Leitungsabdeckung: 7/7
  • 63% Mutationsbedeckung: 5/8

Unser Test durchläuft eindeutig alle Ausführungspfade, sodass die Bewertung der Leitungsabdeckung 100% beträgt. Andererseits führte die PITest-Bibliothek 8 Mutanten ein , von denen 5 getötet wurden - was zu einem Fehler führte -, aber 3 überlebten.

Weitere Informationen zu den erstellten Mutanten finden Sie im Bericht com.baeldung.testing.mutation / Palindrome.java.html :



Dies sind die Mutatoren, die standardmäßig aktiv sind, wenn ein Mutationsabdeckungstest ausgeführt wird:

  • INCREMENTS_MUTATOR
  • VOID_METHOD_CALL_MUTATOR
  • RETURN_VALS_MUTATOR
  • MATH_MUTATOR
  • NEGATE_CONDITIONALS_MUTATOR
  • INVERT_NEGS_MUTATOR
  • CONDITIONALS_BOUNDARY_MUTATOR

Weitere Informationen zu den PITest-Mutatoren finden Sie auf der offiziellen Dokumentationsseite .

Unser Mutations-Coverage-Score spiegelt das Fehlen von Testfällen wider , da wir nicht sicherstellen können, dass unsere Palindrom-Funktion nicht-palindromische und nahezu palindromische String-Eingaben ablehnt.

6. Verbessern Sie den Mutationswert

Jetzt, da wir wissen, was eine Mutation ist, müssen wir unseren Mutationswert verbessern, indem wir die überlebenden Mutanten töten .

Nehmen wir als Beispiel die erste Mutation - negiert bedingt - in Zeile 6. Die Mutante hat überlebt, denn selbst wenn wir das Code-Snippet ändern:

if (inputString.length() == 0) { return true; }

Zu:

if (inputString.length() != 0) { return true; }

Der Test wird bestanden, und deshalb hat die Mutation überlebt . Die Idee ist, einen neuen Test zu implementieren, der fehlschlägt, falls die Mutante eingeführt wird . Gleiches kann für die verbleibenden Mutanten durchgeführt werden.

@Test public void whenNotPalindrom_thanReject() { Palindrome palindromeTester = new Palindrome(); assertFalse(palindromeTester.isPalindrome("box")); } @Test public void whenNearPalindrom_thanReject() { Palindrome palindromeTester = new Palindrome(); assertFalse(palindromeTester.isPalindrome("neon")); }

Jetzt können wir unsere Tests mit dem Mutationsabdeckungs-Plugin ausführen, um sicherzustellen, dass alle Mutationen getötet wurden , wie wir im im Zielverzeichnis generierten PITest-Bericht sehen können.

  • 100% Leitungsabdeckung: 7/7
  • 100% Mutationsabdeckung: 8/8

7. Konfiguration der PITest-Tests

Mutation testing may be resources-extensive sometimes, so we need to put proper configuration in place to improve tests effectiveness. We can make use of the targetClasses tag, to define the list of classes to be mutated. Mutation testing cannot be applied to all classes in a real world project, as it will be time-consuming, and resource critical.

It is also important to define the mutators you plan to use during mutation testing, in order to minimize the computing resources needed to perform the tests:

  com.baeldung.testing.mutation.*   com.baeldung.mutation.test.*   CONSTRUCTOR_CALLS VOID_METHOD_CALLS RETURN_VALS NON_VOID_METHOD_CALLS  

Moreover, the PITest library offers a variety of options available to customize your testing strategies, you can specify the maximum number of mutants introduced by class using the maxMutationsPerClass option for example. More details about PITest options in the official Maven quickstart guide.

8. Conclusion

Note that code coverage is still an important metric, but sometimes it is not sufficient enough to guarantee a well-tested code. So in this article we've walked through mutation testing as a more sophisticated way to ensure tests quality and endorse test cases, using the PITest library.

Wir haben auch gesehen, wie man grundlegende PITest-Berichte analysiert und gleichzeitig den Mutations-Coverage-Score verbessert .

Obwohl Mutationstests Fehler im Code aufdecken, sollte er mit Bedacht eingesetzt werden, da dies ein äußerst kostspieliger und zeitaufwändiger Prozess ist .

Sie können die Beispiele in diesem Artikel im verknüpften GitHub-Projekt überprüfen .