Was sind Flaky Tests und warum sind diese ein nicht zu unterschätzendes Problem für Ihr Projekt?

Wenn ein Softwareprojekt eine bestimmte Größe erreicht, gibt es in der Regel immer ein paar Tests, die die CI/CD-Pipeline erfolgreich durchlaufen, aber sporadisch auch mal fehlschlagen. Dabei kann es sich um Flaky Tests handeln.
Dieser Artikel basiert auf meiner wissenschaftlichen Arbeit zu diesem Thema. Er erklärt die Hintergründe und gibt einen Überblick über den aktuellen Forschungsstand. Entwickler erhalten Impulse, was bereits in der Entwicklung berücksichtigt werden sollte, um Flaky Tests zu vermeiden.
Der Beitrag gibt zudem einen Eindruck wie man mit der Herausforderung besser umgehen kann und so Zeit und Kosten spart.

Das erwartet Sie im folgenden Blog-Beitrag:

Was sind Flaky Tests?

Tests sind in der Softwareentwicklung ein Hilfsmittel um zu überprüfen, ob geforderte Features in einem Projekt umgesetzt wurden. Durch die regelmäßig wiederholte Ausführung dieser Tests wird überprüft, ob bereits implementierte Features auch nach notwendigen Code-Änderungen während der Weiterentwicklung des Projekts weiterhin funktionieren (Regressionstests). Aber nicht alle Tests, die plötzlich schief gehen, stehen im Zusammenhang mit den jüngsten Code-Änderungen: Manche Tests haben ein nicht-deterministisches Verhalten – sie schlagen sporadisch fehl, auch wenn es zwischen mehreren Testläufen keine Änderung gab. Diese „flackernden“ Tests werden in der englischsprachigen Literatur „Flaky Tests“ genannt. Es werden 4,56% [Ziftci 2020] bis 13% [Labuschagne 2017] der Testfehler in einem Softwareprojekt auf Flaky Tests zurückgeführt.

Was macht Flaky Tests so unangenehm und teuer?

Die Zeit, die ein Entwickler aufwendet, um herauszufinden wie seine letzte Code-Änderung zu einem Fehlschlag eines Flaky Tests führte bzw. wie man den Test reparieren kann, ist verschwendet, wenn überhaupt kein Zusammenhang von Änderung zu Fehlschlag besteht. Die Auslieferung der neuen Software-Iteration kann ins Stocken geraten und das Vertrauen der Entwickler in die Tests und damit in die Qualität des Produkts schwindet. Dies ist ein in der Literatur wohlbekanntes Problem [Luo 2014, Lampel 2021, Lam 2019, Ahmad 2021]. Ein weiteres Problem mit fehlerhaften Tests wurde beobachtet, wenn die Programmierung und das Testen von getrennten Teams durchgeführt werden: Niemand fühlt sich für einen fehlschlagenden Test verantwortlich, sodass die Entwickler sagen, die Tester müssten ihren Testcode korrigieren, während die Tester meinen, der Quellcode müsse von den Entwicklern korrigiert werden [Ahmad 2021].

Der derzeitige Umgang mit Flaky Tests

Fehlgeschlagene Tests können ignoriert und über einen längeren Zeitraum beobachtet werden: Wechseln diese über die Zeit mehrmals zwischen Erfolg und Fehlschlag, können diese so als Flaky Tests identifizierten Tests gesondert untersucht werden.

Diese manuelle Identifizierung von Flaky Tests über mehrere Softwareiterationen hinweg kann beschleunigt werden, indem die Test-Suite nach jeder Code-Änderung mehrfach ausgeführt wird. Dieses Vorgehen wird „Re-Run“ genannt und wird von vielen CIs beherrscht, welche damit auch automatisch Flaky Tests identifizieren können. Die Anzahl der benötigten Re-Runs kann je nach Art des Projekts stark schwanken: In einer Studie wurden 95% der Flaky Tests mit 472 Re-Runs gefunden [Gruber 2021], in einer anderen waren 1.000 notwendig, um zwei Drittel zu finden bzw. 10.000 um alle zu finden [Alshammari 2021]. Die Methode Re-Run kann folglich recht kostspielig sein was die Rechenressourcen und Zeit angeht. Google verwendet z.B. 2% bis 16% seiner Rechenressourcen für Re-Runs [Micco 2017]. Im Arbeitsalltag wäre eine automatische, aber effiziente Erkennung von Flaky Tests für den Entwickler sehr hilfreich. Ein schnelles Feedback in der Testergebniszusammenfassung, ob ein Test aufgrund der neuesten Änderung fehlgeschlagen ist oder ob es sich um einen Flaky Test handelt, würde Aufwand und damit Zeit bzw. bares Geld sparen. Es ist sogar vorstellbar, dem Entwickler an gleicher Stelle noch automatisiert Hinweise an die Hand zu geben, wie ein Flaky Test repariert werden könnte. Das Tool RootFinder soll den Ursprung der Flakiness in .NET-UnitTests automatisch erkennen können [Lam 2019] und dem Entwickler dann sofort bewährte Lösungsstrategien angeboten werden. Das Tool ist jedoch – nach meinem Verständnis – nicht in der Lage zu erkennen, ob ein Test tatsächlich flaky ist, sondern findet Muster, die typisch für bestimmte Ursachen für Flakiness sind. Im Folgenden werden diese Ursprünge von Flakiness näher beleuchtet.

Ursachen für Flaky Tests

Die Gründe für Flaky Tests sind mannigfaltig. Luo et al. haben 2014 zehn Kategorien für Ursachen von Flaky Tests definiert, welche durch spätere Publikationen noch ergänzt wurden. Demnach gibt es die folgenden typischen Ursachen:

Order Dependency (OD)

Diese Kategorie beschreibt Tests, die auf denselben Daten arbeiten und bei denen ein Test den Zustand eines anderen verändert, sogenannte „data-pollution“ [Gyori 2015]. Eine Änderung der Reihenfolge, in der die Tests die Daten verändern, kann also zu unterschiedlichen Testergebnissen führen [Zhang 2014, Luo 2014].

Anmerkung: OD-Tests lassen sich mit einer Abwandlung von Re-Run effizienter erkennen als Non-OD-Tests. Hier können schon 209 Re-Runs ausreichend sein, um 95% der Flaky Tests zu erkennen [Gruber 2021]. Zu diesem Zweck kann zwischen einzelnen Re-Runs die Reihenfolge geändert werden, z.B. in umgekehrter Reihenfolge oder randomisiert [Zhang 2014]. OD ist der Grund für etwa 12% [Luo 2014], bis zu möglicherweise 50,5% [Lam 2019] oder auch 59% [Gruber 2021] aller Flaky Tests. Für den Rest der Flaky Tests (die Non-OD-Tests) müsste man also sowieso wieder die deutlich höhere Anzahl an Re-Runs wählen.

Async Wait

Ein fehlerhafter Test wird in diese Kategorie eingeordnet, wenn die Testausführung einen asynchronen Aufruf durchführt und das Ergebnis verwendet, ohne darauf zu warten, dass das Ergebnis zur Verfügung steht [Luo 2014].

Concurrency

Diese Klassifizierung beschreibt Tests mit verschiedenen interagierenden Threads, die zu Race-Conditions, Verletzungen von atomaren Zugriffen oder zu Deadlocks führen [Luo 2014].

Resource Leak

Dies tritt auf, wenn die Anwendung nicht in der Lage ist, eine Ressource ordnungsgemäß zu allokieren oder freizugeben (z.B. Speicherzuweisungen, Datenbankverbindung) [Luo 2014].

Network

Diese Klassifizierung umfasst Remote-Verbindungsfehler und fehlerhaftes Socket-Management [Luo 2014].

Time

Diese Klassifizierung beschreibt Fehler, die durch die Abhängigkeit von der Systemzeit verursacht werden. Zum Beispiel wenn ein Test fehlschlägt, weil sich die Mitternacht in der UTC-Zeitzone ändert oder aber weil verschiedene Hardware-Plattformen unterschiedliche Präzision bei der Zeitmessung bieten [Luo 2014].

IO

Dies bezieht sich auf Lese-/Schreiboperationen von Dateien, d. h. nicht ordnungsgemäß geschlossene File Reader, die aufeinanderfolgende Lesevorgänge blockieren [Luo 2014].

Randomness

Wenn ein Zufallszahlengenerator verwendet wird, aber nicht alle möglichen Ergebnisse verarbeitet werden, fällt ein Flaky Test in diese Kategorie [Luo 2014].

Floating Point Operations

Fließkommaoperationen können zu Overflows und Underflows führen, wodurch ein Test fehlerhaft wird [Luo 2014].

Unordered Collections

Code, der davon ausgeht, Ergebnisse in einer bestimmten Reihenfolge zu erhalten, obwohl die in der Anwendung verwendete Collection keine Reihenfolge garantiert, verhält sich anders als erwartet [Luo 2014].

Program Logic

Diese Kategorie beschreibt Fehler aufgrund von nicht ordnungsgemäß behandelten Edge Cases (In der Regel Software-Bugs) [Thorve 2018].

UI

Nachlässig gestaltete Widget-Layouts auf UIs oder Missverständnisse des zugrundeliegenden UI-Rendering-Prozesses [Thorve 2018] sowie Versuche, eine Aktion auf einer UI-Komponente auszuführen, bevor diese vollständig gerendert ist oder Timing-Probleme bei Animationen [Romano 2021] sind einige Gründe, die als zusätzliche Probleme bei Projekten mit UI festgestellt wurden.

Too Restrictive Range

Diese Kategorie beschreibt falsche Behauptungen, die in den Test-Code festgelegt wurden: Der Test schlägt fehl, obwohl die Ausgabe gültig ist. Die falsche Platzierung der Assertion-Anweisungen fällt auch in diese Kategorie [Eck 2019].

Test Case Timeout

Tests, die zu lange laufen, gehören zu dieser Kategorie. Sie schaffen es beispielsweise nicht, die Dependencies rechtzeitig herunterzuladen oder sie produzieren für eine bestimmte Zeit keine Ergebnisse und werden daher vom Ausführungssystem abgebrochen, in der Annahme, dass der Test hängen geblieben ist [Eck 2019].

Test Suite Timeout

Im Gegensatz zur vorherigen Klassifizierung wird die gesamte Testsuite aufgrund einer Zeitüberschreitung abgebrochen. Dies geschieht, wenn die Testsuite wächst, aber der maximale Laufzeitwert nicht entsprechend angepasst wird [Eck 2019].

Dependency

Thorve et al. haben bei ihren Untersuchungen von Android-Apps Unterschiede in den Testergebnissen beobachtet, abhängig von Hardware und Android OS-Version sowie Abhängigkeiten von Drittanbieter-Bibliotheken [Thorve 2018]. Bestimmt hat der geneigte Leser solche oder ähnliche Erfahrungen auch schon selbst bei ganz anderen Plattformen als Android gemacht.

Platform Dependency

Diese Klassifizierung steht für nicht-deterministische Testfehler, die nur auf bestimmten Plattformen auftreten [Eck 2019].

Anmerkung: Die Literatur ist sich nicht einig, ob die Hardware, auf denen das Testsystem läuft, tatsächlich ein Grund für Flaky Tests sein kann. Die erste Taxonomie verneint dies und sieht die einzige mögliche Plattformabhängigkeit bei der Zeitmessung (siehe Kategorie „Time“). Spätere Studien sehen Anzeichen für Plattformabhängigkeiten und haben mehrere recht ähnliche Kategorien ergänzt (siehe Kategorien „Dependency“, „Platform Dependency“, „Infrastructure“).

Es wurden unterschiedliche Testergebnisse für Android-Apps beobachtet, abhängig von der Hardware und der Android OS-Version [Thorve 2018]. Auch „Plattformfragmentierung“ kann ein zunehmendes Problem für mobile Apps werden: Die schnelle Entwicklung des Android-Betriebssystems führt dazu, dass eine große Anzahl von Android-Betriebssystemversionen auf dem Markt verfügbar ist und dass sich Apps auf verschiedenen Android-Plattformen unterschiedlich verhalten können [Thorve 2018].

Infrastructure

Dies beschreibt einen Test, der aus Gründen außerhalb des Projektcodes, aber innerhalb der Testausführungsumgebung fehlerhaft ist, z.B. wenn die Installation von Abhängigkeiten fehlschlägt. Infrastruktur-Flakiness unterscheidet sich von anderen Arten von Flakiness, da sie nicht durch das Projekt selbst, sondern durch externe Komponenten verursacht wird [Gruber 2021].

Neue Ansätze zum Identifizieren von Flaky Tests

Wie können nun also die gerade betrachteten Ursachen für Flakiness automatisch erkannt werden? Hierzu gibt es verschiedene Ansätze, die im Folgenden in 4 Kategorien gruppiert und zusammengefasst wurden.

Analyse von Quellcodeänderungen

Das Tool „DeFlaker“ [Bell 2018] basiert auf der Idee, dass ein Test flaky sein muss, wenn er plötzlich fehlschlägt, obwohl kein für diesen Test relevanter Code geändert wurde. Die Überprüfung, ob ein fehlgeschlagener Test geänderten Code ausgeführt hat, erfolgt durch den Vergleich von Code-Coverage der Testausführung mit dem tatsächlichen Change-Set des fraglichen Commits.

Fehlschlagstatistiken analysieren

Durch das Analysieren der Testhistorie eines Projekts kann die Fehlerrate jedes Tests mit einer Basislinie von Testfehlern in diesem Projekt verglichen werden [Rehman 2021]. Je mehr ein Test von der Baseline abweicht, je häufiger er also fehlschlägt, desto wahrscheinlicher ist dieser ein Flaky Test und wird als solcher gekennzeichnet. Die objektive Festlegung einer geeigneten Baseline ist jedoch ein ungelöstes Problem. In dem untersuchten Projekt sahen die Entwickler die Tests erst nach der ersten Hälfte der Release-Periode als stabil genug an, um eine Baseline der Fehlschläge zu berechnen, was bedeutet, dass es eine gewisse Vorlaufzeit braucht, bis diese Methode angewendet werden kann.

Metadaten analysieren

Bei diesem Ansatz werden zunächst Job-Fehler durch Experten kategorisiert (Kategorien wie im Abschnitt „Ursachen für Flaky Tests“) [Lampel 2021]. Dann werden Metadaten von Testläufen und dem ausführenden System gesammelt, wie z.B. die Dauer eines Testlaufs oder die CPU-Last. Mittels Data Mining wird dann darin nach Mustern gesucht und die gefundenen Muster auf die entsprechende Expertenklassifizierung des Tests abgebildet. So ist beispielsweise eine lange Laufzeit eines Tests charakteristisch für einen Testabbruch aufgrund eines Timeouts, was wiederum ein typischer Grund für Flaky Tests ist [Luo 2014]. Da die Klassifizierung und das Mapping manuell durchgeführt wird, ist diese Vorgehen aber unter Umständen fehlerbehaftet.

Metadaten und Source-Code analysieren

Das Tool „FlakeFlagger“ [Alshammari 2021] sammelt ebenfalls Metadaten wie die Testausführungszeit und zusätzlich Merkmale aus dem Source-Code, wie z.B. Test Smells [Ahmad 2021]. Anschließend wird Machine Learning auf die Daten angewendet, um vorherzusagen, welche Tests wahrscheinlich fehlerhaft sind. Es gibt weitere Ansätze, um mittels Machine Learning Muster von Schlüsselwörtern im Quellcode zu finden, um auf Flaky Tests zu schließen, jedoch ohne veröffentlichte Implementierung [Pinto 2020, Verdecchia 2021].

Fazit

Ein sofortiges Feedback für den Entwickler, ob ein Testfehlschlag einen echten Fehler durch die letzte Code-Änderung anzeigt, wäre äußerst wertvoll für die Produktivität, das Vertrauen der Entwickler in die Tests, die Softwarequalität des eigenen Produkts und somit in die Arbeit des Teams. Ein Ignorieren der Flaky Tests steht dem entgegen. Ein manuelles Quarantäne-Verfahren, weil die Ressourcen und/oder Wartezeit für tausende Re-Runs nicht aufgebracht werden sollen, ist aufwendig und eher fehlerbehaftet. Die gefundenen Ansätze zur automatischen Identifizierung von Flaky Tests könnten Re-Runs obsolet werden lassen, jedoch ist bisher keiner ohne Nachteil. Manche brauchen bestimmte Vorbedingungen, andere haben keine gute Erkennungsrate. Alle vorgestellten Ansätze zur automatischen Identifizierung analysieren Java Projekte ohne UI bzw. E2E-Tests. Die weitere Entwicklung in diesem Bereich bleibt spannend.

Das Wissen über die erläuterten Ursachen für Flaky Tests ist für Entwickler hilfreich. Es sensibilisiert hinsichtlich einiger Themen auf die beim Schreiben von Source-Code und Tests geachtet werden sollte. Wenn bei einem als solchen identifizierten Flaky Test bekannt wäre, welcher dieser Gründe die Ursache ist, würde dies zu Blue Prints führen, wie der Flaky Test zu verbessern ist, wenn man weiß, wie andere Tests aus dieser Kategorie behoben wurden. Die Erforschung zur automatisierten Kategorisierung von Flaky Tests ist noch nicht weit fortgeschritten, aber sinnvoll im Auge zu behalten oder sogar selbst an der Erforschung teilzunehmen.

Quellen

[Ziftci 2020] C. Ziftci and D. Cavalcanti, „De-flake your tests: Automatically locating root causes of flaky tests in code at google

[Labuschagne 2017] A. Labuschagne, L. Inozemtseva, and R. Holmes, „Measuring the cost of regression testing in practice: A study of java projects using continuous integration

[Ahmad 2021] A. Ahmad, O. Leifler, and K. Sandahl, „Empirical analysis of factors and their effect on test flakiness – practitioners’ perceptions

[Gruber 2021] M. Gruber, S. Lukasczyk, F. Krois, and G. Fraser, „An Empirical Study of Flaky Tests in Python

[Alshammari 2021] A. Alshammari, C. Morris, M. Hilton, and J. Bell, „FlakeFlagger: Predicting flakiness without rerunning tests

[Micco 2017] J. Micco, „The state of continuous integration testing google

[Gyori 2015] A. Gyori, A. Shi, F. Hariri, and D. Marinov, „Reliable testing: Detecting state-polluting tests to prevent test dependency

[Luo 2014] Q. Luo, F. Hariri, L. Eloussi, and D. Marinov, „An empirical analysis of flaky tests

[Thorve 2018] S. Thorve, C. Sreshtha, and N. Meng, „An empirical study of flaky tests in android apps

[Romano 2021] A. Romano, Z. Song, S. Grandhi, W. Yang, and W. Wang, „An empirical analysis of UI-based flaky tests

[Eck 2019] M. Eck, F. Palomba, M. Castelluccio, and A. Bacchelli, „Understanding flaky tests: The developers perspective

[Zhang 2014] S. Zhang, D. Jalali, J. Wuttke, K. Mu ̨slu, W. Lam, M. D. Ernst, and D. Notkin, „Empirically revisiting the test independence assumption

[Lam 2019] W. Lam, P. Godefroid, S. Nath, A. Santhiar, and S. Thummalapenta, „Root causing flaky tests in a large-scale industrial setting

[Bell 2018] J. Bell, O. Legunsen, M. Hilton, L. Eloussi, T. Yung, and D. Marinov, „DeFlaker: Automatically detecting flaky tests

[Rehman 2021] M. H. U. Rehman and P. C. Rigby, „Quantifying no-fault-found test failures to prioritize inspection of flaky tests at ericsson

[Lampel 2021] J. Lampel, S. Just, S. Apel, and A. Zeller, „When life gives you oranges : Detecting and diagnosing intermittent job failures at mozilla

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte beachte unsere Nutzungsrichtlinien

Mehr zu diesem Thema

Deadlock durch Logging

Deadlocks sind oft ein schwer zu lösendes Problem. Wenn dann dabei auch noch die Logging-Komponente, wie z.B. log4net, beteiligt ist, dann ist das nicht nur

Weiterlesen »

Nachweislich keine Deadlocks

Beim Ausführen mehrerer Threads kann es vorkommen, dass sich mehrere Mutexe blockieren. Dadurch kann keiner der Threads weiter arbeiten – es kommt also zum Deadlock.

Weiterlesen »
Um unsere Webseite für Sie optimal zu gestalten und fortlaufend verbessern zu können, verwenden wir Cookies. Weitere Informationen finden Sie in unserer Datenschutzerklärung.