Mutationstests

Von: Thomas Lindegaard

Qualitätssicherung durch Code Coverage und Mutationstests

Fehlerhafte Codes schnell und sicher erkennen

Arbeitet man in der Lösungsentwicklung mit automatischer Qualitätssicherung verlässt man sich auf die Tests: Sie sollen prüfen, ob ein Code gut funktioniert oder nicht. Doch wie lässt sich die Qualität eines Qualitätstests überprüfen? Denn auch diese Tests beruhen auf Code – und jeder Code kann fehlerhaft sein. Wie also kann man einen solchen Test überprüfen?

Kriterien für die Qualität eines Tests

  • Objektivität: Misst der Test unabhängig von den eigenen Erwartungen? Bei einem Qualitätstest heißt das, er erfasst auch Fehler, die die Entwickler*innen nicht vorhersagen konnten.
  • Reliabilität: Misst der Test ausreichend genau?
  • Validität: Misst der Test auch wirklich das, was er soll?

Code Coverage

Rekursion, d.h. das immer neue Schreiben von Tests für die Tests, führt schnell in eine Endlosschleife. Eine Lösung ist Code Coverage. Hierbei wird quantitativ gemessen, wie viel Code durch die vorhandenen Tests beeinflusst wird. An einem konkreten Beispiel erklärt: Die Methode soll den Namen einer Person mit „Mr. “ davor versehen, wenn die Person 21 Jahre oder älter ist:

Mit Code Coverage lässt sich untersuchen, ob alle Zweige des Codes betroffen sind. Im Beispiel kann Code Coverage sagen, ob der Test die Szenarien abdeckt, in denen wir die if-Anweisung eingeben. Mit folgendem Test erreichen wir 100 Prozent Code Coverage für die Methode:

Das Beispiel verdeutlicht zwei Mängel von Code Coverage: Erstens kann dadurch nur eines von zwei Ergebnissen abgedeckt werden, im Beispiel dasjenige mit dem vorangestellten Titel. Zweitens lässt sich dieselbe Code Coverage erreichen, ohne tatsächlich etwas zu testen:

Hier wird eine Code Coverage von 100 Prozent erreicht – aber selbst, wenn der Code einen Fehler enthält, wird der Test niemals fehlschlagen. Auch bei einer Codeabdeckung von 100 Prozent lässt sich daher nicht sagen, dass die Tests umfassend sind. Code Coverage kann also Bereiche aufzeigen, die nicht getestet wurden. Aber das Tool ist nicht stark genug, um zu bestätigen, dass Tests so funktionieren, wie sie sollten.

Mutationstests

Die Alternative zu Code Coverage sind sogenannte Mutationstests. Beim Mutationstest wird gemessen, wie gut getestet wurde, indem der Produktionscode mutiert und der geschriebenen Test-Code beibehalten wird. Man verändert also bewusst den ursprünglich getesteten Code, um zu sehen, ob die Tests damit fehlschlagen. Eine einfache Mutation wäre z.B. eine Änderung von „>“ in „> =“.

  • Wenn die Änderung dazu führt, dass ein oder mehrere Tests fehlschlagen, nennt man die Mutation „tot“ – was großartig ist, da der Test funktioniert hat.
  • Führt die Mutation hingegen nicht zu einem Fehlschlag, hat die Mutation „überlebt“. Für jede „überlebende“ Mutation gibt es eine Stelle im Code, die verändert werden kann, ohne entdeckt zu werden. Je mehr „überlebende“ Mutationen, desto schlechter ist der Qualitätstest.

Zwei Beispiele für Mutationen im Beispiel-Code:

Werkzeuge

Mutationstests von Hand wären unzuverlässig und ineffizient. Darum gibt es Tools, die eine Vielzahl von Mutationen durchführen und die Tests damit dann automatisch testen. Diese Tools erstellen auch Berichte, die die überlebenden Mutationen beschreiben.

In einem Hackathon haben unserer Expert*innen Stryker.NET in drei laufenden Projekten eingesetzt. Die Resultate sind von Projekt zu Projekt und von Klasse zu Klasse unterschiedlich. Hier ein Teilergebnis, bei dem 76 Prozent der Mutationen „starben“:

Eine Methode in einer Klasse hat ein seltsames Ergebnis geliefert. Diese Methode ist ungültig: Alle Mutationen des Codes selbst innerhalb dieser Methode wurden durch Tests gefunden, aber die Mutante, die den gesamten Code entfernte, „überlebte“. Es gab also keine Tests, die die Nebenwirkungen dieser Methode erkannten.

In der aktuellen Version des verwendeten Tools haben unsere Expert*innen einige Hürden gefunden. Unter anderem dauert die Ausführung in einer Continuous-Integration-Pipeline lange, weshalb es manchmal zu Zeitüberschreitungen kam. Das bedeutet, dass das Tool nicht gut als Gate in Pipelines verwendet werden kann. Stattdessen kann man täglich Mutationstests durchführen und die Ergebnisse kontinuierlich analysieren. Die Zeit, die das in Anspruch nimmt, wächst exponentiell mit der Menge und Komplexität des Codes. Eine Microservice-Architektur verkürzt die Prozesszeit. Das Tool kann aber auch direkt im Entwicklungsprozess integriert werden. Hier können nur die Bereiche des Codes mutiert werden, an denen gearbeitet wird.

Fazit

Mutationstests helfen, die Qualitätssicherung bei Software zu verbessern. Sie stellen sicher, dass die Tests, die Developer schreiben und auf die sie sich verlassen, tatsächlich geeignet sind und einen Mehrwert bieten. Unsere Expert*innen empfehlen, Mutationstests zu Beginn zu priorisieren. Korrelieren Sie hierzu beispielsweise Mutations-Scores mit einer Hot-Spot-Analyse. So können Sie  zuerst mit den Bereichen im Code arbeiten, die sich häufig ändern – in Kombination mit der Tatsache, dass es viele überlebende Mutanten gibt.

Unsere Entwickler*innen erstellen maßgeschneiderte Lösungen für Kunden aus ganz unterschiedlichen Branchen. Mehr Informationen dazu finden Sie hier.

 

Dieser Beitrag wurde uns freundlicherweise von der Skaylink Tochter cVation zur Verfügung gestellt. Den Originalbeitrag finden Sie hier.

Starten wir gemeinsam in die Zukunft.

Unsicher, wohin die digitale Reise bei Ihnen führen soll? Unsere Expert*innen stehen Ihnen gerne unverbindlich für Ihre Fragen zur Verfügung!

Einfach das nebenstehende Formular ausfüllen und wir melden uns umgehend bei Ihnen.