Schwerpunkte

Software-Engineering

Mit Mutanten Tests testen

26. Oktober 2020, 15:30 Uhr   |  Autor: Michael Wittner, Redaktion: Selina Doulah

Mit Mutanten Tests testen
© Shutterstock

Die Software von sicherheitskritischen Systemen zu testen, ist ein absolutes Muss. Aber wie stellen Designer:innen sicher, dass die Tests selbst überhaupt etwas taugen? Mit Mutationen werden die Tests auf die Probe gestellt.

Bei »sicherheitskritischen Systemen« geht es nicht immer gleich um die Landeklappen eines Flugzeugs, bei denen eine Fehlfunktion der Software fatale Folgen haben kann. Sicherheitskritisch können bereits scheinbar einfache Anwendungen sein, wie etwa ein Fahrstuhlknopf. Denn drückt jemand auf die Taste, sollen sich die Türen nur dann öffnen, wenn sie den Zugang zur Kabine gewähren – und nicht einen gähnend leeren Fahrstuhlschacht freigeben. Funktionen wie diese werden durch Software gesteuert. Um so ein System sicher zu entwickeln, muss das Programmierteamoder die Abteilung der Qualitätssicherung den Code genau definierten funktionalen und nicht-funktionalen Tests gemäß den Sicherheitsstandards unterziehen.

Sowohl funktionale als auch nicht-funktionale Codeprüfungen können mithilfe von Testwerkzeugen durchgeführt werden, die Tausende von Codezeilen (fast) automatisch auf Fehler durchsuchen. Die im Testwerkzeug hinterlegten Testfälle prüfen, ob der ausgeführte Code tatsächlich die erwartete Funktion auslöst. Ist das nicht der Fall, gilt es, den Fehler im Code zu finden. Wird die Funktion aber wie erwartet durchgeführt, ist der Code richtig – und das Testwerkzeug zeigt keinen Fehler an. Was sich an dieser Stelle gut anhört, ist aber trügerisch. Denn ein Testergebnis kann nur dann verlässlich sein, wenn auch der Test selbst gut ist. Bei sicherheitsrelevanten Anwendungen gilt es daher nachzuweisen, dass auch die Testfälle eine hohe Qualität haben.

Codeabdeckung allein reicht nicht

Einer der in der Praxis am häufigsten verwendeten Qualitätsmaßstäbe in der Softwareentwicklung ist die Messung der Codeabdeckung. Die Abdeckung zeigt, welche Teile des Codes während des Tests ausgeführt wurden. Sie liefert aber keine Information darüber, was getestet wurde, sondern zeigt nur, was nicht getestet wurde. Damit lässt sich sichernachweisen, dass 100 Prozent des Codes getestet wurde. Aber als Qualitätsmaßstab reicht das nicht aus.

Mutierte Software

Besser geeignet sind sogenannte »Mutationstests«: Mit ihnen lässt sich die Fehlererkennung von Testfällen sogar automatisch prüfen. Die Idee dahinter ist, absichtlich Fehler in die Software einzubauen und diese mutierte Software dann den Tests zu unterziehen. Im Falle einer Embedded-Anwendung wird der zu testende C/C++-Code leicht verändert, also mutiert, sodass typische Programmierfehler vorliegen. Wenn die eingesetzte Testsuite die Mutation erkennt, werden diese Tests als nützlich bewertet. Das Entwicklungsteam erfährt auf diese Weise, welche Testfälle schwach sind, und bekommt konkrete Hinweise, wie diese zu verbessern sind. Damit erzielen sie letztendlich eine bessere Testqualität und erfüllen damit die Anforderungen der vorgeschriebenen Standards.

Mängel müssen erkannt werden

Es ist möglich, Tests für ein Testobjekt – also Tests eines Codeabschnitts – zu erstellen, ohne ein Ergebnis zu prüfen. Dabei sollte zumindest die Prüfung irgendeines Ausgangswerts, einer Sequenz an Funktionsaufrufen (Function Calls) oder andere Verifikationsmöglichkeiten zum Einsatz kommen. Beim Refactoring – der Verbesserung – von Tests können Änderungen dazu führen, dass alle Verifikationen entfernt werden und somit schließlich ein leerer Test vorliegt. Eine automatisierte Prüfung der Tests sollte solche Mängel bei der Testausführung erkennen.

Ausgangswerte wirklich berechnen

Bild 1. Der Test prüft einen Ausgangswert, der nicht durch das Testobjekt aktualisiert wurde.
© Razorcat

Bild 1. Der Test prüft einen Ausgangswert, der nicht durch das Testobjekt aktualisiert wurde.

Das Beispiel in Bild 1 zeigt eine schwache Testdefinition: Da das Testobjekt nicht jedes Mal einen neuen Wert für die Ausgangsvariable berechnet, ist das Ergebnis zufällig – hier wiederholte sich das Ergebnis des vorherigen Testfalls.

Aus diesem Grund werden die Ausgangswerte von Tests normalerweise manuell mit einem ungültigen Wert initialisiert, um zu sehen, ob das Ergebnis vom Testobjekt erstellt wurde. Der Nachteil eines solchen Ansatzes besteht darin, dass es Eingangswerte gibt, die nur technisch notwendig sind, aber von den eigentlichen Testaspekten ablenken.

Eine automatisierte Initialisierung der Ausgangswerte mit unterschiedlichen Patterns würde dabei helfen, die Testdefinition von technischen Artefakten zu bereinigen und das Augenmerk mehr auf die testrelevanten Aspekte zu lenken.

Erkennt ein Test unbeabsichtigte Codeänderungen?

Bild 2. Mutierter Code, der immer noch die gleichen Testergebnisse liefert.
© Razorcat

Bild 2. Mutierter Code, der immer noch die gleichen Testergebnisse liefert.

Vor fast 50 Jahren schlug der Computerspezialist Richard Lipton [4] vor, eine Modifikation des zu testenden Codes vorzunehmen, um einen Fehler einzuschleusen, und dann die Testsuite erneut auszuführen. Wenn die Testsuite fehlschlägt, war sie in der Lage, diesen Fehler zu erkennen.

Das Beispiel in Bild 2 zeigt eine leichte Mutation des Testobjekts, die von den vorhandenen Tests nicht erkannt wurde, obwohl sie 100 Prozent der Codezweige abdeckten. Das deutet darauf hin, dass Tests fehlen.

In neueren Veröffentlichungen wird diese Idee aufgegriffen [3]: Mutationstests werden als eine mögliche Lösung für solche Fragestellungen vorgestellt. Problematisch waren in der Vergangenheit die fehlende Werkzeugunterstützung und die hohe Anzahl von Mutationen. Moderne Tools lösen dieses Problem durch Parallelisierung und bieten Mutationstests für Programmiersprachen wie JAVA, C/C++ oder C# an. Jedoch unterstützt kein Tool die Ausführung mit Compilern und Zielplattformen speziell für Embedded-Software.

Begriffe und Definitionen für Mutationstests

Bei der Modifikation von Programmen sind der Fantasie keine Grenzen gesetzt, jedoch muss der Code syntaktisch korrekt bleiben, um ihn in den Tests ausführen zu können.

Typische Mutationen in C-Programmen sind Änderungen in logischen Ausdrücken – zum Beispiel Ersetzen eines logischen UND-Operators durch einen logischen ODER-Operator –, die Modifikation von arithmetischen Ausdrücken – wie das Hinzufügen eines konstanten Wertes zu einem vorgegebenen Ausdruck – und die Manipulation von Variablen – beispielsweise der Austausch von zwei lokalen Variablen. Auch Änderungen im Programmablauf sind möglich, zum Beispiel durch das Entfernen eines ELSE-Zweigs oder das Einfügen einer RETURN-Anweisung.

Jede Anwendung eines Mutationsoperators erzeugt einen Mutanten. Mutanten, die sich wie ein nicht modifizierter Code verhalten, werden als äquivalente Mutanten bezeichnet.

Nach der Ausführung der Testsuite liegen folgende Ergebnisse vor:

Beseitigte Mutanten sind solche, die durch einen fehlgeschlagenen Test entdeckt wurden. Das ist ein guter Testfall, da Code-Modifikation entdeckt wurde.

Verbliebene Mutanten sind solche, die keinen fehlgeschlagenen Test hervorgebracht haben. Die Testsuite war nicht in der Lage, die Modifikation zu erkennen. Entweder weisen die Tests Schwächen auf oder es handelt sich um einen äquivalenten Mutanten. Das muss nun manuell untersucht werden.

Für jeden Test kann anhand des Prozentsatzes beseitigter Mutanten im Vergleich zur Gesamtzahl an Mutanten ein Mutationswert berechnet werden. Alle Tests mit einem Mutationswert von Null weisen auf potenzielle Schwächen hin und sollten geprüft werden. Ein Mutationstestzyklus lässt sich gut in eine testgetriebene Entwicklung integrieren, wenn automatisierte Mutationstests zum Einsatz kommen [7]. Die Mutationsanalyse sollte in der Entwicklung Teil einer Feedback-Schleife sein. Mutationstests basieren auf künstlich eingebrachten Fehlern. Doch was bedeutet das nun im Hinblick auf echte Fehler?

Künstliche Fehler

Bild 3. Automatisierter Entwicklungs- und Testzyklus mit Mutationsanalyse.
© Razorcat

Bild 3. Automatisierter Entwicklungs- und Testzyklus mit Mutationsanalyse.

Eine Hypothese besagt, dass „Program- mierer:innen im Allgemeinen kompetent genug sind, um Code zu produzieren, der zumindest fast richtig ist“ [5]. Die meisten eingebrachten Fehler sind kleine syntaktische Fehler. So funktioniert auch ein Mutationstest: Er wendet eine Mutation nach der anderen an und verändert den ursprünglichen Code nur geringfügig.

Auch der Kopplungseffekt gilt [6]: Wenn die Testsuite eine einzige Mutation erkennt, kann sie auch mehrere Mutationen erkennen. Daher ist es nicht notwendig, die Tests mit kombinierten Mutationen durchzuführen. Außerdem kann die gleichzeitige Anwendung mehrerer Mutationen dazu führen, dass eine Mutation durch die andere verborgen bleibt, was zu einem äquivalenten Mutanten führen würde, der nicht beseitigt werden kann.

Parallel testen

Die beiden hauptsächlichen Probleme des Mutationstestkonzepts resultieren aus der hohen Anzahl an Mutanten für eine vorgegebene Codebasis und dem Erzeugen äquivalenter Mutanten. Aufgrund der sehr langen Testdauer bei einer großen Anzahl an Mutanten ist es notwendig, die Ausführung der Mutanten zu optimieren, zum Beispiel indem Entwickler:innen parallel testen. Auch die iterative Ausführung der Mutationstests für geänderte Codeteile ist sinnvoll, um ein schnelles Feedback zu erhalten.

Mutationstests in Normen

Die Norm IEC 61508 – der Entwicklungsstandard für elektrische, elektronische und programmierbare elektronische Systeme – empfiehlt Mutationstests für Sicherheitsintegritätsgrade von SIL 2 bis SIL 4 [1]. Sie besagt auch, dass es möglich ist, die Anzahl der verbleibenden Fehler in einer Software nach der Anwendung von Mutationstests anhand der Anzahl der verbliebenen Mutanten im Verhältnis zur Gesamtzahl der Mutanten zu schätzen.

Der Mutationstest darf aber nicht mit einem Fehlerinjektionstest verwechselt werden, der in der Norm ISO 26262 – dem Standard für sicherheitsrelevante elektrische/elektronische Systeme in Kraftfahrzeugen – als Prüfmethode empfohlen wird [2] und nur eine Art von Robustheitstest darstellt.

Mutationen spüren Fehler auf

Mit Mutationstests können Entwickler:innen und Qualitätsprüfer:innen schwache oder unzureichende Tests aufdecken. Sie spüren potenzielle Fehler in der Software auf und sparen Zeit, weil sich Teile des Reviews von Tests automatisieren lassen. Wichtig hierfür ist die Verfügbarkeit von Tools, um für vorhandene Tests automatisch Mutationen zu erstellen und auszuführen. Innerhalb des Unit-Test-Tools „Tessy“ für C/C++-Code von Razorcat ist der Mutationstest aller bestandenen Tests eine der zusätzlichen Optionen für die automatische Testausführung [8]. Auch die Initialisierung von Ausgangsvariablen mit unterschiedlichen Testdaten-Pattern und die Prüfung des Vorhandenseins von mindestens einer Verifikation innerhalb eines Tests kann bei allen ausgeführten Tests automatisch angewendet werden. Diese Optionen ermöglichen die automati- sche Qualitätsmessung von Tests, die auf Embedded-C/C++-Compilern und Zielplattformen ausgeführt werden.

[1] IEC 61508, Part 3, Table B.2 and part 7, C.5.6

[2] ISO 26262, Part 6, Table 10 and Table 13

[3] Frank Büchner, Franz Graser: „Mutationstest – ein Plädoyer für eine vernachlässigte Testtechnik“, Elektronik Praxis, 2012

[4] R. Lipton: „Fault Diagnosis of Computer Programs“, 1971

[5] Richard A. DeMillo, Richard J. Lipton, and Fred G. Sayward: “Hints on test data selection: Help for the practicing programmer”, IEEE Computer, 11(4):34-41, April 1978

[6] A. Offutt: “The coupling effect: fact or fiction.”, In Proceedings of the ACM SIGSOFT ‚89 third symposium on Software testing, analysis, and verification (TAV3)

[7] “Real world mutation testing” for JAVA code, http://pitest.org

[8] Razorcat, TESSY: https://www.razorcat.com/en/product-tessy.html

Der Autor

Michael Wittner ist Geschäftsführer von Razorcat in Berlin und seit über 25 Jahren im Bereich Softwareentwicklung und Test tätig. Nach dem Studium der Informatik an der TU Berlin arbeitete er als wissenschaftlicher Mitarbeiter bei Daimler an der Entw
© Razorcat

Michael Wittner

Michael Wittner ist Geschäftsführer von Razorcat in Berlin und seit über 25 Jahren im Bereich Softwareentwicklung und Test tätig. Nach dem Studium der Informatik an der TU Berlin arbeitete er als wissenschaftlicher Mitarbeiter bei Daimler an der Entwicklung von Testmethoden und -werkzeugen. Seit 1997 ist Wittner geschäftsführender Gesellschafter von Razorcat Development, Hersteller des Unit-Test-Tools Tessy und CTE sowie des Testmanagement-Tools ITE und der Testspezifikationssprache CCDL.

Auf Facebook teilenAuf Twitter teilenAuf Linkedin teilenVia Mail teilen

Das könnte Sie auch interessieren

Technische Grenzen einfach umgehen
Gendern ist für Alle da!
Von Diskursen und wachsendem Gras

Verwandte Artikel

Razorcat