Design für Testbarkeit: Die nächste Barriere durchbrechen
4 Min. Lesezeit

In unserer fortlaufenden Reihe DevOps for the Win dokumentieren wir unsere DevOps-Transformationsreise. In Kapitel 1 haben wir das DevOps-Dreieck: Tools, Architektur und Menschen in der Produktentwicklung vorgestellt, wo wir die grundlegenden Elemente besprochen haben, die den DevOps-Erfolg vorantreiben. In Kapitel 2 haben wir Die ersten Schritte unserer DevOps-Transformation beleuchtet und dabei geteilt, wie wir die Grundlagen für bedeutsame Veränderungen gelegt haben.

In Kapitel 3 widmen wir uns nun der nächsten großen Herausforderung, der wir begegnet sind: Design für Testbarkeit. Nachdem wir unsere Teamstruktur verbessert, unsere Definition of Done verfeinert und eine automatisierte Code-Analyse implementiert hatten, stellten wir fest, dass sich der Hauptengpass in unserer Pipeline auf das Software-Design – insbesondere die Testbarkeit – verlagert hatte. Die Arbeit staute sich in der Testphase an, was uns daran hinderte, schnellere Release-Zyklen zu erreichen. Dieses Kapitel befasst sich damit, wie wir diese Herausforderung gemeistert und unsere Fähigkeit verbessert haben, hochwertige Software effizient bereitzustellen.

Der Test-Engpass

Der Hauptgrund für diese Verzögerung war, dass das Testen überwiegend manuell erfolgte und sich auf UI-Tests und Benutzer-Workflows konzentrierte. Da die Benutzeroberfläche (UI) meist eines der letzten abzuschließenden Elemente war, gab es kaum oder keine Automatisierung, und manuelles Testen wurde zum einzig praktikablen Ansatz. Automatisierte UI-Testfälle (z. B. Selenium-Tests) wurden typischerweise nach Abschluss der manuellen Tests geschrieben, hauptsächlich zu Regressionszwecken.

Diese Abhängigkeit vom manuellen Testen führte zu –

  • Erheblichen Verzögerungen bei Software-Veröffentlichungen.
  • Die Notwendigkeit mehrerer Bereitstellungen, um parallele Tests durch mehr als ein QA-Teammitglied zu ermöglichen.

Dies unterstrich die Bedeutung, den Code besser testbar zu machen, um den Wertfluss zu optimieren. Dies zu erreichen, erwies sich jedoch als schwieriger als erwartet.

API-Tests und Unit-Tests priorisieren

Um diese Herausforderung anzugehen, konzentrierten wir uns auf zwei Hauptbereiche:

  1. Unit-Tests – Sicherstellen, dass einzelne Komponenten isoliert getestet werden können.
  2. API-Tests – Reduzierung der Abhängigkeit von UI-basierten Tests und Datenbankabhängigkeiten.

API-Tests: Frühere Tests für schnelleres Feedback

Wir haben unsere API-Teststrategie mit diesen wichtigen Verbesserungen neu definiert:

  1. Gut definierte APIs – APIs wurden so konzipiert, dass sie einfach, gut dokumentiert und frühzeitig in der Entwicklung verfügbar sind.
  2. Datenbank als Integrationspunkt vermeiden – Die Abhängigkeit von Datenbanken für die Integration führte zu Abhängigkeiten, die die Tests verlangsamten. Stattdessen setzten wir auf eine API-basierte Integration mittels REST über HTTP und GraphQL. Dies minimierte den Einrichtungsaufwand für Datenbanken und verbesserte die Testautomatisierung.

Diese Umstellung reduzierte Verzögerungen erheblich, ermöglichte eine schnellere Automatisierung und Tests in frühen Phasen und zeigte, dass die Konzentration auf API-First-Designs sowohl die Testbarkeit als auch die Effizienz verbesserte.

Unit-Tests: Ein Wandel in der Denkweise

Anfangs führten unsere Unit-Test-Bemühungen zu einem schnellen Anstieg der Testabdeckung, doch wir erkannten schnell Probleme:

  • Einige Tests waren oberflächlich und wurden nur geschrieben, um die Ziele der Codeabdeckung zu erreichen.
  • Es fehlte ihnen an einer aussagekräftigen Validierung von Funktionalität, Grenz- und Fehlerfällen.

Um dem entgegenzuwirken, legten wir Wert auf:

  • Entwickler im Schreiben wertvoller Tests schulen.
  • Den Code refaktorieren, um die Testbarkeit zu verbessern.

Herausforderungen beim Schreiben von testbarem Code

Das größte Hindernis für effektives Unit-Testing war die mangelnde Testbarkeit der Codebasis selbst. Zu den Hauptproblemen gehörten:

  • Große, monolithische Funktionen.
  • Eng gekoppelte Komponenten.
  • Schlechte Trennung von Belangen.
  • Mangelnde Abgrenzung.

Diese Probleme erschwerten es, einzelne Einheiten effektiv zu isolieren und zu testen. Um dies zu beheben, haben wir:

  1. Bereitgestellte Richtlinien: Wir haben bewährte Verfahren zu folgenden Themen geteilt:
    • Auswahl von Funktionen für das Unit-Testing.
    • Den Code refaktorieren, um die Testbarkeit zu verbessern.
    • Entwicklung und Einsatz von Mocks zur Erleichterung des Testens.
  2. Fokus auf schrittweise Verbesserungen: Entwickler wurden ermutigt, kleine, aber wirkungsvolle Änderungen vorzunehmen, um die Testbarkeit im Laufe der Zeit zu verbessern.

Langsamer, aber stetiger Fortschritt

Trotz dieser Bemühungen war der Fortschritt langsam, insbesondere bei älteren Produkten. Die bestehende Architektur begrenzte die Anzahl der Unit-Tests, die wir schreiben konnten. Diese Maßnahmen zielten jedoch nicht nur auf die Verbesserung der aktuellen Codebasis ab – sie waren eine Investition in die Zukunft:

  • Die Verbesserung der Entwicklerfähigkeiten beim Entwerfen von testbarem Code stellte sicher, dass zukünftige Projekte nicht unter den gleichen Problemen leiden würden.
  • Inkrementelle Verbesserungen verhinderten Störungen und erhöhten gleichzeitig stetig die Abdeckung der Testautomatisierung.
  • Ein Wandel in der Denkweise half den Teams, Testbarkeit nicht als Last, sondern als Notwendigkeit für einen nachhaltigen DevOps-Erfolg zu sehen.

Für Teams und Organisationen, die eine DevOps-Transformation beginnen, ist Testbarkeit ein entscheidender Schwerpunkt. Sie hilft, Engpässe in der Entwicklungsphase zu mindern. Es ist jedoch wichtig, die Erwartungen zu steuern – sofortige Ergebnisse sind möglicherweise nicht realisierbar, insbesondere beim Umgang mit großen, älteren Codebasen. Das Refactoring solchen Codes ohne ausreichende Unit- und Regressionstests ist eine große Herausforderung.

Lehren aus „Der Pragmatische Programmierer“

Für Szenarien, in denen der Start mit einer neuen Codebasis nicht praktikabel ist, bietet „The Pragmatic Programmer: Your Journey to Mastery“ von Andy Hunt und David Thomas praktische Anleitungen:

  • Schrittweise Änderungen anstreben: Konzentrieren Sie sich auf das Refactoring kleiner, überschaubarer Teile der Codebasis.
  • Komponenten entkoppeln: Reduzieren Sie Abhängigkeiten, um einzelne Einheiten leichter testbar zu machen.
  • Monolithische Funktionen aufbrechen: Teilen Sie große Funktionen in kleinere, stärker fokussierte Einheiten auf.
  • Modulares Design einführen: Machen Sie den Code durch verbesserte Modularität testbarer und wartbarer.

Die Botschaft ist klar: Sobald die anfänglichen Engpässe behoben sind, sollte die Testbarkeit zu einem zentralen Schwerpunkt werden. Dies wird Engpässe in der Testphase reduzieren und eine schnellere Bereitstellung hochwertiger Software ermöglichen.

Ausblick

Unser Weg zur Verbesserung der Testbarkeit lehrte uns wertvolle Lektionen über die Rolle des Software-Designs bei der Ermöglichung reibungsloser DevOps-Transformationen. Durch die Konzentration auf die Testbarkeit haben wir Engpässe beseitigt, die Fähigkeiten der Entwickler verbessert und den Grundstein für zukünftigen Erfolg gelegt. Auch wenn die unmittelbaren Ergebnisse langsam eintreten mögen, machen die langfristigen Vorteile in Bezug auf Qualität, Effizienz und Teamwachstum diese Investition lohnenswert.

Während wir unsere DevOps-Reise fortsetzen, wird die Erfolgsmessung zur nächsten großen Herausforderung. In Kapitel 4 werden wir untersuchen, wie wir KPIs und Dashboards eingerichtet haben, um unseren Fortschritt zu verfolgen und weitere Verbesserungsbereiche zu identifizieren.

Seien Sie gespannt, wenn wir uns ansehen, wie datengestützte Entscheidungen uns geholfen haben, unsere DevOps-Prozesse zu verfeinern und die Leistung zu optimieren!

Abonnieren Sie den Freyr-Blog

Datenschutzerklärung