Kolumne: Stropek as a Service

Testing mit Microservices – Herausforderungen beim automatisierten Testen von Cloudlösungen
Keine Kommentare

Eine nennenswert komplexe Cloudlösung komplett ohne Testautomatisierung zu entwickeln, ist kaum möglich. Der überwiegende Teil der Tests sollten Unit-Tests sein, also Tests, die eine abgegrenzte Programmfunktion automatisiert testen. Abhängigkeiten werden durch Mock-Objekte ersetzt.

Wenn ich mit anderen Entwicklungsteams über das Thema automatisiertes Testen diskutiere, stoße ich auf ganz unterschiedliche Meinungen und Vorgehensweisen. Die einen schwören auf End-to-End-Tests, die anderen setzen voll und ganz auf Unit-Tests, und wieder andere finden automatisierte Tests überhaupt überbewertet und verlassen sich auf manuelles Testen, weil Automatisierung zu teuer und unzuverlässig ist.

Unit- vs. Integrations- vs. End-to-End-Tests

Meiner Meinung nach liegt der Königsweg wie so oft nicht bei den Extrempositionen. Eine nennenswert komplexe Cloudlösung komplett ohne Testautomatisierung zu entwickeln, ist kaum möglich. Man gibt dadurch die wichtigsten DevOps-Prinzipien auf, weil manuelles Testen zeitraubend ist, daher nur selten ausführlich gemacht werden kann. Die Folge ist, dass häufiges Ausrollen von Softwareverbesserungen unmöglich wird. Sich ganz auf eine Testart wie End-to-End-Tests oder Unit-Tests zu fokussieren, bringt auch nichts. Auf die Mischung kommt es meiner Meinung nach an:

  • Der überwiegende Teil der Tests sollten Unit-Tests sein, also Tests, die eine abgegrenzte Programmfunktion automatisiert testen. Abhängigkeiten werden durch Mock-Objekte ersetzt.
  • Ein geringerer Teil sollten Integrationstests sein. Sie testen das Zusammenspiel mehrerer Programmteile, deren Funktion bereits durch Unit-Tests geprüft wurde. Es werden bewusst weniger Mock-Objekte eingesetzt.
  • Bei End-to-End Tests simuliert ein Softwareroboter den Benutzer und bedient automatisiert die vollständige Anwendung über dessen Benutzerschnittstelle. Diese Tests sind aufwendig zu entwickeln und sollten sparsam eingesetzt werden. Der Fokus liegt auf wenigen, zentralen Prozessen, die Benutzer in der Software abarbeiten und deren korrektes Funktionieren besonders wichtig ist.
  • C# 8.0 Spickzettel

    Kostenlos: C# 8.0 – neue Sprachfeatures auf einen Blick

    Der C#-8.0-Spickzettel fasst die neuen Features der Sprache zusammen mit Blick auf das aktuelle .NET Core 3.0 bzw. .NET Standard 2.1. Jetzt herunterladen und schneller & effektiver programmieren!

Ausnahmen bestätigen natürlich die Regel. Eine Anwendung die praktisch nur aus UI besteht, ist anders zu testen als eine Anwendung, die sich fast vollständig um komplexe Algorithmen dreht.

DevOps verlangt keine vollständige Testabdeckung

Ich bin kein Verfechter einer hundertprozentigen Testabdeckung. Code,

  • der sehr einfach strukturiert ist und dadurch ein geringes Fehlerrisiko hat,
  • bei dem die Folgekosten eines enthaltenen Fehlers gering sind oder
  • der sich sehr selten ändert,

muss nicht unbedingt automatisiert getestet werden. Man darf nicht vergessen, dass jeder automatisierte Test eine Investition ist und Opportunitätskosten hat. Statt den Test zu entwickeln, könnte man der Software aus Anwendersicht wertvolle Funktionen hinzufügen oder andere Tests schreiben. Ich habe schon einige Teams gesehen, bei denen einen sehr hohe Testabdeckung zu einem Fetisch geworden ist und irgendwann wichtiger war als das Umsetzen von Anwenderwünschen. Man darf nicht vergessen, dass das DevOps-Prinzip Continuous Flow of Customer Value aus zwei Teilen besteht: Continuous Flow und Customer Value. Wer so sehr damit beschäftigt ist, durch Automatisierung den Continuous Flow sicherzustellen, dass keine Zeit mehr für die Entwicklung von Customer Value bleibt, wird keinen Erfolg haben.

Herausforderung Microservices

Unit-Tests unterscheiden kaum zwischen klassischen und cloudnativen Anwendungen, die sich aus Microservices zusammensetzen. Anders sieht es bei Integrations- und End-to-End-Tests aus. Für jemanden, der für die Entwicklung eines Backend Microservice verantwortlich ist, sind End-to-End- und manuelle Tests eine besondere Herausforderung. Das eigene Microservice ist nur ein kleines Rädchen in einem größeren Ganzen. Um die gesamte Anwendung zu testen, müssen zig verschiedene, technisch heterogene Microservices zusammenspielen. Eine solche Umgebung für Debugging und Tests aufzubauen, ist eine Herausforderung.

Lösungsansatz: Container

Container sind ein Schlüsselelement zur Lösung dieses Problems. Als Entwickler kann man Microservices, die man für integrative Tests braucht, als Container hochfahren und braucht sich dadurch nicht um ihre Systemvoraussetzungen zu kümmern. Ohne Container sind Tests über Microservicegrenzen hinweg oft sehr schwierig, weil jeder Microservice spezifische Einstellungen, Laufzeitumgebungen, Patches etc. braucht.

Microsoft hat diesem Problem in der Azure-Cloud ein eigenes Produkt gewidmet: Azure Dev Spaces. Sie machen es Entwicklerinnen besonders einfach, ein Microservice zu Entwicklungs- und Testzwecken in eine Kubernetes-Umgebung zu deployen, in der die anderen Microservices verfügbar sind. Dadurch reduziert man die Anforderungen an die Leistung und richtige Konfiguration der Entwicklungsumgebung enorm.

Wer eine fertige Lösung wie Dev Spaces nicht einsetzen kann oder will, der sollte seine Gesamtlösung so bauen, dass Entwicklerinnen möglichst einfach lokal oder in Testumgebungen laufende Microservices in eine Gesamtumgebung einbinden können. Hier einige Beispiele:

  • Es sollte durch Konfiguration möglich sein, eine Testumgebung so einzustellen, dass ein gewisses Microservice durch eine Test- oder Entwicklungsversion ersetzt werden kann. Das muss beim Erarbeiten des Service-Discovery-Konzepts für die Microservices-Architektur berücksichtigt werden.
  • Lokal, z. B. im Debugger laufende Microservices sind bei HTTP-basierender Kommunikation nicht so einfach in eine Gesamtlösung, die in der Cloud läuft, zu integrieren. Werkzeuge wie ngrok sind in diesem Zusammenhang hilfreich und sollten in Zusammenarbeit mit den Netzwerkteams den Entwicklungsteams zugänglich gemacht werden. Alternativ kann man das lokale Entwicklungsnetzwerk auch mit VLANs in der Cloud (z. B. Azure-VPN-Gateway) verbinden und so eine Verbindung von Cloud- und Entwicklungsumgebung für Debugging und Tests ermöglichen.
  • Bei Message-basierenden Protokollen (z. B. Azure Service Bus) spielt es oft ein geringere Rolle, ob der eine Kommunikationspartner in der Cloud und der andere auf einem Entwicklungsrechner oder in einer Testumgebung läuft.

Infrastructure as a flexible Resource

Die Cloud macht das automatisierte, integrative Testen aber nicht nur schwieriger, sie ermöglicht auch neue Szenarien und Vorgehensweisen. Wer auf das manuelle Konfigurieren seiner Cloudumgebung über irgendwelche bunten Portale verzichtet und stattdessen getreu der DevOps-Mentalität die Cloudarbeiten über Scripts automatisiert, der kann jederzeit und ohne großen Aufwand Testumgebungen für Integrations- und End-to-End-Tests hochfahren. Statt solche Tests auf lokalen Servern laufen zu lassen, die sich den Großteil des Tages langweilen, rollt man bei Bedarf eine neue Umgebung in der Cloud aus, lässt die Tests laufen, und löscht am Ende wieder alles. Durch das nutzungsbasierte Preismodell in der Cloud sind die Kosten für dieses Vorgehen minimal.

Dazu kommt, dass man sich durch diesen Testansatz immer sicher sein kann, dass man jederzeit eine komplett neue Cloudumgebung von Grund auf herstellen kann. Es gibt viele Situationen, in denen das nützlich ist. Hier einige Beispiele:

  • Man möchte in neue geografische Regionen vorstoßen und beschließt, in entsprechenden Rechenzentren eine neue Produktionsumgebung hochzufahren.
  • Man ist wegen eines Problems (z. B. Rechenzentrumsausfall, DDoS-Attacke) gezwungen, kurzfristig auf ein neues Deployment umzusteigen.
  • Man braucht kurzfristig eine produktionsgleiche Umgebung für Tests, die in der Produktionsumgebung nicht machbar sind (z. B. Destructive Testing, Lasttests, Penetrationstests).
  • Entwicklungsteams können ohne großen Aufwand produktionsgleiche Umgebungen ausrollen, in denen sie anderen Teams neue Softwareversionen zur Verfügung stellen können (z. B. explorative Tests durch Testteam, Akzeptanztests durch Kunden, Tests durch Friendly Customers, etc.).
  • Für Trainings- oder Demonstrationsszenarien können produktionsgleiche Umgebungen einfach herstellt werden.

Testdatengenerator

In diesem Zusammenhang empfehle ich, darüber nachzudenken, ob im jeweiligen Projekt beim automatisierten Ausrollen von Cloudumgebungen das optionale Befüllen mit Testdaten wertvoll ist. Bei vielen der oben genannten Beispiele wäre es hilfreich, wenn die erstellte Anwendung nicht leer, sondern mit Daten befüllt wäre. Diese Testdaten sollten nicht statisch sein. Sie sollten über Scripts generiert werden, damit sie aktuell sind (z. B. Datum von generierten Transaktionen in einem aktuellen Zeitbereich) und die Menge an Testdaten konfigurierbar ist (z. B. für Lasttests).

Es gibt in der Cloud eine Menge nützlicher Testdatengeneratoren, die für gewisse Datenkategorien (z. B. Namen von Personen oder Produkten, Länder, Zahlenwerte mit definierter, statischer Verteilung, GPS Koordinaten, Kreditkartennummern, Profilbilder) Testdaten generieren. Eine Internetrecherche mit Begriffen wie Generate Test Data bringt Sie auf die richtige Spur. Viele Generatoren sind für kleinere Szenarien kostenlos, bieten aber meistens auch kostenpflichtige Preispläne, bei denen größere Datenmengen generiert werden können. Die Testdaten stehen über Webservices zur Verfügung und können dadurch in Scripts zum automatisierten Aufbau von Testumgebungen integriert werden.

Headless Coding

Egal, ob automatisiert oder manuell getestet wird, selbst mit schlauen Deployment-Skripts, Containern und Dev Spaces ist das integrative Testen eines Microservice im Kontext des Gesamtsystems eine Herausforderung. Normalerweise ist man gewohnt, mit einem Klick in der Entwicklungsumgebung eine Debug Session binnen Sekunden zu starten. Ist damit aber ein Deployment in irgendeine Cloudumgebung verbunden, verlängert sich die Zeit zum Starten des Tests oder Debuggers um viele Sekunden, vielleicht sogar Minuten. Insofern kommt meiner Ansicht nach Unit-Tests in Zusammenhang mit Microservices eine besondere Bedeutung zu. Sie ermöglichen mir als Entwickler, meinen Code ohne UI, also headless auszuprobieren und zu debuggen. Sie steigern durch die reduzierte Wartezeit meine Produktivität. Das ist neben den üblicherweise vorgebrachten Vorteilen wie positive Auswirkungen auf das Softwaredesign, Dokumentationscharakter und Schutz vor Regressionen ein wichtiges Argument für Unit-Tests und wiegt den Nachteil des Wartungsaufwands für die Tests zu einem Teil auf.

Windows Developer

Windows DeveloperDieser Artikel ist im Windows Developer erschienen. Windows Developer informiert umfassend und herstellerneutral über neue Trends und Möglichkeiten der Software- und Systementwicklung rund um Microsoft-Technologien.

Natürlich können Sie den Windows Developer über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. Außerdem ist der Windows Developer weiterhin als Print-Magazin im Abonnement erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -