Teststrategie

Mithilfe der Schleupen.CS-Testpyramide wird definiert, wie fachliche Funktionalität sichergestellt wird. Dies wird durch eine möglichst hohe Testabdeckung erreicht, wobei alle Bausteine, aber auch Kopplungspunkte getestet werden. Eine automatisierte Durchführung ist zwingend erforderlich, um eine geeignete Qualität dauerhaft sicherstellen zu können. Hierzu verwendet Schleupen eine Continuous-Delivery-Pipeline, die bei jedem Check-In angestoßen wird und die verschiedenen Tests einer Teststufe in verschiedenen Schritten ausführt.

Testpyramide

Warum Pyramide?

Die Testpyramide besteht aus mehreren Teststufen, die gleichartige Tests beinhalten. Je höher eine Teststufe in der Pyramide angesiedelt ist, desto teurer die Implementierung im Allgemeinen. Denn die Entwicklung zahlreicher Geschäftsprozesstests (als Beispiel für höherwertige Tests) sind in der Entwicklung deutlich aufwendiger als die Erstellung von Unittests. Zudem ist eine vollständige Codeabdeckung durch Geschäftsprozesstests unmöglich. Hinzu kommen längere Durchlaufzeiten von höherwertigen Tests, so dass diese eine zeitnahe Auslieferung konterkarieren. Dennoch sind diese höherwertigen Tests bindend notwendig, um das Zusammenspiel der Bausteine sicherzustellen. Um ein geeignetes Maß zu finden, wird die Anzahl der unterschiedlichen Testarten durch eine Pyramide ausgedrückt: Tests der obersten Ebene sind weniger zu erstellen, Tests der untersten Ebene gibt es am meisten. Unittests werden auf der untersten Ebene der Testpyramide erstellt. Diese benötigen eine sehr hohe Codeabdeckung, um Codeänderungen nahezu gefahrlos durchführen zu können. Die Durchlaufzeiten von Unittests sind gering, Unittests sind einfach zu implementieren und zu warten.

Damit ergibt sich in Summe das Ziel, eine möglichst flache und breite Pyramide zu erstellen. Denn je früher ein Fehler gefunden wird, desto geringer der Schaden und desto billiger die Reparatur.

Die Testpyramide ist wie bereits erwähnt in Teststufen unterteilt. Die Definition der Teststufen ergibt sich also daraus, dass eine Teststufe für Tests definiert wird, die sich signifikant in der Anzahl unterscheiden. Insbesondere ist hierbei die Ausführungszeit und der Implementierungsaufwand, aber auch das Testziel relevant. Dabei sollte ein Test nur genau ein Testziel haben (und damit aussagekräftig sein).

Schleupen-Testpyramide samt Teststufen

In Schleupen.CS definieren wir folgende Teststufen, die z.T. noch untergliedert sind:

  • HIP-IT – HIP-Integrationstest
    Diese Tests finden vierteljährlich gemäß unseres an SAFe angelehnten Entwicklungsmodells statt und beinhalten sowohl explorative als auch betriebliche und Performancetests.
  • GPT – Geschäftsprozesstests
    Geschäftsprozesstests testen Geschäftsprozesse von Ende zu Ende und weisen somit ein hohe Komplexität auf. Auch die Durchlaufzeiten sind deutlich höher als bei Tests niedrigerer Teststufen.
  • AIT – Automated Integrative Tests
    Diese Tests beinhalten Tests, die Abschnitte von Geschäftsprozessen testen. Das sind beispielsweise Dialogabläufe oder Workflows. Aber auch integrative Tests innerhalb von Ländern und Geschäftsprozesskomponenten werden hierzu gezählt.
  • CD – Tests im CD-Build
    Tests im CD-Build haben eine sehr kurze Durchlaufzeit und sind Unittests und Tests im Zusammenspiel mit einer lokalen Datenbank als einzige Abhängigkeit. 

Strategie - Wann schreibt man Tests für welche Stufe?

Generell stellt sich die Frage, wie man entscheidet, ob Tests auf einer bestimmten Teststufe erstellt werden sollten. Hier verwendet Schleupen folgende Strategie: Wird ein Fehler auf einer höheren Stufe entdeckt (beispielsweise im Regressionstest), so sollte zunächst ein Unittest erstellt werden, der den Fehler nachstellt. Schleupen.CS ist in der Mikroarchitektur derart konzipiert, dass für alle Klassen Unittests erstellt werden können. Damit kann also einfach eine breite, flache Testpyramide erreicht werden.

Welche Tests sollten auf höheren Ebenen definiert werden? Hier gibt es kein festes Kriterium, wohl aber eine Daumenregel:
Je wichtiger der Testfall für das Geschäft des Kunden, desto eher ist dieser als höherwertiger Test (z.B. Geschäftsprozess-Test) zu implementieren, um die Funktionalität in Summe sicherzustellen.

Ausführungsstufe in Pipeline

Da bei Schleupen eine Continuous-Delivery-Pipeline verwendet wird, werden diese Stufen entsprechend wie folgt abgebildet:

Continuous-Delivery-Pipeline in Schleupen.CS

Dabei werden durch ein Check-In die Tests der ersten Stufe ausgeführt. Sind für den Baustein alle Tests der ersten Stufe - also die Unittests, ComponentTests und Repository.ConnectionTests - erfolgreich durchlaufen, so hat er die erste Stufe der Continuous-Integration bestanden. Danach durchläuft der Baustein im Erfolgsfall die weiteren Stufen Development, Release-Candidate und Certified-Release, wobei jeweils die zugehörigen automatisierten Tests ausgeführt werden.

Teststufen

Im Folgenden werden die einzelnen Teststufen genauer betrachtet.

Unittests

Unittests testen nur eine einzelne Klasse. Nur im Domänenmodell darf hiervon leicht abgewichen werden und sich diese auch auf ein Aggregat beziehen (was streng genommen kein Unittest ist). Denn ein Aggregat hat eine Konsistenzgrenze die sichergestellt werden muss und in Schleupen.CS hierfür keine eigene Teststufe ausgeprägt worden ist. Wichtig ist in jedem Fall, dass keine Infrastruktur verwendet wird. 

Mithilfe von Unittests können Whitebox-Tests und Blackbox-Tests implementiert werden. Die Ausführungszeit ist per se gering, da keinerlei Infrastruktur verwendet werden darf. Auch ist die Implementierung hierdurch einfach.

Die folgende Abbildung zeigt, an welchen Stellen, welche Art von Unittests empfehlenswert ist. Prinzipiell können alle Bausteine per Unittest getestet werden. Spezielle Klassen des Schleupen.CS-Frameworks vereinfachen hier die Arbeit - so kann beispielsweise ein bereitgestellter Test verwendet werden, der den Zusammenbau der Serviceimplementierung per Dependency-Injection-Container sicherstellt.

Für das Codieren von Unittests kommen geeignete Hilfsmittel wie Testdouble (Mocks, Stubs, Testspy, etc.) beispielsweise bei Usecase-Controllern zum Einsatz. Hierzu verwenden wir das Framework Moq. Zur vereinfachten Testdatenerstellung wird u.a. das Framework AutoFixture.

Unittests in Ländern

Die folgende Abbildung zeigt, wo Unittests in GP-Komponenten bei Workflows zum Einsatz kommen.

Unittests für Workflows in GP-Komponenten
Unittests für UI-Elemente in GP-Komponenten

Analog können Unittests in GP-Komponenten für UI-Elemente erstellt werden.

In einer typischen Solution werden die Projekte bzgl. der Teststufen getrennt. So befinden sich die Unittests in Projekten mit dem Postfix UnitTests, wie in der folgenden Abbildung für eine Land-Solution exemplarisch dargestellt, ersichtlich ist. Damit kann die Zuordnung zu den Schritten der Continuous-Delivery-Pipeline einfach zugeordnet werden.

Test-Projekte in einer Land-Solution gemäß Konvention

Unittests werden durch Entwickler implementiert.

ComponentTests

ComponentTests testen einen Baustein (= Komponente) isoliert, d.h. ohne Infrastruktur und ohne andere Bausteine (Länder oder GP-Komponenten). Hierbei werden Tests von sinnvollen Szenarien / Geschäftsvorfällen (d.h. nicht Prozesse!), mit echter Testdatenbank für das getestete Land ohne weitere externe Ressourcen, also keine echten externen Services, durchgeführt. Ziel dieser Tests ist das Sicherstellen der Funktionalität der Komponente primär als BlackBox-Tests. Es wird das Zusammenspiel der einzelnen Bausteine sichergestellt.

ComponentTests haben eine relativ geringe Ausführungszeit und können entsprechend zur Continuous-Integration verwendet werden. Sie sind allerdings langsamer als Unittests, da eine Datenbank als einzige externe Komponente erlaubt ist.

Mocking externer Komponenten bei ComponentTests in Ländern

An den "Nahtstellen" der Komponente wie zum Beispiel Gateways werden Mocks oder andere Test-Double verwendet, um ein geeignetes externes Verhalten zu emulieren.

Die folgenden Abbildungen zeigen dies auch noch einmal für Workflow-Tests und UI-Tests in GP-Komponenten.

Mocking externer Komponenten bei ComponentTests in Workflows
Mocking externer Komponenten bei ComponentTests in UI-Elementen

Analog zu den Projekten für Unittests, werden Projekte zur klaren Trennung der Tests für ComponentTests definiert. Exemplarisch kann man dies am Beispiel eines Landes sehen:

Projekte für ComponentTests in einer Solution

ComponentTests werden durch Entwickler implementiert.

ConnectionTests

Die Teststufe der ConnectionTests dient zum Testen des Anschlusses externer Ressourcen. Dies ist z.B. die Datenbankanbindung, Nutzung externer Services oder das Dateisystem. Diese Tests gehören zu den Automated-Integrative-Tests (vgl. Pipeline).

ConnectionTests in Ländern

Diese Teststufe teilt sich in zwei Stufen auf, die auf verschiedenen Ebenen der Pyramide angesiedelt sind und in der Continuous-Delivery-Pipeline zu unterschiedlichen Zeitpunkten ausgeführt werden.

Repository.ConnectionTests

Diese Tests werden im CD-Build ausgeführt und testen insbesondere die Implementierung von Repositorys im Zusammenspiel mit einer Datenbank. Aus der Erfahrung heraus hat sich hier eine echte Datenbank als vorteilhaft herausgestellt, wobei die Laufzeit hier natürlich höher ist als bei Unittests.

Die Projekte sind hier analog organisiert:

Projekte für ConnectionTests in einer Solution

ConnectionTests werden durch Entwickler implementiert.

ContractTests (fällt als Kategorie mit ComponentTests zusammen)

Mithilfe von Consumer-Driven Contract-Tests werden Service-Schnittstellen bezüglich der Schemata & Semantik anhand „echter“ Einsatzszenarien aus Nutzungssicht überprüft (Regressionstest, Inhalt i.d.R. von nutzenden Teams). Dabei wird zum einen durch den Bereitsteller des Service durch Tests sichergestellt, dass das Ergebnis eines Service-Calls dasselbe ist wie das vom Client erwartete. Diese Erwartungshaltung wird in Form eines Contracts definiert, der sowohl syntaktische als auch semantisches Kompatibilität sicherstellt. Gleichzeitig kann im Service diese Erwartungshaltung im Mock des Service-Client codiert werden, so dass das echte Zusammenspiel von Nutzer und Service nicht mehr integrativ getestet werden muss. Es reichen höherwertige integrative Tests wie unten beschrieben.

Auf integrative Tests kann nicht vollständig verzichtet werden, da mit diesem Konzept derzeit nur synchrone Aufrufe, nicht aber asynchrone Verarbeitungen getestet werden können (Jobs, Messaging, usw.).

ContractTests werden ähnlich wie ComponentTests implementiert, so dass nur der Unterschied durch den Ersteller existiert. Wenn möglich, werden diese Tests dem betreuenden Team des Microservice übergeben, um hierdurch möglichst früh Rückmeldung zu bekommen. Bei externen Teams geht dies natürlich nicht.

Diese Tests haben noch eine relativ geringe Ausführungszeit zu Geschäftsprozesstests. Die schematische Nutzung von Land-Services zeigt folgende Abbildung:

Äquivalent ist dies für Services von GP-Komponenten.

Implementiert werden diese Tests durch Entwickler als Blackbox-Tests.

Das Vorgehen zur Erstellung dieser Tests ist in Muster: ContractTest für WCF-Services beschrieben.

IntegrativeTests

IntegrativeTests einer Komponente mit weiteren Ressourcen wie insbesondere Services werden ebenfalls auf AIT-Ebene in der Pipeline ausgeführt, da sie eine spürbar höhere Laufzeit aufweisen. Diese Tests einer Komponente stellen das Zusammenspiel mit anderen direkten Nachbarkomponenten sicher.

Auch diese Tests werden vornehmlich durch Entwickler implementiert. Dann werden hierzu ebenfalls eigene Test-Projekte wie hier im Beispiel eines Landes angelegt.

Projekte für integrative Tests in einer Solution

In ausgewählten Szenarien (z.B. durch Verwendung von Pester werden hier auch Tests gemäß Behavior-Driven-Development durch Tester erstellt.

Um Autonomie bei den Teams zu verbessern, ist es häufig sinnvoll, Services insbesondere anderer Teams, die bei den integrativen Tests genutzt werden, zu mocken / zu faken.

Hierzu verwenden wir einen sogenannten FakeServer, der auf WireMock.Net basiert.

Dieser kann insbesondere sowohl REST-Services als auch SOAP-Services (insbesondere der WCF) faken und konfigurierbare Ergebnisse zurückgeben. Hierzu wird in der Service Registry eine anderer Endpunkt - der des FakeServers - eingetragen.

GP-Abschnitt-Tests

Geschäftsprozess-Abschnitt-Tests gehören zu den Automated-Integrative-Tests (vgl. Pipeline) und Testen nur die Funktionalität einer GP-Komponente (oder eines Landes) im Sinne des Testziels. Diese Tests erstrecken sich im Allgemeinen mindestens über eine GP-Komponente (oder ein Land) und weisen entsprechend eine deutlich höhere Komplexität hinsichtlich der Erstellung auf. Auch die Laufzeit ist entsprechend deutlich höher als der darunterliegende Teststufe. Die Testbeschreibung gehört zur GP-Komponente.

Bausteine von GP-Abschnitt-Tests

Auch diese Tests sind durch Tester erstellbar.

Auch hier ist häufig die Verwendung eines FakeServers sinnoll!

GP-Tests

Tests, die ganze End-To-End-Geschäftsprozesse testen, sind aufwendig zu implementieren und zu warten und weisen teils eine hohe Komplexität auf. Bei der Ausführung ist es wichtig, dass sie auf klar definierte Datenstände und klar definierte Systemzustände aufsetzen um möglichst deterministische Ergebnisse zu erzielen. Auch ist es hier wichtig, nicht weitere Testziele wie Lasttest o.ä. reinzumischen. Das Testziel ist die Sicherstellung der Funktionalität des Gesamtprozesses.

GP-Tests benötigen zur parallelen Ausführung isolierte Testdaten

Die Geschäftsprozesstests teuer in der Erstellung sind, sollten zuerst die Geschäftsrelevantesten umgesetzt werden.

Weitere Anforderungen an die Testdaten sind:

  • Testdaten müssen für jeden Testfall (= GP-Test) isolierbar sein, damit Tests parallel ausführbar sind und man möglichst schnell ein Gesamttestergebnis erhält. Hierbei wird zwischen Testdaten, die nicht geändert werden (Read-Only-Daten) und Daten, die durch den Test geändert werden (Write-Enabled), unterschieden. Letztere müssen individuell pro Test sein, um Kollisionen bei paralleler Testausführung zu vermeiden.
Daten werden unterschiedlich bei der Ausführung genutzt, was bei Parallelität zu beachten ist
  • Testdaten sollen möglichst nah am echten Szenario sein.
  • Testdaten müssen der EU-DSGVO genügen.
  • Testdaten müssen im Einklang mit Softwareständen versioniert werden können.
Explorative Tests

Explorative Tests werden 1/4-jährlich durch Test-Touren durchgeführt. Hier wird versucht, ein Echt-Szenario nachzubilden. Diese Tests sind natürlich sehr teuer und nur selten durchführbar.

Betriebliche Tests

Ähnlich verhält es sich mit betrieblichen Tests wie

  • Backup / Recovery
  • Verhalten unter Last
  • Sicherheitstests
  • ...
Cookie Consent mit Real Cookie Banner