Mutationstest mit PITest

1. Überblick

Softwaretest bezieht sich auf die Techniken, die zur Beurteilung der Funktionalität einer Softwareanwendung verwendet werden. In diesem Artikel werden einige der in der Softwaretestbranche verwendeten Kennzahlen wie Code Coverage und Mutation Testing besprochen, wobei besonderes Interesse an der Durchführung eines Mutationstests unter Verwendung des http://pitest besteht .org/[ PITest Bibliothek ].

Der Einfachheit halber werden wir diese Demonstration auf eine grundlegende Palindrom-Funktion stützen. Beachten Sie, dass ein Palindrom eine Zeichenfolge ist, die dieselbe Vorwärts- und Rückwärtsrichtung liest.

2. Abhängigkeiten von Maven

Wie Sie in der Konfiguration der Maven-Abhängigkeiten sehen können, werden wir JUnit verwenden, um unsere Tests und die PITest -Bibliothek auszuführen, um Mutanten ** in unseren Code einzufügen.

Sie können die neueste Abhängigkeitsversion immer im Vergleich zum zentralen Repository von Maven nachschlagen, indem Sie diesem link folgen.

<dependency>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-parent</artifactId>
    <version>1.1.10</version>
    <type>pom</type>
</dependency>

Damit die PITest-Bibliothek betriebsbereit ist, müssen Sie auch das Plugin pitest-maven in unsere Konfigurationsdatei pom.xml aufnehmen:

<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>1.1.10</version>
    <configuration>
        <targetClasses>
            <param>com.baeldung.testing.mutation.** </param>
        </targetClasses>
        <targetTests>
            <param>com.baeldung.mutation.test.** </param>
    </targetTests>
     </configuration>
</plugin>

3. Projektaufbau

Nun, da wir unsere Maven-Abhängigkeiten konfiguriert haben, werfen wir einen Blick auf diese selbsterklärende Palindrom-Funktion:

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 in der gewünschten 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 Code- und Mutationsabdeckung unter Verwendung der PITest-Bibliothek.

4. Code-Abdeckung

Die Codeabdeckung wurde in der Software-Industrie intensiv eingesetzt, um zu messen, wie viel Prozent der Ausführungspfade während automatisierter Tests ausgeübt 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 Code-Coverage können wir problemlos eine Abdeckung von 100% erreichen. Beachten Sie, dass isPalindrome rekursiv ist.

Leider können Codeabdeckungsmesswerte manchmal sehr ineffektiv sein, da ein Code-Coverage-Wert von 100% nur bedeutet, dass alle Zeilen mindestens einmal ausgeführt wurden. Sie sagt jedoch nichts über Genauigkeit der Tests oder Vollständigkeit der Verwendungsfälle aus warum Mutationstests eigentlich wichtig sind.

5. Mutationsabdeckung

Mutationstesting ist eine Testmethode, die dazu dient, die Angemessenheit von Tests zu verbessern und Fehler im Code zu erkennen. Die Idee ist, den Produktionscode dynamisch zu ändern und die Tests fehlschlagen zu lassen.

Gute Tests schlagen fehl

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 abgetötet wird , wenn sie einen Testfehler verursachen kann. Wir sagen auch, dass die Mutation überlebte ** , wenn die Mutante das Verhalten der Tests nicht beeinflussen konnte.

Lassen Sie uns nun den Test mit Maven ausführen. Die Zieloption lautet:

org.pitest: pitest-maven: mutationCoverage .

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

  • 100% Leitungsabdeckung: 7/7

  • 63% Mutationsabdeckung: 5/8

Unser Test führt eindeutig über alle Ausführungspfade. Daher beträgt der Leitungsabdeckungswert 100%. In der PITest-Bibliothek wurden dagegen 8 Mutanten eingeführt, von denen 5 getötet wurden - Verursacht einen Fehler -, aber 3 überlebten.

Wir können den com.baeldung.testing.mutation/Palindrome.java.html - Bericht auf weitere Details zu den erzeugten Mutanten überprüfen:



Dies sind die standardmäßig aktivierten Mutatoren ** , wenn ein Mutation Coverage Test 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 im offiziellen Link Dokumentationsseite .

Unser Mutationsabdeckungswert spiegelt das Fehlen von Testfällen wider, da wir nicht sicherstellen können, dass unsere Palindrom-Funktion nicht palindromische und nahezu palindromische Zeichenfolgeneingaben ablehnt.

6. Verbessern Sie den Mutationswert

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

Nehmen wir die erste Mutation - negierte Bedingung - in Zeile 6 als Beispiel. Der Mutant hat da überlebt, auch 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 der Mutant eingeführt wird . Dasselbe kann für die verbleibenden Mutanten gemacht 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 Mutations-Coverage-Plugin durchführen, um sicherzustellen, dass alle Mutationen abgebrochen wurden , wie wir im im Zielverzeichnis generierten PITest-Bericht sehen können.

  • 100% Leitungsabdeckung: 7/7

  • 100% Mutationsabdeckung: 8/8

7. PITest-Tests Konfiguration

Mutationstests können manchmal sehr umfangreich sein, daher müssen wir die richtige Konfiguration vornehmen, um die Effektivität der Tests zu verbessern. Wir können den Tag targetClasses verwenden, um die Liste der zu mutierenden Klassen zu definieren. Mutationstests können nicht auf alle Klassen in einem realen Projekt angewendet werden, da dies zeitaufwändig und ressourcenkritisch ist.

Es ist auch wichtig, die Mutatoren zu definieren, die Sie während des Mutationstests verwenden möchten, um die zur Durchführung der Tests erforderlichen Rechenressourcen zu minimieren:

<configuration>
    <targetClasses>
        <param>com.baeldung.testing.mutation.** </param>
    </targetClasses>
    <targetTests>
        <param>com.baeldung.mutation.test.** </param>
    </targetTests>
    <mutators>
        <mutator>CONSTRUCTOR__CALLS</mutator>
        <mutator>VOID__METHOD__CALLS</mutator>
        <mutator>RETURN__VALS</mutator>
        <mutator>NON__VOID__METHOD__CALLS</mutator>
    </mutators>
</configuration>

Darüber hinaus bietet die PITest-Bibliothek eine Vielzahl von Optionen zur Anpassung Ihrer Teststrategien . Sie können beispielsweise die maximale Anzahl von Mutanten angeben, die von class eingeführt werden, beispielsweise mit der Option maxMutationsPerClass . Weitere Informationen zu den PITest-Optionen finden Sie in der offiziellen Maven-Kurzanleitung .

8. Fazit

Beachten Sie, dass die Codeabdeckung immer noch eine wichtige Kennzahl ist, aber manchmal nicht ausreicht, um einen gut getesteten Code zu gewährleisten. In diesem Artikel haben wir den Mutationstest als eine anspruchsvollere Methode zur Sicherstellung der Testqualität und zur Bestätigung der Testfälle mit der PITest-Bibliothek durchlaufen.

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

Obwohl Mutationstests Fehler im Code aufdecken, sollte dies weise verwendet werden, da dies ein äußerst kostenintensiver und zeitaufwändiger Prozess ist.

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