Continuous Testing: Mit automatisierten Tests zu Stabilität und Autonomie

Posted on | 1041 words | ~5 mins

Eines unserer wesentlichen Tech-Ziele zur weiteren Optimierung der Software-Erfahrung für unsere AnwenderInnen in 2022 ist das Vorantreiben unseres „Cloud First“-Handlungsfeldes. Dies hat auch seinen Eingang in unsere OKRs gefunden und wurde kürzlich auf unserem OKR-Workshop nochmal validiert. Wir sehen insbesondere Continuous Integration und Continuous Delivery (CI/CD) als wichtige Treiber für viele unserer Teams, um kurze Entwicklungs-Zyklen zu ermöglichen. Ganz nach dem Motto: Dinge, die uns schwerfallen, tun wir öfter.

CI/CD kann ohne eine weitreichende Testautomatisierung nicht funktionieren. Während wir in unseren Legacy-Systemen in der Regel monatliche Releases durchführen und wir uns hierfür aktuell noch eine Menge Zeit für die Durchführung manueller Tests und Bugfixes nehmen müssen, wollen wir in der CI/CD-Welt ein Mindset des Continuous Testings etablieren. Unsere Idee: Über alle Ebenen unserer Testpyramide laufen spätestens bei einem Code-Commit in eines unserer GitHub-Repositories automatisierte Tests ab. Schlagen hierbei Tests fehl oder werden definierte Abdeckungsgrade nicht erreicht, ist ein Merge in den Release-Zweig gar nicht erst möglich.

Um unsere ambitionierten Test-Ziele und die Umsetzung in den einzelnen Teams übergreifend zu orchestrieren, haben wir in den letzten Monaten eine Testing Community of Practice (CoP) ins Leben gerufen. In dieser Testing CoP wirken Entwickler und Test Engineers mit, die sich für automatisierte Tests begeistern und gemeinsam daran arbeiten wollen, die Vision einer weitgehenden Testautomatisierung Realität werden zu lassen.

Unsere Testpyramide sieht hierbei wir folgt aus:

Modul- und Unittests

Auf diesem niedrigsten Level der Testpyramide geht es uns darum, möglichst schnell ein Feedback zur fachlichen und technischen Korrektheit unseres Codes zu bekommen. Wir setzen hier auf JUnit und Test NG im Java-Umfeld sowie auf Jasmine und Jest im Frontend-/Angular-Umfeld. Um auf diesem Test-Level nicht auf Umsysteme angewiesen zu sein, mocken wir sie weg – mittels Mockito, MockK oder WireMock. Die Teams committen sich hierbei auf eines der genannten Tools. Wichtig ist, dass definierte Werte in der Line- und Condition-Coverage erzielt werden, die Tests in der Pipeline bei jedem Commit ablaufen und Builds zwingend fehlschlagen, wenn die definierten Coverage-Zielwerte nicht erreicht werden. Im Sinne eines kontinuierlichen Verbesserungsprozesses setzen wir darauf, dass die Coverage nicht fallen sollte.

API- und Integrationstests

In diese nächste Ebene unserer Testpyramide fallen gleich eine ganze Reihe wichtiger Tests:

  • API-Tests: Verhalten sich die vom Microservice bereitgestellten APIs richtig – im positiven und auch negativen Fall? Wir nutzen bspw. Gauge, Karate und Ready API.
  • API-Linting: Entspricht das Design der REST-APIs unseren Standards? Hierfür nutzen wir Zally.
  • Consumer Driven Contract Tests (CDCT): Breche ich bei einem Update den Kontrakt mit meinen Konsumenten? Wichtig für die Sicherstellung der Team-Autonomie. Wir setzen auf Pact.
  • Micro-Lasttests: Erreichen wir auch unter Last die Antwortzeiten, die unsere Anwender erwarten? Hierfür nutzen wir Gatling und JMeter.
  • Security-Tests: Sind wir gegen Angriffe wie beispielsweise Cross Site Scripting (XSS) optimal geschützt? OWASP ZAP ist das Tool unserer Wahl.

Auch hier gilt, dass definierte Abdeckungsgrade erreicht werden müssen. Im Gegensatz zu unseren Modul- und Unittests laufen die API- und Integrationstests jedoch in der Regel länger und benötigen ein lauffähiges Deployment. Hier verfolgen wir den Ansatz, mittels GitHub Actions nach dem Build automatisiert Testumgebungen zu provisionieren, die jeweilige Anwendung/Microservice zu deployen und die Tests laufen zu lassen. Diese Umgebungen arbeiten ausschließlich mit Mocks, die von den Service-Gebern bereitgestellt werden. Ein Merge in den Release-Branch ist nur möglich, wenn auch diese automatisierten Tests erfolgreich waren.

System- und GUI-Tests

Auch diese Art von Tests erfordert eine automatisiert deployte Testumgebung. Für das Design der Testfälle sind uns klare Akzeptanzkriterien wichtig, die unsere Produktmanager mit unseren Fachbereichen und Nutzern abstimmen. Aus diesen Akzeptanzkriterien erzeugen die Teams dann je User Story mindestens einen GUI-Test. Auch in diesem Umfeld setzen wir diverse Tools ein: Für die Automatisierung der UI-Tests unserer Web-Anwendungen nutzen wir Selenide, Gauge & Taiko sowie Playwright (das „beste“ Tool muss sich durch Praxis-Einsatz noch herauskristallisieren). Für die wenigen Apps, die wir haben, setzen wir auf Appium. Alle Tests führen wir auf BrowserStack durch, um schnell Aussagen über das Verhalten auf unterschiedlichen Betriebssystemen, Browsern und Gerätetypen zu erhalten.

Abnahme- und Akzeptanztests

Ganz ohne manuelle Tests geht es dann doch nicht. Natürlich wollen auch wir vor einem Go-Live die Möglichkeit haben, unsere Entwicklungen noch einmal „anzufassen“ und in der Interaktion mit echten Schnittstellen zu erleben. Den Benchmark für unsere Abnahmetests bilden einerseits die in Figma erstellten UX-Designs und Clickdummies, sowie die in Zephyr Scale definierten fachlichen Testfälle. Wenn nicht bereits ausreichend automatisierte Tests vorliegen, nehmen Product Owner und Tester vor dem Go-Live die einzuführende User Story ab. In Kombination mit den vielfältigen vorangegangenen Tests in unserer Testpyramide können wir so guten Gewissens nach Produktion deployen.

Reflektion

Aufgrund unserer flächendeckend synchronisierten OKRs und der Community of Practice haben wir erstmalig übergreifend über die Frage diskutiert, wie wir in unseren einzelnen Entwicklungsteams Test-Verfahren und -Tools heute einsetzen und künftig einsetzen sollten. Diese offene Diskussion im Konkreten war für alle Teilnehmer hilfreich und hat uns allen viele neue Erkenntnisse gebracht. So haben wir beispielsweise Test-Frameworks und -Tools bereits aus der Aufstellung entfernt, von denen wir überzeugt sind, dass sie mittlerweile durch bessere Tools überholt wurden und daher grundsätzlich nicht mehr verwendet werden sollten.

Es wird jedoch auch deutlich, dass es für einige Einsatzzwecke nicht „das eine“ Tool gibt. Und auch in Bezug auf übergreifende Zielwerte für z.B. Coverage, Testanzahl und das Exception-Handling haben wir im Austausch erkannt, dass wir den unterschiedlichen Ausgangslagen der Teams – zumindest fürs erste – stärker Rechnung tragen müssen als zunächst gedacht.

Nichtsdestotrotz streben wir nach weitgehender Standardisierung und Verschlankung unserer Technologie-Landschaft. Wir sind der Überzeugung, das überschaubare und greifbare Standards über intensivere Wiederverwendung und Knowhow-Transfer eine höhere Effektivität und Effizienz in der Softwareentwicklung ermöglichen. Gleichzeitig möchten wir einen hohen Grad an Identifikation und Ideenreichtum unserer Entwicklungs-Teams mit ihren Themen fördern. Dazu gehört auch ein handhabbares Maß an Heterogenität in den eingesetzten Tools und Verfahren.

Wir können uns vorstellen, dass sich durch die regelmäßige Diskussion in unserer neuen Testing CoP künftig noch weitere Standards herauskristallisieren und im Zuge einer kontinuierlichen Verbesserung mitunter auch aktuell bestehende Ansätze ablösen werden. Letztlich stellen wir auch fest, dass die Lernkurve beim Einsatz verschiedener Frameworks und Tools überschaubar ist, sodass auch ein Wechsel einzelner EntwicklerInnen zwischen unterschiedlichen Teams, auch ggfs. nur temporär, nicht wesentlich erschwert wird.

Aufgrund der bisherigen Erkenntnisse sind wir vom CoP-Ansatz überzeugt und sind gespannt, wie wir über die weiteren Erkenntnisse aus der Community, unsere Software-Qualität fortlaufend verbessern. Wir werden berichten.