Headerbild DNA aus Code

Wenn kleine Einheiten für Alarm sorgen

Java-Testabdeckung Teil 4: Mutation Testing

Von: Sven Ruppert

In dieser Reihe von Blogbeiträgen werden wir uns eingehend mit den verschiedenen Aspekten der Testabdeckung befassen und verschiedene Techniken und Tools untersuchen, die Entwicklern dabei helfen, qualitativ hochwertige Software zu erstellen. Dieser Teil der Reihe untersucht das mutationsbasierte Testen.

Mutationstests

Mutationstests dienen dazu, die Qualität von Softwaretests zu bewerten. Dafür wird der Quellcode eines Programms in kleinen Einheiten, sogenannten „Mutanten“, abgewandelt. Dann wird die Testsuite ausgeführt, um zu überprüfen, ob die Tests bei den Mutanten Alarm schlagen. Eine gute Testsuite sollte in der Lage sein, die Mutanten zu erkennen – was darauf hindeutet, dass die Tests auch ungewollte Fehler effektiv erkennen können.

Schlüsselkonzepte des Mutationstests

Mutanten: Variationen des ursprünglichen Programms, die durch kleine Änderungen am Code erzeugt werden. Es können zum Beispiel Operatoren, Konstanten oder ein Kontrollfluss geändert werden.

Mutationsoperatoren: Spezifische Regeln oder Muster, die zur Erstellung von Mutanten verwendet werden. Beispiele beinhalten:

  • Ersetzung arithmetischer Operatoren (AOR): Ersetzt arithmetische Operatoren wie `+` durch `-`.
  • Ersetzung logischer Operatoren (LOR): Diese Funktion ersetzt logische Operatoren wie `&&` durch `||`.
  • Relationaler Operator-Ersatz (ROR): Ersetzt relationale Operatoren wie `>` durch `<`.

Mutations-Score: Der Prozentsatz der Mutanten, die von der Testsuite erkannt (getötet) werden.

Schritte beim Mutationstest

Zuerst wendest die Mutationsoperatoren auf den ursprünglichen Quellcode an, um eine Reihe mutierter Programme zu erstellen. Dann führst du die Testsuite für jeden Mutanten aus. Bestimme, ob die Tests für jeden Mutanten erfolgreich sind oder nicht. Schlägt ein Test fehl, gilt der Mutant als „getötet“. Laufen alle Tests erfolgreich durch, hat der Mutant „überlebt“. Eine manuelle Durchführung ist aufgrund der Anzahl an Mutationen praktisch nicht durchführbar.

Pitest für Mutationstests in Java

Pitest (PIT) ist ein weit verbreitetes Mutationstest-Tool für Java, das sich in verschiedene Build-Tools und CI/CD-Pipelines integrieren lässt.

PIT zu Deinem Projekt hinzufügen

So fügst du das PIT-Plugin zu deiner `pom.xml` hinzu:

```xml
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.6.9</version>
<executions>
<execution>
<goals>
<goal>mutationCoverage</goal>
</goals>
</execution>
</executions>
</plugin>

Führe dann die Mutationstests aus mit: `mvn org.pitest:pitest-maven:mutationCoverage`

PIT generiert einen detaillierten HTML-Bericht, der den Mutations-Score zeigt, welche Mutanten getötet wurden und welche überlebt haben. Der Bericht hilft dabei, Schwachstellen in der Testsuite zu identifizieren.

Beispiel für Mutationstests

Das folgende Beispiel zeigt eine einfache Java-Klasse und ihren Test:

Calculator.java:

```java
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
```

CalculatorTest.java:

```java
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

public class CalculatorTest {
@Test
public void testAdd() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
}
```

Beim Ausführen eines Mutationstests in diesem Beispiel könnte ein typischer Mutant den Additionsoperator in einen Subtraktionsoperator ändern:

```java
public int add(int a, int b) {
return a - b;
}
```

Wenn die Testsuite wachsam ist, schlägt sie für diesen Mutanten fehl und tötet ihn. Andernfalls muss die Testsuite möglicherweise verbessert werden, um dieses Szenario abzudecken.

Vorteile von Mutationstests

Verbesserung der Testqualität: Mithilfe von Mutationstests können Schwachstellen in der Testsuite und Bereichen, in denen möglicherweise Tests fehlen, identifiziert werden.

Umfassende Abdeckung: Sie führen zu einer strengeren Bewertung der Testeffektivität im Vergleich zu Codeabdeckungsmetriken allein.

Identifizierung redundanter Tests: Mutationstests helfen außerdem bei der Erkennung von Tests, die nicht zur Erkennung von Fehlern im Code beitragen.

Herausforderungen und Best Practices

Mutationstests können zeitaufwändig sein, da die Testsuite mehrmals ausgeführt werden muss. Das schlägt insbesondere bei großen Codebasen zu Buche. Einige Mutanten sind möglicherweise logisch äquivalent zum Originalcode und können durch keinen Test getötet werden, was schwierig zu identifizieren und zu handhaben sein kann. Um den Leistungsaufwand zu reduzieren, konzentriere dich auf kritische Teile der Codebasis oder verwende eine Teilmenge von Mutationsoperatoren.

Mutationstests sind eine leistungsstarke Technik zur Beurteilung der Wirksamkeit deiner Testsuite, indem kleine Änderungen (Mutanten) am Code vorgenommen werden und überprüft wird, ob die Tests diese Mutanten erkennen. Tools wie PIT ermöglichen die praktische Integration von Mutationstests in Java-Projekte, liefern wertvolle Einblicke in die Testqualität und tragen dazu bei, die Robustheit deiner Tests zu verbessern. Durch die Ergänzung traditioneller Testmethoden durch Mutationstests kannst du ein höheres Vertrauen in die Korrektheit und Zuverlässigkeit deiner Software erreichen. Für eine umfassende Teststrategie ist es von Vorteil, verschiedene Ansätze zu kombinieren und die unterschiedlichen Stärken gebündelt zu nutzen, um sowohl ein gründliches als auch ein praktisches Testen sicherzustellen.

Und nun: Happy Coding.

Über den Autor: Sven Ruppert

Sven programmiert seit 1996 Java in Industrieprojekten, seit über 15 Jahren weltweit Java in Branchen wie Automobil, Raumfahrt, Versicherungen, Banken, der UNO und der Weltbank. Seit zehn Jahren ist er als Sprecher auf Konferenzen und Community-Events in Ländern von Amerika bis Neuseeland. Er hat als Developer Advocate für JFrog und Vaadin gearbeitet und schreibt regelmäßig Artikel für IT-Magazine und Technologieportale.


Diese Beiträge könnten dich auch interessieren: