Mutationstests

By Gerald Mücke | May 29, 2015

Mutationstests

Ende Januar habe ich an der OOP2015-Konferenz in München teilgenommen. Unter den vielen interessanten Sitzungen war eine, die einen bleibenden Eindruck hinterlassen hat. Es war der Workshop von Filip van Laenen und Markus Schirp über Mutationstests (Folien hier), den ich in diesem Beitrag zusammenfassen möchte.

Was ist Mutationstesting?

Mutationstesting ist eine Methode, um die Qualität Ihrer Tests zu gewährleisten. Mit Mutationstests überprüfen Sie, ob Ihre Tests nicht nur Ihren Produktcode aufrufen, um Zeilen und Verzweigungen abzudecken, sondern ob die Tests tatsächlich die Semantik Ihres Codes widerspiegeln. Während Ihre Tests Ihren Produktcode vor Fehlern schützen, schützt das Mutationstesting Ihre Test-Suite vor kritischen Lücken.

Wie funktioniert es?

Mutationstesting ist für eine Reihe von Sprachen verfügbar. Die Implementierung für Java ist das PIT. PIT modifiziert den kompilierten Bytecode durch Anwendung einer Reihe von vordefinierten Regeln, sogenannte Mutatoren. Jede Änderung am Bytecode wird als Mutant bezeichnet. Gegen den veränderten Bytecode werden Ihre Unit-Tests ausgeführt, was zu drei Ergebnissen führt:

  • Der Test schlägt fehl. Das beweist, dass Ihre Tests die Änderungen am Bytecode korrekt erkennen und einen Fehler melden. Dieses Ergebnis wird als getöteter Mutant bezeichnet.
  • Der Test schlägt nicht fehl, aber die Codezeile wird von der Testausführung abgedeckt. Das beweist, dass Ihr Test nicht die gesamte Semantik des Produkts abdeckt. Dieses Ergebnis wird als überlebender Mutant bezeichnet (ich nenne sie Überlebende).
  • Der Produktcode wird überhaupt nicht von einem Test abgedeckt, enthält aber einen Mutanten. Dieses Ergebnis ist ein unentdeckter Mutant (ich nenne sie Lauerer), der auch von einem Code-Coverage-Tool wie Cobertura oder Jacoco angezeigt werden kann.

Was bringt es?

Jeder Mutant, der nicht durch einen Test getötet wird, ist ein blinder Fleck Ihrer Test-Suite und kann auf lange Sicht ein tatsächlicher Fehler werden. Mit Mutationstests können Sie feststellen, ob Ihr Unit-Test - die detaillierte Spezifikation - alle Semantiken Ihres Produktcodes abdeckt. Für jeden unentdeckten Mutanten können Sie entweder entscheiden, ob Ihre Spezifikation (Test) unvollständig ist oder Ihr Produktcode unnötige Semantiken enthält, die entfernt werden sollten. So führt Mutationstesting zu kleinerem und einfacherem Code und ist zudem ein Indikator dafür, wann Sie mit dem Testen fertig sind.

Kein Allheilmittel

Mutationstesting ist kein Allheilmittel, da es den Bedarf an ordnungsgemäß gestalteten Tests nicht ersetzt. Das Testen aller Kombinationen von Mutationen kann eine ziemlich ressourcenintensive Operation werden, wodurch der Umfang des Mutationstests in größeren Projekten auf die kritischen Teile reduziert werden muss.

Außerdem müssen einige überlebende Mutanten akzeptiert werden. Überlebende Mutanten können leicht auftreten und sind nicht unbedingt ein Indikator dafür, dass Ihre Abdeckung unzureichend ist. Denken Sie zum Beispiel an Logging-Code oder die Gleichheit in einigen Vergleichsfällen, wie (a > b ? a : b), was in Java gleich (a >= b ? a : b) ist.

Wie verwendet man es?

Um es in einem Maven-Projekt zu verwenden, steht ein offizielles Maven-Plugin zur Verfügung (siehe Link für eine gute Dokumentation zur Verwendung). Es gibt auch Optionen für die Kommandozeile oder Ant. Dadurch wird ein Bericht in HTML oder XML erstellt. Sie können den Umfang des Mutationstests mit Include- und Exclude-Parametern eingrenzen. Der Bericht selbst, der regelmäßig erstellt wird, ist bereits eine gute Informationsquelle für die Durchführung von Code-Reviews.

TL;DR

Mutationstesting verändert Ihren Produktcode auf eine deterministische Weise und überprüft, ob Ihre Test-Suite den induzierten Fehler findet. Dies führt zu kleinerem und einfacherem Quellcode und Sie können sagen, wann Sie mit dem Testen fertig sind.

comments powered by Disqus