Die Mutanten sind los. Holt brennende Fackeln und Forken.

Was ist Mutation Testing?

Die Verwendung von Tests zur Überprüfung des Codes ist akzeptiert. Meist wird sogar TDD angewendet. Aber: Die Anzahl der Tests sagts nichts über deren Qualität aus!

Mit einer Coverage von 100% lassen sich ohne Assertions keine Fehler im Code erkennen.

Mit Hilfe von Mutation Testings lässt sich die Qualität der Tests überprüfen.

Voraussetzung: Alle Tests sind grün.

Vorgehen:

  1. Der Sourcecode wird verändert = Mutation.
  2. Wenn ein Test fehlschlägt wurde die Mutation gekillt.
  3. Wenn kein Test fehlschlägt, hat die Mutation überlebt (und das ist nicht gut).

Güte der Tests = Anzahl getöteter Mutationen / Anzahl aller Mutationen

Mit Hilfe von Mutation Testing:

  • lassen sich fehlende Testfälle finden: neue Testdaten oder neue Tests
  • lassen sich überflüssige Codestellen finden: lassen sich keine Testfälle finden, um Mutanten zu töten, ist der Code evtl. redundant oder wird gar nicht verwendetSou

pitest

In der Javawelt kann dafür das pitest Framework verwendet werden.

Es gibt verschiedene Mutatoren, deren Verwendung konfiguriert werden kann.

Beispiele für Mutatoren:

  • Relationale Operatoren ersetzen: Aus einem if (a < b) wird ein if (a <= b) (andere Grenzen) und ein if (a > b) (Gegenteil) und ein if (true) (Konstante).
  • Arithmetische Operatoren ersetzen: Aus a = b + c wird a = b - c
  • Änderung der Rückgabewerte: Aus return x wird return x!=null ? null : throw new RuntimeException()
  • Anweisungen entfernen

Es gibt eine Vorkonfiguration bestimmter Mutatoren. Diese können durch das Setzen von Gruppen umkonfiguriert werden oder es können einzelne Mutatoren verwendet werden.

Anwendung

Nach jeder Anwendung eines Mutators werden alle Tests durchlaufen. Gibt es im Projekt bereits eine große Testsuite, würde die Ausführung ewig dauern. Dann können die pitest nur lokal für den Bereich der Entwicklung angewendet werden und/oder können in einem nightly Build auf dem Buildserver gestartet werden.

Durch Mutationen können Endlosschleifen erzeugt werden. Es sollte für jeden Tests ein Timeout gesetzt werden.

Gradle

plugins {
	id 'info.solidsoft.pitest' version '1.5.1'
}

pitest {
    // Bei Auskommentierung des testPlugins wird junit4 verwendet
    testPlugin = 'junit5'
	pitestVersion = '1.5.1'  //for Java 11 compatibility with gradle-pitest-plugin 1.3.0
	//adds dependency to org.pitest:pitest-junit5-plugin and sets "testPlugin" to "junit5"
    junit5PluginVersion = '0.12'
    
    targetClasses = ['packagename.*']
    threads = 4
    outputFormats = ['XML', 'HTML']
    timestampedReports = false
    mutators = ['STRONGER']	// groups: OLD_DEFAULTS, DEFAULTS, STRONGER, ALL
}

Ausführen der Tests mit gradle pitest.

Die Ergebnisse liegen dann unter build/reports/pitest. In den Tests wird für jedes Package/Klasse aufgeführt, wie hoch die Abdeckung der Codezeilen ist und wie hoch die erkannten Mutationen sind.

Beispiel

Präsentation github