Zum Inhalt springen

Suchen...

Automatische Testfallselektion für Regressionstests

6,5 Stunden Testlaufzeit, auf 10-15 Minuten reduziert: Wie Testfallselektion über C++ und Java hinweg das möglich macht.

8 Min. Lesezeit
Cover für Automatische Testfallselektion für Regressionstests

Regressionstestfälle selektieren bedeutet, bei einem Code-Check-in automatisch nur die Testfälle auszuführen, die von der jeweiligen Änderung tatsächlich betroffen sind. Die Grundlage bildet eine nächtlich aufgebaute Zuordnung: Welche Dateien, Klassen oder Funktionen öffnet jeder Testfall? Ändert sich eine davon, laufen nur die passenden Tests. Das reduziert Läufe von mehreren Stunden auf oft 10 bis 15 Minuten.

Das Wichtigste in Kürze

  • Die Kombination aus nächtlicher Testphasen-Analyse und compile-zeitlicher C++-Abhängigkeitsverfolgung ermöglicht eine vollautomatische Testfallselektion über Sprachgrenzen hinweg, ohne manuellen Eingriff.
  • Eine Test-Suite mit 25.000 Testfällen und ursprünglich 6,5 Stunden Laufzeit lässt sich durch Selektion in mehr als 50 Prozent der Commits auf 10 bis 15 Minuten Feedbackzeit reduzieren.
  • Die Granularität der Selektion entscheidet über die Effizienz: Auf DLL-Ebene zu selektieren reicht nicht aus, erst die Analyse auf Funktionsebene brachte bei C++ denselben Durchbruch wie die Klassenebene zuvor bei Java.
  • Parallelisierte Validierung über mehrere Wochen hinweg zeigte, dass kein einziger echter Testfehlschlag durch die Selektion übersehen wurde, alle Abweichungen gingen auf Infrastrukturprobleme zurück.
  • Skepsis im Team gegenüber dem Projektstart wich erst, als der Java-Teil fertig war und der konkrete Nutzen messbar auf dem Tisch lag.

Warum lange Regressionstests die Entwicklung blockieren

Eine vollständige Testausführung von sechseinhalb bis sieben Stunden bremst jede Pipeline aus. Bei der IVU laufen rund 25.000 Testfälle, deren kompletter Durchlauf so lange dauert. Wer als Entwickler etwas eincheckt, will nicht stundenlang warten, bis klar ist, ob der Commit sauber war.

Das Problem skaliert mit der Zahl der parallel gepflegten Branches. Bei der IVU sind das bis zu zehn: mehrere Releases stehen bei Kunden, jede Fehlerbehebung wandert im Zweifel durch vier oder fünf Branches. Ein einziger Check-in kann dadurch auf mehreren Branches jeweils sechseinhalb Stunden Tests auslösen.

Auch mit zehn großen Maschinen im Dauerbetrieb reichte die Kapazität nicht. Ein Teil der Testfälle musste bewusst gekappt werden, weil sich sonst ein Rückstau aufbaute. Die Alternative, alles einmal pro Nacht durchzutesten, hat einen Preis: Schlägt morgens ein Test fehl, beginnt erst die Suche nach dem Commit, der den Fehler verursacht hat. Das kostet zusätzlich Analysezeit.

Was Testfallselektion für Regressionstests bedeutet

Testfallselektion heißt, aus einer großen Test-Suite genau die Testfälle auszuwählen, die von einer konkreten Code-Änderung betroffen sind. Statt alle 25.000 Testfälle laufen zu lassen, führt das System nur die aus, die wirklich vom jeweiligen Check-in berührt werden.

Die Selektion läuft bei der IVU vollautomatisch. Ein manueller Eingriff ist nicht nötig. Anhand der eingecheckten Dateien entscheidet das System, welche Testfälle relevant sind, und stößt nur diese an.

Entwickelt wurde der Ansatz gemeinsam mit der TU München. Am Lehrstuhl von Professor Alexander Pretschner arbeitete der Doktorand Daniel Elsner drei Jahre lang im Projekt mit und untersuchte die Optimierung automatischer Regressionstests. Aus mehreren Experimenten entstand schrittweise das heutige Verfahren.

Die Herausforderung: zwei Sprachen und eine Datenbank

Die Software der IVU mischt zwei große Technologien. Weite Teile sind in C++ implementiert, andere in Java. Die Komponenten bauen aufeinander auf: Fahrten erzeugen Dienste, Dienste erzeugen die konkreten Arbeitsanweisungen für Mitarbeiter, und einzelne Stufen dieser Kette liegen mal in C++, mal in Java.

Dazu kommt eine starke Abhängigkeit von Daten in der Datenbank. Ohne Daten lässt sich kaum sinnvoll testen, und alles zu mocken ist zu kompliziert, vor allem für die Randfälle. Deshalb spielt das System zu Beginn eines Testlaufs Daten in eine Datenbank ein, wofür meist die C++-Komponenten gebraucht werden.

Welche Binaries dabei nötig sind, unterscheidet sich pro Testfall. Ein Java-Programm ruft erst ein C++-Binary auf, um seine Testdaten zu erzeugen, und führt danach Aktionen darauf aus, etwa einen Druck erstellen oder eine Schnittstelle in einem Umsystem aktivieren. Nicht jeder Java-Testfall braucht dieselben C++-Binaries, manche gar keine.

Wie die Selektion technisch funktioniert

Der Kern des Verfahrens ist eine Verlinkung zwischen eingecheckten Dateien und den Testfällen, die diese Dateien tatsächlich nutzen. Der erste Schritt war, auf der Java-Seite zu erfassen, welche Dateien bei jedem Testlauf geladen werden.

Dabei ging es zunächst nicht um die Code-Ebene im Detail, sondern um geladene Artefakte. Welche Java-Klasse wird konkret aus einem JAR aktiviert? Welche C++-Bibliothek wird geladen, um Daten zu erzeugen? Und auch ganz ohne Code: welche XML-, CSV- oder YAML-Datei wird geöffnet, etwa um ein Testorakel zu definieren oder Einstellungen vorzunehmen. Auch solche Dateien beeinflussen eine Testausführung.

Dafür existieren zwei Pipelines. Die erste baut und testet laufend. Die zweite läuft regelmäßig, meist nachts, führt alle Testfälle aus und protokolliert, welche Dateien dabei geöffnet werden. Aus diesen gesammelten Daten lässt sich beim nächsten Check-in sofort ableiten, welche Testfälle eine geänderte Datei berühren.

Ein Sonderfall sind die C++-Bibliotheken, denn eingecheckt werden nicht die DLLs, sondern die Quell- und Header-Dateien. Hier hilft der C++-Compile selbst: Beim Kompilieren fällt die Information an, welche Dateien in welche DLL eingehen. Diese Verlinkung wird zusätzlich hergestellt, sodass sich aus jeder Änderung der betroffene Testfall ableiten lässt.

Die nächtlich gesammelten Daten bilden eine Art Index. Bei der eigentlichen Selektion wird nur noch auf diesen Index geschaut, nicht erneut analysiert. So bleibt die Auswahl bei der Testausführung schnell.

Die Ergebnisse: von Stunden auf Minuten

Die Selektion spart 50 bis 60 Prozent aller Testfälle ein. Untersucht wurden eine Entwicklungsbranche und eine bereits freigegebene Branche mit reinen Fehlerbehebungen.

Auf der Java-Seite sank die Laufzeit deutlich. Vorher lag sie bei zweieinhalb Stunden, im Mittel sind es jetzt eine Stunde. In vielen Fällen bekommen Entwickler nach 10 bis 15 Minuten Rückmeldung, ob ihr Commit in Ordnung ist.

Noch größer war der Sprung auf der C++-Seite nach einer zweiten Ausbaustufe. Statt nur auf DLL-Ebene zu selektieren, wird nun innerhalb der DLLs auf Funktionsebene geprüft, welche C++-Funktion tatsächlich aufgerufen wird. Hat sich eine Funktion geändert, laufen nur die Testfälle, die durch diese Funktion gehen. Die C++-Seite fiel damit von zuvor rund vier bis viereinhalb Stunden auf in der Regel 10 bis 15 Minuten.

Die langen Vollläufe verschwinden nicht ganz, sie werden seltener. Bei großen Änderungen am Kern laufen weiterhin alle nötigen Testfälle, und dann sind die zweieinhalb Stunden wieder akzeptabel. Gerade in freigegebenen Branches, deren Fehlerbehebungen kurz danach an Kunden gehen, will man die Sicherheit eines vollen Durchlaufs.

VorherNachher (im Mittel)
Java-Seite2,5 Stunden1 Stunde, oft 10-15 Minuten
C++-Seite (Funktionsebene)4-4,5 Stunden10-15 Minuten
Eingesparte Testfälle50-60 Prozent

Vertrauen entsteht durch Validierung, nicht durch Versprechen

Damit ein Selektionsverfahren akzeptiert wird, muss es beweisen, dass es keine echten Fehler übersieht. Über vier bis sechs Wochen lief parallel die komplette Suite, während protokolliert wurde, was die Selektion ausgewählt hätte.

Das Ergebnis: kein einziger übersehener Fehlschlag. Die wenigen Fehler, die nur die Vollausführung fand, lagen ausschließlich an der Infrastruktur, etwa einer kurzzeitig nicht erreichbaren Datenbank wegen Netzwerkproblemen. Solche Aussetzer zählen nicht als übersehener Testfehler.

Diese Validierung ist die Grundlage des Vertrauens in die Selektion. Ohne den belastbaren Abgleich gegen den Vollauf bliebe immer der Zweifel, ob das System die richtigen Testfälle wegfiltert.

Schnelleres Feedback verändert das Verhalten der Entwickler

Kleine Commits zahlen sich aus, wenn die Selektion greift. Wer wenig auf einmal eincheckt, ändert weniger Code und löst dadurch eine kleinere Testmenge aus. Das Feedback kommt schneller zurück. Wer dagegen über Tage sammelt und alles auf einmal einkippt, startet wieder eine große Suite.

Die spürbarste Reaktion war das Verschwinden der Beschwerden. Vorher gab es viel Kritik an der langsamen, nicht funktionierenden Pipeline. Diese Klagen sind weg, auch wenn überschwängliches Lob selten direkt ankommt.

Bemerkenswerter ist, wie die Stimmung im Projekt kippte. Vor dem Start zweifelten einige, ob sich der Aufwand lohnt, weil frühere Versuche, den Quelltext genauer zu analysieren, an Größe und Komplexität gescheitert waren.

Als wir den Java-Teil fertig hatten, gab es auf einmal sehr viele anerkennende Stimmen. Das hätte ich nicht gedacht, dass wir so viel Nutzen aus diesem Projekt ziehen. Silke Reimer

Was als Nächstes optimiert werden kann

Der größte offene Hebel ist die Priorisierung der verbleibenden langen Läufe. Heute führt das System alle selektierten Testfälle aus, ohne Reihenfolge. Bei langlaufenden Teilen ließe sich steuern, welche Testfälle zuerst laufen, sodass wahrscheinliche Fehlschläge früh sichtbar werden.

Für die Priorisierung gibt es zwei Wege. Über Testabdeckung kann man jeden zusätzlichen Testfall so wählen, dass er möglichst viel neue Abdeckung bringt. Alternativ nutzt man historische Daten und führt dort zuerst aus, wo besonders häufig etwas fehlschlug.

Technisch muss dafür auch die Jenkins-Pipeline mitspielen. Der Lauf soll nicht beim ersten Fehler abbrechen, weil weitere Fehlschläge interessant bleiben. Gleichzeitig soll der Entwickler früh eine Warnung bekommen: vorläufige Meldung sofort, vollständiger Bericht später.

Auf der Java-Seite steht zudem der gleiche Verfeinerungsschritt an wie bei C++. Aktuell selektiert das System dort auf Ebene der Java-Klassen. Der nächste Schritt wäre, bis auf die einzelne Methode zu gehen und nur die Testfälle auszuführen, die genau die geänderte Methode treffen.

Der Maßstab: Millionen Codezeilen, nächtlicher Index

Das Verfahren bewährt sich an einer großen Codebasis. Der C++-Teil umfasst knapp 10 Millionen Codezeilen, der Java-Teil rund 4 Millionen. Die Skepsis, eine solche Menge ließe sich gar nicht sinnvoll analysieren, war einer der Gründe für die anfänglichen Zweifel am Projekt.

Der nächtliche Index-Lauf, der die Verlinkung zwischen Dateien und Testfällen neu aufbaut, dauert etwa fünf bis sechs Stunden. Die IVU hat die Zahl der Pipeline-Maschinen inzwischen von zehn auf fünf reduziert, weil nachts wenig eingecheckt wird und diese freie Kapazität für die Index-Läufe genutzt werden kann.

Bei zehn Branches und fünf Maschinen schafft das System ungefähr einen Lauf pro Nacht und Branch. Im Schnitt wird der Index also etwa alle zwei Tage neu aufgebaut, schneller, sobald eine Maschine früher frei wird. Der Java-Teil läuft seit etwa anderthalb bis zwei Jahren produktiv, die genauere C++-Analyse auf Funktionsebene seit drei bis vier Monaten.

Diese Seite teilen

Ähnliche Beiträge