Erstgespräch vereinbaren
Kommende Termine
4. Mai 2026
IT-Talk: Digitale Verantwortung
Details →
12. Juni 2026
Vernissage
28. September 2026
IT-Talk: Event-Driven Architecture

Bessere Testergebnisse mit PI-Test

Warum Code Coverage nicht reicht und wie Mutationstests mit PI-Test die Qualität von Unit-Tests nachhaltig verbessern.

Code auf einem Monitor

Warum Code Coverage nicht reicht

Code Coverage kann trügerisch sein, wenn es um die Qualität von Unit-Tests geht. Die meisten Projekte messen Code Coverage über das reine Testen hinaus und streben oft eine Abdeckung von über 70% an, manche sogar eine nahezu vollständige Abdeckung.

Ein einfaches Beispiel

Eine einfache Additionsfunktion:

public abstract class Numbers {
  static int add(int a, int b) {
    return a + b;
  }
}

Mit dem dazugehörigen Test:

public class NumbersTest {
  @Test
  void resultIsSumOfBoth() {
    assertThat(Numbers.add(2,2)).isEqualTo(4);
  }
}

Tests können mit hoher Coverage bestehen und trotzdem Mutationen nicht erkennen – zum Beispiel wenn a + b zu a * b geändert wird. Der Test würde immer noch bestehen, da 2 + 2 und 2 * 2 beide 4 ergeben.

Alternative Teststrategien

Es gibt zwei Ansätze, um dieses Problem zu lösen:

  1. Property-based Testing – statt Konstanten generierte Werte verwenden (z.B. mit jqwik )
  2. Mutationstests – absichtlich Fehler in den Bytecode einbauen und prüfen, ob die Tests diese erkennen

PI Test ist ein Plugin, das diese Art von Analyse ermöglicht.

Mutationstests mit PI Test

Die Verbreitung von PI Test ist noch begrenzt – hauptsächlich wegen der Ausführungszeit (es sind mehrere Testdurchläufe erforderlich) und der Menge an Problemen, die in bestehenden Projekten aufgedeckt werden. Dennoch ist das Tool wertvoll für kritischen Code, der hohe Zuverlässigkeit erfordert.

PI Test in der Anwendung

Die Konfiguration als Maven-Plugin:

<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>1.7.2</version>
    <executions>
        <execution>
            <goals>
                <goal>mutationCoverage</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <mutators>
            STRONGER,AOR,AOD,OBBN,ROR,REMOVE_INCREMENTS,NON_VOID_METHOD_CALLS,INLINE_CONSTS,CONSTRUCTOR_CALLS
        </mutators>
        <timestampedReports>false</timestampedReports>
        <verbose>false</verbose>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.pitest</groupId>
            <artifactId>pitest-junit5-plugin</artifactId>
            <version>0.15</version>
        </dependency>
    </dependencies>
</plugin>

Bessere Tests

Eine Funktion zur Bereichsprüfung:

public abstract class Numbers {
  static boolean isInRange(int value, int min, int max) {
    if (value<min) return false;
    if (value>max) return false;
    return true;
  }
}

Verbesserte Tests mit Grenzfällen:

@Test
void valueIsBetween() {
  assertThat(Numbers.isInRange(3, 3, 4)).isTrue();
  assertThat(Numbers.isInRange(4, 3, 4)).isTrue();
}

Der entscheidende Unterschied zwischen den Tests in diesem Beispiel ist einfach: Wir haben die Grenzfälle getestet.

Zusammenfassung

  • Unit-Tests stellen sicher, dass das erwartete Verhalten unter definierten Bedingungen gegeben ist
  • Code Coverage identifiziert nicht getesteten Code
  • Mutationstests helfen, bessere Tests zu schreiben, die unerkannte Fehler verhindern
Zurück zum Blog