Ein Scrum-Ansatz ohne Testplanung führt zu einem „Proof of Concept“ (POC) auf Steroiden.
Heutzutage starten viele erfolgreiche Projekte mit einem Proof of Concept (POC). Das ist eine kleine Überprüfung einer Idee, bei der die gewählte Technologie und die wichtigsten Funktionen getestet werden. Man analysiert die möglichen Auswirkungen auf die Nutzer und, wenn sich der Ansatz bewährt, wird ein komplettes Projektteam beauftragt. Dieses Team entwirft und liefert das vollständige Produkt und bringt es in die Produktion.
Dies ist der ideale Einsatz für ein Scrum-Team. Das Team kann schnell einen Prototyp entwickeln und in jedem Sprint wichtige neue Funktionen hinzufügen. Die Nutzer können den schnellen Fortschritt live mitverfolgen und sehen, wie die Idee in wenigen Sprints von Grund auf entsteht. Dies hinterlässt einen starken Eindruck, was ja das Hauptziel des POC ist. Allerdings hat dieser Ansatz eine wichtige Eigenheit: minimale oder gar keine Testaktivitäten. Der Gedanke an einen Testprozess wirkt hier eher fehl am Platz.
Für ein Scrum-Team ist Testen oft keine beliebte Tätigkeit. Viele bevorzugen es, ohne einschränkende Prozesse zu arbeiten. Jede Testaktivität kann als eine solche Einschränkung empfunden werden, da sie den Prozess verlangsamen kann. Niemand will unnötige Verzögerungen, wenn es darum geht, die Nutzer zu beeindrucken.
Wenn das Projektteam nach der POC-Phase weiterhin auf diese Weise arbeitet, entsteht ein Zustand, den ich „POC auf Steroiden“ nenne. Hierbei wächst ein Produktionssystem in Größe und Funktionalität, verhält sich aber immer noch wie ein POC – ein unfertiges Produkt mit versteckten Fehlern und ungeprüften Sonderfällen. Ein schwerer Fehler ist hier nur eine Frage der Zeit.
Noch bevor es soweit kommt, wird es für das Team zunehmend schwieriger, stabile Releases zu liefern, da die Behebung von Problemen immer komplizierter wird.
Hier sind einige bewährte Techniken, die mir bei ähnlichen Problemen geholfen haben. Sie können als Best Practices für die Implementierung von soliden Testprozessen gelten, ohne den Entwicklungsfortschritt zu stark zu beeinträchtigen – ein wichtiges Ziel für jedes Scrum-Team.
Die Last verteilen
Wo sollte man anfangen, wenn es darum geht, unnötige Belastungen zu vermeiden? Richtig, man teilt sie auf :).
Ich meine damit, einen Plan zu erstellen, der von den Entwicklern hier und da kleine zusätzliche Aufgaben erfordert. Diese Aufgaben tragen aber im Laufe der Zeit inkrementell und kontinuierlich zum gemeinsamen Ziel bei.
#1. Unit-Tests für jeden neuen Code schreiben
Wenn es gelingt, die Scrum-Teams davon zu überzeugen, die Entwicklung von Unit-Tests für jeden neuen Code in die Definition-of-Done aufzunehmen, ist das langfristig gesehen ein großer Erfolg.
Die Gründe sind offensichtlich:
Es zwingt Entwickler, über unterschiedliche, nicht-standardmäßige Wege des Codes nachzudenken.
- Solche Unit-Tests können in automatisierte DevOps-Pipelines integriert und bei jeder Bereitstellung in der Entwicklungs- oder Testumgebung ausgeführt werden. Die Metriken aus der Pipeline können einfach exportiert und verwendet werden, um die prozentuale Abdeckung der Testfälle für die Nutzer zu demonstrieren.
Wichtig ist, frühzeitig damit zu beginnen. Je später man mit der Entwicklung von Unit-Tests beginnt, desto schwieriger wird es für die Entwickler, diese in einem Sprint noch hinzuzufügen.
- Es erfordert erhebliche Anstrengungen, um Komponententests für bereits existierenden Code nachträglich zu entwickeln. Einige Code-Teile könnten von anderen Entwicklern geschrieben worden sein, und der aktuelle Entwickler hat vielleicht nicht mehr das genaue Wissen, wie jeder Code-Teil im Detail funktioniert. In manchen Fällen kann das Hinzufügen eines Unit-Tests für geänderten Code mehr Zeit in Anspruch nehmen als die eigentliche Feature-Änderung (ein Zustand, den niemand bei der Sprintplanung vorhersehen kann).
#2. Die Ausführung aller Unit-Tests in der Entwicklungsumgebung zur Routine machen
Noch bevor ein Pull-Request zum Zusammenführen von neuem Code in den Master-Branch erstellt wird, sollte es Standard sein, den Feature-Code sowie den Unit-Test-Code in der Entwicklungsumgebung zu testen. Dies stellt sicher, dass:
- Der Unit-Test-Code auch wirklich funktioniert (schließlich ist er auch nur Code, der verifiziert werden muss). Dieser Schritt wird oft übersprungen. Man geht davon aus, dass, wenn der Unit-Test sowieso in die DevOps-Pipeline geht, er dort irgendwann ausgeführt und getestet wird. Dies bedeutet aber nichts anderes, als die Probleme in spätere Phasen des Sprints zu verschieben, in denen das Team meist weniger Zeit und mehr Stress hat.
- Der neue Funktionscode vom Entwickler auf grundlegende Funktionen getestet wird. Dieser Test kann natürlich nicht die vollständige Geschäftsfunktionalität prüfen, aber er bestätigt zumindest, dass der Code sich so verhält, wie der Entwickler es erwartet (dabei wird vorerst ignoriert, dass die Geschäftslogik während der Entwicklung möglicherweise falsch interpretiert wurde).
#3. Die Ausführung von Unit-Tests nach dem Zusammenführen mit dem Master-Branch erwarten
Es ist eine Sache, funktionierenden Code auf dem lokalen Branch zu haben (wo außer dem Branch-Eigentümer niemand an neuen Funktionen arbeitet). Es ist aber etwas völlig anderes, denselben Code nach dem Pull-Request und dem Einspielen in den Master-Branch zu haben.
Der Master-Branch enthält Änderungen von anderen Scrum-Teammitgliedern. Selbst wenn die Konflikte gelöst sind und alles gut aussieht, ist der Code nach dem Zusammenführen und der Konfliktlösung immer noch im Grunde ungetestet. Es ist riskant, ihn ohne vorherige Überprüfung weiterzuleiten.
Was sich hier als effektiv erwiesen hat, ist, einfach zu fragen, ob die gleichen Unit-Tests, die bereits zuvor in der Entwicklungsumgebung durchgeführt wurden, auch in der Umgebung ausgeführt werden, die aus der Master-Branch-Version des Codes erstellt wurde.
Für die Entwickler mag dies ein zusätzlicher Schritt sein, aber es ist kein großer Aufwand. Es wird nichts Neues erfunden, sondern nur das wiederholt, was bereits getan wurde.
Dieser Schritt kann in manchen Fällen auch übersprungen werden:
- Die automatisierten Unit-Tests in den DevOps-Pipelines sind so umfassend, dass sie das gesamte darüber hinausgehende manuelle Testen bereits abdecken.
Auch wenn dieser Zustand erreichbar ist, habe ich ihn im realen Leben noch nicht erlebt. Es wäre wahrscheinlich auch für die Entwickler zu zeitaufwendig, solche detaillierten automatisierten Unit-Tests zu erstellen. Der Product Owner würde es möglicherweise nicht akzeptieren, dass das Team so viel Zeit für diese Aktivität aufwendet, da dies die Anzahl der Stories, die innerhalb eines Sprints geliefert werden können, deutlich reduzieren würde.
Die Priorisierung von Sprint-Inhalten darf jedoch niemals als Entschuldigung dienen, um Unit-Tests nicht durchzuführen oder zu minimieren. Das würde das Team in einen Zustand bringen, in dem die Codeabdeckung durch Unit-Tests zu gering ist und es für eine Aufholaktion zu spät sein kann. Der Aufwand für die Unit-Test-Ergänzung ist dann höher als die eigentliche Codeänderung für einen Sprint.
Aus diesen Gründen würde ich eine erneute Ausführung der Unit-Tests auf der Master-Code-Version uneingeschränkt empfehlen. Es ist ein geringer Aufwand im Vergleich zu dem Wert, den er bringt.
Es ist die endgültige Überprüfung der Master-Branch-Bereitschaft für die Release-Testphase. Außerdem hilft es, die meisten technischen Fehler zu finden (z. B. Fehler, die die erfolgreiche Ausführung des Quellcodes verhindern), sodass sich die nächste Phase ausschließlich auf die Überprüfung der Geschäftsfunktionalität konzentrieren kann.
Vorbereitung auf Funktionstests
Alle bisherigen Testaktivitäten sollen zu einem konkreten Ergebnis führen: Der Master-Branch-Code ist frei von technischen Fehlern und kann reibungslos für durchgängige Funktionsabläufe verwendet werden.
Diese Phase kann gut von einer einzelnen Person durchgeführt werden. Diese Person muss nicht einmal einen technischen Hintergrund haben.
Es ist sogar besser, wenn es von jemandem gemacht wird, der nicht direkt mit den Entwicklern zu tun hat, aber ein gutes Verständnis dafür hat, wie sich Geschäftsbenutzer von dem Verhalten des Codes erwarten. Es gibt zwei Hauptaufgaben:
#1. Gezielter Funktionstest für neue Sprint Stories
Jeder Sprint soll idealerweise eine neue Funktion (Sprint Goal Increment) hervorbringen, die vorher nicht vorhanden war, und diese muss überprüft werden. Es ist wichtig sicherzustellen, dass die neue Software so funktioniert, dass die Benutzer zufrieden sind, da sie sich wahrscheinlich mindestens den ganzen letzten Sprint darauf gefreut haben :).
Es ist eine enttäuschende Erfahrung, wenn die versprochene Funktion nach der Ankündigung des Releases nicht ordnungsgemäß funktioniert.
Daher ist es wichtig, neue Sprint-Funktionen richtig zu testen. Für einen erfolgreichen Test sollten wichtige Testfälle im Voraus von relevanten Beteiligten (entweder vom Product Owner oder sogar von den Endbenutzern) gesammelt und eine Liste dieser Testfälle für den jeweiligen Sprint-Inhalt erstellt werden.
Das mag auf den ersten Blick überwältigend erscheinen, ist aber meiner Erfahrung nach auch für eine einzelne Person machbar. Da die Sprints meist recht kurz sind (z.B. zwei Wochen), gibt es meist nicht allzu viele neue Inhalte, da der Sprint auch zusätzliche Aktivitäten wie technische Schulden, Dokumentation, Design/Spike Stories usw. umfasst.
Anschließend werden Testfälle mit dem Ziel ausgeführt, die gewünschte Funktionalität zu überprüfen. Wenn ein Problem auftritt, wird nur der zuständige Entwickler kontaktiert (derjenige, der die Änderungen im Zusammenhang mit diesem Fehler durchgeführt hat), um das Problem schnell zu beheben.
Auf diese Weise verbringen die Entwickler nur minimale Zeit mit Funktionstests und können sich auf die Aktivitäten konzentrieren, die sie am besten beherrschen.
#2. Ausführung von Regressionstestfällen
Der zweite Teil des funktionalen Tests besteht darin, sicherzustellen, dass alles, was bisher funktioniert hat, auch nach dem nächsten Release noch funktioniert. Hierfür sind Regressionstests da.
Regressionstestfälle sollten regelmäßig gepflegt und vor jedem Release überprüft werden. Basierend auf den spezifischen Projektanforderungen, sollten die Testfälle einfach gehalten werden, aber die wichtigsten Kernfunktionalitäten und wichtige End-to-End-Flows durch das gesamte System abdecken.
Normalerweise gibt es in jedem System solche Prozesse, die verschiedene Bereiche berühren. Das sind die besten Kandidaten für Regressionstestfälle.
In einigen Fällen decken Regressionstests auch implizit neue Funktionen des Sprints ab, z. B. wenn die neue Story im Sprint einen bestimmten Teil des bestehenden Ablaufs verändert.
Daher ist es in den meisten Fällen nicht sehr aufwendig, Regressionstests neben neuen Funktionstests durchzuführen, insbesondere wenn dies regelmäßig vor jedem Produktions-Release und die Periodizität der Produktions-Releases nicht zu kurz ist.
Vor jedem Produktionsrelease auf Qualitätssicherungstests bestehen
Der Qualitätssicherungstest (QA) ist der letzte Schritt vor der Produktionsfreigabe und wird oft als unwichtig abgetan. Vor allem, wenn das Scrum-Team unter Druck steht, schnell neue Inhalte zu liefern.
Auch die Benutzer sagen vielleicht, dass sie an neuen Features interessiert sind und nicht so sehr daran, die Funktionalität zu erhalten oder die Anzahl der Fehler gering zu halten. Aber langfristig gesehen ist dies der Hauptgrund, warum die Entwickler irgendwann langsamer werden und die Nutzer letztendlich nicht das bekommen, was sie wollen.
Der QA-Test sollte ausschließlich von Personen außerhalb des Scrum-Teams durchgeführt werden, idealerweise von den Nutzern selbst in einer dedizierten Umgebung, die so nah wie möglich an der zukünftigen Produktion ist. Alternativ kann hier der Product Owner die Endbenutzer vertreten.
Auf jeden Fall sollte es aus der Sicht des Endbenutzers ein sauberer Funktionstest sein, ohne Verbindung zum Entwickler-Scrum-Team. Es ist eine endgültige Sicht auf das Produkt und wird möglicherweise auf eine Weise genutzt, die niemand aus dem Scrum-Team erwartet hat. Es ist immer noch Zeit für Last-Minute-Korrekturen.
Es kann auch klar werden, dass die Erwartungen vom Scrum-Team nicht richtig verstanden wurden. In einem solchen Fall können wir uns zumindest noch lange vor der eigentlichen Produktionsfreigabe auf einen Folgeplan einigen. Das ist zwar nicht ideal, aber immer noch besser, als den Fehler direkt nach dem Produktionsrelease einzugestehen.
Wie geht es von hier aus weiter?
Die Anwendung dieser Praktiken in der täglichen Scrum-Arbeit kann zu stabileren und planbareren Sprint-Releases führen, ohne Produktions-Releases zu verzögern oder den ganzen Sprint nur mit der Vorbereitung des nächsten Releases zu verbringen. Auch zwingt man niemanden im Scrum-Team, etwas zu tun, das er nicht mag oder nicht effektiv umsetzen kann.
Man muss aber hier nicht aufhören.
Die Einbeziehung von Leistungstests ist ein wichtiges Thema, das hier erwähnt werden sollte. Diese werden oft ignoriert oder als unnötig erachtet. Ich hatte aber immer Bedenken, wie sich das Produktionssystem im Laufe der Zeit entwickelt, wenn es nicht regelmäßig auf seine Leistung überprüft wird.
Eine weitere Stufe wäre die Einführung von automatisierten Tests.
Ich habe bereits eine Gruppe automatisierter Tests (Unit-Tests in der Pipeline) behandelt. Man kann aber auch vollständige End-to-End-Regressionstests entwickeln, die vollständig automatisiert nach jeder Bereitstellung in der Testumgebung von selbst ausgeführt werden. Das würde das Entwicklungs-Scrum-Team noch mehr entlasten. Es erfordert jedoch ein dediziertes Team, um solche automatisierten Tests zu entwickeln und zu warten. Es wäre eine kontinuierliche Aufgabe, da jede Änderung des Produktionscodes einige bestehende automatisierte Tests ungültig machen kann. Sie müssen dann aktualisiert werden, damit sie in den Pipelines weiter funktionieren. Es ist ein Aufwand, den nicht viele bereit sind zu tragen, aber der Nutzen für das Dev-Scrum-Team wäre groß.
Das sind Themen, die den Rahmen dieses Artikels sprengen, ebenso wie die Frage nach dem richtigen Zeitplan und Timing für jeden hier erwähnten Testtyp, damit alles in einem Sprint erledigt werden kann. Darauf werde ich gerne in Zukunft noch eingehen!