Was ist Mutation Testing?

Neben den bereits bekannten Unit Tests, Integrationstests usw. hat sich der Begriff des Mutation Testing etabliert. Beim Mutation Testing werden bestimmte Anweisungen des Quellcodes geändert (mutiert), um die Fehlererkennung von Testfällen in der Softwareentwicklung automatisch zu prüfen.

Wie funktioniert Mutation Testing?

Das Ziel von Mutationstests ist es, die Qualität der Testfälle in Bezug auf Robustheit zu gewährleisten. Hierfür werden Änderungen am Quellcode durchgeführt und die Tests sowohl auf das Originalprogramm als auch auf die jeweiligen Mutanten ausgeführt. Die Änderungen sollten klein gehalten werden, um die Gesamtfunktionalität des Programms nicht zu beeinträchtigen.

Man spricht von Mutanten töten, wenn die Testfälle den mutierten Code erkennen und somit fehlschlagen. Ist dies nicht der Fall, dann:

  1. Es müssen genauere Testfälle geschrieben werden, um alle mutierten Versionen des Programms zu töten.
  2. Ein Mutant und das ursprüngliche Programm sind funktional äquivalent. Der Mutant kann in diesem Fall unmöglich durch die Testdaten/Testansteuerung getötet werden, weil sich das mutierte Programm trotz Änderung weiterhin wie gewünscht verhält.

Für die qualitative Bewertung der Testfallqualität wird der Mutation Adequacy Score (oder kurz Mutation Score) herangezogen. Er lässt sich wie folgt berechnen:

Mutation Score = Getötete Mutanten / (Alle Mutanten – funktional äquivalente Mutanten) * 100

Die funktional äquivalente Mutanten müssen aussortiert werden, da sie im Allgemeinen nicht getötet werden können.

Vorteile von Mutation Testing

  1. Im Gegensatz zur Code-Abdeckung, wird tatsächlich überprüft, ob die Tests in der Lage sind, Fehler im ausgeführten Code zu erkennen.
  2. Hilft den Testern, bessere Tests zu schreiben oder Schwachstellen in den verwendeten Testdaten zu finden.
  3. Die Mutationsoperatoren basieren auf klassischen Programmierfehlern, z. B. durch Verwendung eines falschen Operators oder Variablennamens, oder erzwingen die Implementierung von typischen Grenzwertsituationen (Division eines Ausdrucks durch Null, Verarbeitung von Null-Referenzen) etc.

Nachteile von Mutation Testing

  1. Zeitaufwendig und kostenintensiv, da viele Mutanten generiert und ausgeführt werden müssen.
  2. Kein Black-Box Testing möglich, da Zugriff auf Quellcode erforderlich.

Arten von Änderungen

Es gibt sehr viele Mutationsoperatoren, unter anderem auch explizit für objektorientierte Programmiersprachen. Hier einige Beispiele (für Java):

  • Arithmetic Operator Replacement (AOR): counter++; wird zu counter--;
  • Relational Opereator Replacement (ROR): if (age > 18) {...} wird zu if (age >= 18) {...}
  • Variable Initialization Elimination (VIE): float price = 12.37; wird zu float price;
  • Access Modifier Change (ACM): z.B. private-Modifier wird zu public oder protected
  • Polymorphism New method call with Child class type (PNC): Animal a; a = new Animal(); wird zu Animal a(); a = new Dog();

Werkzeugunterstützung

Mutation Testing kann schnell kompliziert und aufwendig werden , wenn man die Änderungen am Programm manuell durchführt (vor allem auch, wenn man die hohe Anzahl an Mutationsoperatoren bedenkt). Glücklicherweise werden i.d.R. Automatisierungswerkzeuge eingesetzt, um den Prozess zu beschleunigen und die Kosten zu senken. Folgende zwei Werkzeuge sind sehr bekannt in diesem Bereich. Beide unterstützen verschiedene Programmiersprachen und bieten eine große Auswahl an Mutationsoperatoren: