Automatisiertes Testen für CI/CD

Continuous Integration und Continuous Deployment (CI/CD) sollen Entwicklungsteams in die Lage versetzen, funktionsfähige Software in kurzen Abständen auszuliefern. Das Ziel ist dabei, einerseits den Mehrwert für die Kunden zu steigern und andererseits hilfreiches Feedback darüber zu geben, wie die Software in der realen Welt eingesetzt wird. Viele Unternehmen haben sich DevOps-Verfahren zugewandt, um mit der Konkurrenz Schritt zu halten.

Der wirtschaftliche Druck, schneller zu liefern, sollte jedoch nicht auf Kosten die Produktqualität gehen. Schließlich erwarten Ihre Benutzer*innen eine stabile und funktionierende Software – selbst wenn sie lautstark nach dem nächsten coolen Feature verlangen. Ein zuverlässiger und gründlicher automatisierter Testprozess, der Vertrauen in neue Builds entstehen lässt, ist daher in der Praxis von entscheidender Bedeutung für Continuous Integration und Delivery.

Warum sollten CI/CD-Tests automatisiert werden?

Tests sind unerlässlich für die Sicherung der Softwarequalität, und daher sind sie seit langem ein Bestandteil der Softwareentwicklungspraktiken.

In der Wasserfallmethode fand die manuelle Test- oder Qualitätssicherungsphase erst statt, nachdem Entwicklung und Integration des Codes abgeschlossen waren. Der Zweck bestand darin, zu überprüfen, ob sich die Anwendung spezifikationsgemäß verhält.

Durch diesen linearen Ansatz wird der Releaseprozess verlangsamt, und Ihre Entwickler*innen können erst mit großem Abstand überprüfen, ob ihre Arbeit wie beabsichtigt funktioniert. Bis dahin wurde diese Arbeit jedoch bereits als Fundament für die weitere Entwicklung verwendet. Im Gegensatz dazu ermöglicht ein CI/CD-Automatisierungsprozess einen agilen Ansatz mit kurzen Iterationszyklen, die schnelles Feedback bieten und die häufige Veröffentlichung von kleinen Updates erlauben. Ein wesentlicher Teil dieser kurzen, iterativen Zyklen besteht darin, mittels automatisierter Tests sicherzustellen, dass der neue Code funktioniert und nichts anderes beschädigt hat.

Bei Continuous Integration werden Codeänderungen regelmäßig per Commit in den Master- oder Trunk-Branch übernommen. Dabei wird gegebenenfalls ein Build ausgelöst und die Softwarequalität jedes Mal sichergestellt. Um die Vorteile von CI wirklich nutzen zu können, sollten Ihre Teammitglieder versuchen, mindestens einmal pro Tag einen Commit mit ihren Änderungen durchzuführen. Aber selbst bei einem kleinen Team würde ein solcher Umfang an manuellen Automatisierungstests eine erhebliche Belastung des QS-Teams mit sehr monotonen Tätigkeiten bedeuten. Hier kommt die Testautomatisierung ins Spiel.

Automatisierte Tests eignen sich ideal für wiederholungsintensive Aufgaben und führen zu zuverlässigeren Ergebnissen als der manuelle Prozess, da Menschen unweigerlich dazu neigen, Details zu übersehen oder Überprüfungen uneinheitlich durchzuführen, wenn sie dieselben Schritte immer und immer wieder identisch ausführen sollen.

Automatisierte Tests sind nicht nur schneller als das manuelle Ausführen vergleichbarer Tests, sondern können auch parallel ausgeführt werden, sodass Sie Ihre Qualitätssicherung bei Zeitdruck skalieren können (sofern Ihre Infrastruktur es zulässt). Der Vorab-Aufwand, der zum Schreiben automatisierter Tests erforderlich ist, macht sich bald bezahlt, wenn Ihre Teammitglieder regelmäßigere Commits vornehmen und die Produktionsreleases in einem viel engeren Abstand freigegeben werden können.

Automatisierte Tests entlasten Ihr QS-Team zwar von vielen langweiligen, sich wiederholenden Aufgaben, aber sie machen sie nicht überflüssig. Neben dem Definieren und Priorisieren von entsprechenden Fällen arbeitet das QA-Team auch an der Erstellung automatisierter Tests mit, häufig in Zusammenarbeit mit dem Entwicklerteam. Wie wir später sehen werden, gibt es außerdem Tests, die sich nicht automatisieren lassen – auch für diese braucht man Personal.

Wie fügen sich Tests in den CI/CD-Prozess ein?

Tests finden in mehreren Phasen der Pipeline statt.

Wenn Sie mit Continuous Integration und Deployment noch nicht vertraut sind, hört sich das vielleicht übertrieben an. Aber bei CI/CD und QS-Automatisierung geht es um kurze Feedback-Schleifen, die Ihrem Team helfen, Probleme so früh wie möglich zu erkennen.

Es ist viel einfacher, Probleme kurz nach ihrem Entstehen zu beheben, denn dadurch vermeiden Sie, dass noch mehr Code geschrieben wird, der auf einer fehlerhaften Grundlage aufbaut. Auch für die Teammitglieder selbst ist es effizienter, Korrekturen zeitnah vorzunehmen, bevor sie zum nächsten Schritt übergehen und den Kontext verlieren.

Viele Build-Test-Automatisierungstools unterstützen die Integration mit CI/CD, sodass Sie die Testdaten in die Pipeline einspeisen und die Tests schrittweise ausführen können. Die Ergebnisse werden nach jedem Schritt bereitgestellt. Mit einem entsprechenden CI-Tool können Sie basierend auf den Testergebnissen einer Phase entscheiden, ob der Build in die nächste Phase vorrücken soll.

Um die Pipeline durch automatisierte Tests optimal zu nutzen, ist es im Allgemeinen sinnvoll, die Build-Tests so anzuordnen, dass die schnellsten Tests zuerst ausgeführt werden. Auf diese Weise erhalten Sie früher Feedback und nutzen Ihre Testumgebungen effizienter, da sichergestellt wird, dass die ersten Tests bestanden wurden, bevor längere, aufwendigere Tests gestartet werden.

Um bei der Erstellung und Ausführung automatisierter Tests eine sinnvolle Prioritätenfolge zu finden, ist es hilfreich, das Konzept der Testpyramide zur Hilfe zu ziehen.

Aufbau einer Testpyramide

Die Testpyramide ist eine praktische Methode, um die Priorisierung von Continuous-Integration-Tests in einer CI/CD-Pipeline zu konzeptualisieren, und zwar sowohl in Bezug auf die relative Anzahl als auch auf die Reihenfolge, in der sie durchgeführt werden.

In der ursprünglich von Mike Cohn definierten Testpyramide befinden sich unten die Unit-Tests, darüber die Servicetests und oben die UI-Tests.

Auch wenn die Benennung möglicherweise nicht ganz zutreffend ist, ist die Prämisse sinnvoll: Wir starten mit einer soliden Basis an automatisierten Unit-Tests, die schnell und einfach auszuführen sind, und fahren dann mit Tests fort, die sowohl komplexer zu erstellen sind als auch länger dauern. Abgeschlossen wird mit einer kleinen Anzahl hochkomplexer Prüfungen. Welche Arten von CI/CD-Tests sollten Sie in Betracht ziehen? Sehen wir uns die Optionen an.

Unit-Tests

Unit-Tests bilden zu Recht die Basis der Testpyramide. Sie sollen sicherstellen, dass der Code wie erwartet funktioniert, indem die kleinstmögliche Verhaltenseinheit getestet wird. In Teams, die sich entschieden haben, in das Schreiben von Unit-Tests zu investieren, werden diese in der Regel vom Entwicklerteam direkt beim Schreiben des Codes erstellt. Bei der testgetriebenen Entwicklung (test-driven development, TDD) erfolgt dies automatisch, aber TDD ist keine Voraussetzung für die Verwendung von Unit-Tests.

Wenn Sie an einem vorhandenen System arbeiten, für das noch keine Unit-Tests vorliegen, ist das Abdecken des gesamten Codebestands „aus dem Stand“ oft ein illusorisches Ziel. Obwohl eine weitflächige Abdeckung mit Unit-Tests empfehlenswert ist, können Sie mit so viel beginnen, wie gerade möglich ist, und im Laufe der Zeit darauf aufbauen. Eine realistische Strategie wäre, für jeden Codeabschnitt, den Sie anfassen, Unit-Tests zu schreiben, um sicherzustellen, dass der neue Code vollständig abgedeckt ist. Die Prioritätenfolge für den Bestandscode kann dann basierend auf Ihrer Interaktion mit diesen Codebereichen während der Entwicklung erfolgen.

Integrationstests

Mit Integrationstests stellen Sie sicher, dass die Interaktion zwischen den verschiedenen Teilen Ihrer Software – z. B. Anwendungscode und Datenbank – wie erwartet funktioniert. Es kann hilfreich sein, Integrationstests in breit und eng gefasste Tests zu unterteilen. Bei engen Integrationstests wird die Interaktion mit einem anderen Modul mithilfe eines Testgerüsts anstelle des eigentlichen Moduls getestet, während bei breiten Integrationstests die Komponente oder der Service höchstpersönlich antreten muss.

Abhängig von der Komplexität Ihres Projekts und der Anzahl der internen und externen Services kann es sinnvoll sein, eine Schicht enger Integrationstests zu schreiben, die schneller ausgeführt werden als breite Integrationstests (da sie nicht die Verfügbarkeit anderer Systemteile voraussetzen). Darauf können eine Reihe breiter Integrationstests folgen, eventuell mit Schwerpunkt auf priorisierten Bereichen Ihres Systems.

End-to-End-Tests

End-to-End-Tests, auch als Full-Stack-Tests bezeichnet, betreffen die gesamte Anwendung. Diese Tests können zwar über die GUI ausgeführt werden, dies ist aber nicht zwingend erforderlich. Auch mit einem API-Aufruf können mehrere Teile des Systems getestet werden (andererseits können APIs auch Gegenstand von Integrationstests sein). Die Testpyramide empfiehlt eine geringere Anzahl dieser Tests, nicht nur aufgrund der längeren Dauer, sondern auch, weil sie oft instabil sind.

Jede Änderung an der Benutzeroberfläche kann diese Tests aus der Bahn werfen, was einerseits störendes Rauschen in den Build-Testergebnissen verursacht und andererseits Zeitaufwand für die Aktualisierung des Tests erfordert. Es lohnt sich, End-to-End-Tests sorgfältig zu gestalten und dabei den Abdeckungsumfang der auf einer niedrigeren Ebene angesiedelten Build-Tests zu berücksichtigen, um den Mehrwert zu maximieren.

Performancetests

Auch wenn in der Testpyramide Performancetests nicht explizit erwähnt werden, sollten sie in Ihre automatisierte Testsuite Eingang finden, insbesondere wenn Stabilität und Geschwindigkeit wichtige Produktkriterien sind.

Der Überbegriff „Performancetest“ umfasst eine Reihe von Teststrategien, mit denen überprüft werden kann, wie sich Ihre Software in einer Live-Umgebung verhält. Lasttests prüfen, wie sich das System bei steigender Belastung verhält. Bei Stresstests wird die erwartete Belastung absichtlich überschritten und bei Dauerleistungstests („Soak-Tests“) die Leistung bei anhaltend hoher Last gemessen.

Diese Testarten sollen nicht nur bestätigen, dass die Software mit den definierten Parametern zurechtkommt, sondern auch überprüfen, wie sie sich bei Überschreitung dieser Parameter verhält. Idealerweise sollte es dabei zu einem geordneten Herunterfahren statt einem unkontrollierten Crash kommen.

Testumgebungen

Sowohl Performance- als auch End-to-End-Tests erfordern Umgebungen, die der Produktionsumgebung sehr ähnlich sind. Möglicherweise werden auch Build-Testdaten benötigt. Damit ein automatisiertes Testsystem Vertrauen in die getestete Software schafft, ist es wichtig, die Build-Tests jedes Mal auf die gleiche Weise auszuführen. Dazu gehört auch, dass die Testumgebungen zwischen den verschiedenen Läufen gleich bleiben (wobei sie bei Änderungen in der Produktionsumgebung entsprechend angepasst werden sollten).

Das manuelle Verwalten von Umgebungen kann zeitaufwändig sein. Es lohnt sich daher, die Erstellung und Entsorgung von Vorproduktionsumgebungen, die bei jedem neuen Build zum Einsatz kommen, zu automatisieren.

Feedback integrieren

Mit der Durchführung automatisierter Tests im Rahmen eines CI/CD-Workflows verfolgen wir das Ziel, schnelles Feedback zu den gerade vorgenommenen Änderungen zu erhalten. Daher ist es wichtig, auf dieses Feedback entsprechend zu reagieren.

CI-Server ermöglichen in der Regel die Integration von automatisierten Testtools, sodass alle Ergebnisse an einem Ort zusammengefasst werden können. Um sich über die Leistung des neuesten Builds zu informieren, kombinieren Entwicklungsteams häufig eine Dashboard- oder Radiator-Anzeige der neuesten Ergebnisse mit automatisierten Benachrichtigungen an Kommunikationsplattformen wie Slack.

Wenn ein Test fehlschlägt, wird die Ursachenforschung beschleunigt, wenn klar ist, auf welchen Bereich des Codes sich der Test bezieht, und wenn durch den Test ausgegebene Informationen – z. B. Stacktraces, Ausgabewerte oder Screenshots – einsehbar sind. Nehmen Sie sich die Zeit, Überprüfungen sorgfältig zu entwerfen. Jeder Test sollte einen einzigen Sachverhalt prüfen, und alle Tests sollten klar benannt werden, damit sofort erkennbar ist, welcher Test fehlgeschlagen ist. Continuous-Integration-Test- und CI-Tools, die zusätzliche Informationen zu Testfehlern bereitstellen, können ebenfalls dabei helfen, Ihre Builds schneller wieder in den grünen Bereich zu bringen.

Tools und Practices sind jedoch wie immer nur eine Seite der Medaille. Ein wirklich guter CI/CD-Automatisierungs-Workflow erfordert eine Teamkultur, die nicht nur den Wert automatisierter CI/CD-Tests erkennt, sondern auch den Stellenwert einer schnellen Reaktion auf fehlgeschlagene Tests, damit die Software jederzeit zum Deployment bereit ist.

Läuten CI/CD und Testautomatisierung das Ende manueller Tests ein?

Ein häufiges Missverständnis unter CI/CD-Neulingen ist, dass die Testautomatisierung manuelle Tests und professionelle QA-Teams überflüssig macht.

Die CI/CD-Automatisierung spart den QA-Teams zwar etwas Zeit, macht sie jedoch nicht überflüssig. Statt sich in Routineaufgaben zu verfangen, kann sich das Testteam darauf konzentrieren, entsprechende Fälle zu definieren, automatisierte Tests zu schreiben und beim explorativen Testen Kreativität und Einfallsreichtum zu zeigen.

Im Gegensatz zu automatisierten Build-Tests, die zur Ausführung durch einen Computer sorgfältig geskriptet werden müssen, erfordern explorative Tests nur eine lose Aufgabendefinition. Der Wert von explorativen Tests besteht darin, Dinge zu finden, die bei geplanten, strukturierten Tests durch das Netz fallen. Im Wesentlichen sucht man dabei nach Problemen, für die noch kein Testfall geschrieben wurde. Bei der Auswahl der zu explorierenden Bereiche sind sowohl neue Features zu berücksichtigen als auch Systembereiche, die bei einem Problem im Produktionseinsatz den größten Schaden verursachen würden.

Beim explorativen Testen sollte man nicht in manuelle, sich wiederholende Tests hineinrutschen. Das Ziel ist nicht, jedes Mal die gleichen Tests durchzuführen. Werden beim explorativen Testen Probleme entdeckt und behoben, nehmen Sie sich die Zeit, einen automatisierten Test zu schreiben, damit der Fehler bei einem erneuten Auftreten abgefangen wird – und zwar an einem viel früheren Punkt. Um die Zeit des Testteams effizient zu nutzen, sollten manuelle Tests erst durchgeführt werden, nachdem alle automatisierten Tests bestanden wurden.

Kontinuierliche Verbesserungen bei der Testautomatisierung

Automatisierte Tests spielen in jeder CI/CD-Pipeline eine zentrale Rolle.

Das Schreiben automatisierter Tests erfordert zwar Zeit und Mühe, durch schnelles Feedback und Transparenz in Bezug auf die Einsatzbereitschaft des Codes machen sie sich jedoch schnell bezahlt. Allerdings ist der Aufbau einer Testsuite kein Unterfangen, das man abschließt und dann zu den Akten legt.

Ihre automatisierten Build-Tests sollten genauso Teil Ihrer Anwendung sein wie der Rest Ihres Codes. Dementsprechend müssen sie gewartet werden, damit sie relevant und nützlich bleiben. Daher sollten Ihre Bemühungen um eine kontinuierliche Verbesserung Ihres Codes auch Ihre Tests umfassen.

Wenn Sie Ihre automatisierten Tests auf neue Features erweitern und die Ergebnisse explorativer Tests einfließen lassen, bleibt Ihre Testsuite effektiv und effizient. Es lohnt sich auch, die Performance zu analysieren und sich zu fragen, ob es sich lohnen könnte, die Arbeitsschritte umzustellen oder weiter aufzuteilen, um schneller Feedback zu erhalten.

CI-Tools stellen verschiedene Kennzahlen für eine Optimierung der Pipeline bereit. „Flaky“-Indikatoren für unzuverlässige Tests wiederum können uns vor fehlgeleiteten positiven oder negativen Einschätzungen bewahren. Kennzahlen im Auge zu behalten kann zwar hilfreich sein, um den automatisierten Testprozess zu verbessern, aber hüten Sie sich vor dem Trugschluss, dass Testabdeckung an sich das Ziel ist. Das eigentliche Ziel lautet, Ihrer Benutzergemeinde regelmäßig ein funktionierendes Softwareprodukt zur Verfügung zu stellen. Die Automatisierung dient diesem Ziel, indem sie schnelles und zuverlässiges Feedback liefert, damit Sie Ihre Software zuversichtlich für die Produktion freigeben können.