KAPITEL 9

Infrastruktur-Stacks testen

Dieses Kapitel wendet die zentrale Praktik des kontinuierlichen Testens und Auslieferns von Code auf Infrastruktur-Stacks an. Es nutzt das ShopSpinner-Beispiel, um zu zeigen, wie Sie ein Stack-Projekt testen. Dazu gehört der Einsatz von Online- und Offline-Test-Stages und das Verwenden von Test-Fixtures, um den Stack von den Abhängigkeiten zu entkoppeln.

Beispiel-Infrastruktur

Das ShopSpinner-Team nutzt Reusable-Stack-Projekte (siehe »Pattern: Reusable Stack« auf Seite 102), um für alle Kundinnen und Kunden des Unternehmens konsistente Instanzen von Anwendungs-Infrastruktur zu schaffen. Es kann diese auch verwenden, um Testinstanzen der Infrastruktur in der Pipeline anzulegen.

Die Infrastruktur für diese Beispiele ist ein Standard-Drei-Schichten-System. Die Infrastruktur in jeder Schicht besteht aus:

Webserver-Container-Cluster

Das Team betreibt ein einzelnes Webserver-Container-Cluster für jede Region und in jeder Testumgebung. Anwendungen in der Region oder Umgebung nutzen dieses Cluster gemeinsam. Die Beispiele in diesem Kapitel konzentrieren sich auf die Infrastruktur, die spezifisch für jede Kundin und jeden Kunden ist, und lassen die gemeinsame Infrastruktur außen vor. Daher stellt das gemeinsam genutzte Cluster in diesen Beispielen eine Abhängigkeit dar. Details zum Koordinieren und Testen von Änderungen, die diese Infrastruktur betreffen, finden Sie in Kapitel 17.

Anwendungsserver

Die Infrastruktur für jede Anwendungs-Instanz besteht aus einer virtuellen Maschine, einem persistenten Disk Volume und dem Networking. Zum Networking gehören ein Adressblock, ein Gateway, Routen zum Server und seinem Netzwerkport und Netzwerk-Zugriffsregeln.

Datenbank

ShopSpinner betreibt eine separate Datenbankinstanz für jede Instanz der Kundenanwendung und greift dafür auf den DBaaS des Providers zurück (siehe »Storage-Ressourcen« auf Seite 59). Der Infrastruktur-Code von Shop-Spinner definiert zudem einen Adressblock, Routing und die Regeln für die Datenbank-Authentifizierung und den Zugriff.

Der Beispiel-Stack

Zunächst können wir einen einzelnen Reusable Stack definieren, der bis auf das Webserver-Cluster die gesamte Infrastruktur enthält. Die Projektstruktur sieht vielleicht wie in Listing 9-1 aus.

Listing 9-1: Stack-Projekt für die Kundenanwendung von ShopSpinner

stack-project/

└── src/

├── appserver_vm.infra

├── appserver_networking.infra

├── database.infra

└── database_networking.infra

In diesem Projekt enthält die Datei appserver_vm.infra Code unter anderem mit den Zeilen aus Listing 9-2.

Listing 9-2: Teil des Inhalts von appserver_vm.infra

virtual_machine:

name: appserver-${customer}-${environment}

ram: 4GB

address_block: ADDRESS_BLOCK.appserver-${customer}-${environment}

storage_volume: STORAGE_VOLUME.app-storage-${customer}-${environment}

base_image: SERVER_IMAGE.shopspinner_java_server_image

provision:

tool: servermaker

parameters:

maker_server: maker.shopspinner.xyz

role: appserver

customer: ${customer}

environment: ${environment}

storage_volume:

id: app-storage-${customer}-${environment}

size: 80GB

format: xfs

Ein Teammitglied oder ein automatisierter Prozess kann eine Instanz des Stacks erzeugen oder aktualisieren, indem das Stack-Tool ausgeführt wird. Mit einem der Patterns aus Kapitel 7 übergibt man Werte an die Instanz.

Wie in Kapitel 8 beschrieben nutzt das Team mehrere Test-Stages (siehe »Progressives Testen« auf Seite 148), die in einer sequenziellen Pipeline organisiert sind (siehe »Infrastruktur-Delivery-Pipelines« auf Seite 152).

Pipeline für den Beispiel-Stack

Eine einfache Pipeline für den Infrastruktur-Stack der ShopSpinner-Anwendung besteht aus zwei Test-Stages,1 gefolgt von einer Stage, die den Code auf die Produktivumgebung aller Kunden anwendet (siehe Abbildung 9-1).

image

Abbildung 9-1: Vereinfachte Beispiel-Pipeline für einen Stack

Die erste Stage der Pipeline ist die Stack-Build-Stage. Eine Build-Stage für eine Anwendung kompiliert normalerweise Code, führt Unit Tests aus (siehe »Testpyramide« auf Seite 149) und baut ein deploybares Artefakt. In »Ein Infrastruktur-Projekt bauen« auf Seite 364 gehe ich detaillierter auf die typische Build-Stage für Infrastruktur-Code ein. Weil die frühen Stages in einer Pipeline schneller laufen sollten, wird die erste Stage normalerweise dazu genutzt, Offlinetests auszuführen.

Die zweite Stage der Beispiel-Pipeline führt Onlinetests für das Stack-Projekt aus. Jede der Pipeline-Stages kann mehr als einen Satz Tests laufen lassen.

Offline-Test-Stages für Stacks

Eine Offline-Stage lässt »lokal« einen Agenten-Knoten des Service laufen, der die Stage betreibt (siehe »Software und Services für die Delivery-Pipeline« auf Seite 156), statt Infrastruktur auf Ihrer Infrastruktur-Plattform provisionieren zu müssen. Strikte Offlinetests laufen vollständig auf dem lokalen Server oder in der Container-Instanz, ohne sich mit einem externen Service (wie einer Datenbank) zu verbinden. Eine nicht ganz so strikte Offline-Stage mag sich mit einer bestehenden Serviceinstanz verbinden (vielleicht sogar mit einer Cloud-API), nutzt aber keine echte Stack-Infrastruktur.

Folgendes sollte eine Offline-Stage tun:

Zu den Tests, die Sie für Ihren Stack-Code in einer Offline-Stage ausführen können, können Syntax-Checks, statische Offline-Code-Analyse, statische Code-Analyse mit der Plattform-API und das Testen mit einer Mock-API gehören.

Syntax-Checks

Bei den meisten Stack-Tools können Sie einen Dry-Run-Befehl ausführen, der Ihren Code parst, ohne ihn auf die Infrastruktur anzuwenden. Der Befehl endet mit einem Fehler, wenn es einen Syntaxfehler gibt. Mit diesem Check erfahren Sie sehr schnell, ob Sie einen Tippfehler in Ihrer Code-Änderung haben, während viele andere Fehler unentdeckt bleiben. Beispiele für skriptbare Tools für Syntax-Checks sind terraform validate oder aws cloudformation validate-template.

Die Ausgabe eines fehlschlagenden Syntax-Checks kann so aussehen:

$ stack validate

Error: Invalid resource type

on appserver_vm.infra line 1, in resource "virtual_mahcine":

stack does not support resource type "virtual_mahcine".

Statische Offline-Code-Analyse

Manche Tools können Stack-Quellcode parsen und analysieren und dabei mehr als nur die Syntax prüfen, während sie trotzdem nicht mit einer Infrastruktur-Plattform verbunden sind. Diese Analyse wird oft als Linting bezeichnet.1 Solche Tools halten nach Coding-Fehlern und verwirrendem oder schlechtem Programmierstil Ausschau, achten darauf, dass Stilrichtlinien für den Code eingehalten werden und dass auf potenzielle Sicherheitsprobleme hingewiesen wird. Manche Tools können den Code sogar anpassen, damit dieser einem bestimmten Stil entspricht, wie zum Beispiel der Befehl terraform fmt. Es gibt nicht so viele Tools zum Analysieren von Infrastruktur-Code wie für Anwendungs-Programmiersprachen. Beispiele sind tflint (https://oreil.ly/ZBAng), CloudFormation Linter (https://oreil.ly/lDuDV), cfn_nag (https://oreil.ly/u2Hp1), tfsec (https://oreil.ly/isSKE) und checkov (https://www.checkov.io).

Hier ein Beispiel für einen Fehler aus einem fiktiven Analysetool:

$ stacklint

1 issue(s) found:

Notice: Missing 'Name' tag (vms_must_have_standard_tags)

on appserver_vm.infra line 1, in resource "virtual_machine":

In diesem Beispiel haben wir eine selbst definierte Regel namens vms_must_have_standard_tags, nach der alle virtuellen Maschinen einen bestimmten Satz Tags besitzen müssen – unter anderen eines mit dem Namen Name.

Statische Code-Analyse per API

Abhängig vom Tool verbinden sich manche Checks zur statischen Code-Analyse eventuell mit der API der Cloud-Plattform, um zu prüfen, ob es zu Konflikten mit dem kommt, was die Plattform anbietet. So kann tflint beispielsweise Terraform-Projektcode prüfen, um sicherzustellen, dass alle im Code definierten Instanztypen (Größe virtuelle Maschinen) oder AMIs (Server-Images) tatsächlich vorhanden sind. Anders als beim Preview von Änderungen (siehe »Preview: Prüfen, welche Änderungen vorgenommen werden« auf Seite 169) prüft diese Art von Validierung den Code im Allgemeinen und nicht gegen eine spezifische Stack-Instanz auf der Plattform.

Die folgende Beispielausgabe liefert einen Fehler, weil der Code, der den virtuellen Server deklariert, ein Server-Image angibt, das auf der Plattform nicht vorhanden ist:

$ stacklint

1 issue(s) found:

Notice: base_image 'SERVER_IMAGE.shopspinner_java_server_image' doesn't

exist (validate_server_images)

on appserver_vm.infra line 5, in resource "virtual_machine":

Testen mit einer Mock-API

Eventuell können Sie Ihren Stack-Code auf eine lokale Mock-Instanz der API Ihrer Infrastruktur-Plattform anwenden. Es gibt nicht viele Tools zum Mocken dieser APIs. Das einzige, das ich aktuell kenne, ist Localstack (https://oreil.ly/8Quwj). Manche Tools können Teile einer Plattform mocken, wie zum Beispiel Azurite (https://oreil.ly/FazP_), das Blob- und Queue-Storage bei Azure emuliert.

Das Anwenden deklarativen Stack-Codes auf einen lokalen Mock kann Coding-Fehler aufdecken, die durch Syntax- oder Code-Analyse-Checks nicht erkannt werden. In der Praxis ist das Testen deklarativen Codes durch Mocks von Infrastruktur-Plattform-APIs nicht allzu wertvoll – aus den Gründen, die in »Herausforderung: Tests für deklarativen Code haben häufig nur einen geringen Wert« auf Seite 143 aufgeführt sind. Aber diese Mocks können nützlich sein, wenn Sie Unit Tests von imperativem Code laufen lassen (siehe »Programmierbare, imperative Infrastruktur-Sprachen« auf Seite 73), insbesondere für Bibliotheken (siehe »Stack-Elemente dynamisch mit Bibliotheken erstellen« auf Seite 315).

Online-Test-Stages für Stacks

In einer Online-Stage wird die Infrastruktur-Plattform genutzt, um eine Instanz des Stacks zu erstellen und mit ihr zu interagieren. Diese Art von Stage ist langsamer, kann aber zu sinnvolleren Tests führen als bei Offlinetests. Der Delivery-Pipeline-Service führt das Stack-Tool normalerweise auf einem seiner Knoten oder Agenten aus, aber es nutzt die Plattform-API, um mit einer Instanz auf dem Stack zu interagieren. Der Service muss sich gegen die API der Plattform authentifizieren – in »Secrets als Parameter nutzen« auf Seite 133 finden Sie Ideen zum sicheren Umgang.

Auch wenn eine Online-Test-Stage von der Infrastruktur-Plattform abhängt, sollten Sie den Stack mit möglichst wenig anderen Abhängigkeiten testen können. Insbesondere sollten Sie Ihre Infrastruktur, Stacks und Tests so designen, dass Sie eine Instanz eines Stacks erstellen und testen können, ohne auf Instanzen anderer Stacks zugreifen zu müssen.

So arbeitet beispielsweise die Kundenanwendungs-Infrastruktur bei ShopSpinner mit einem gemeinsam genutzten Webserver-Cluster-Stack. Die Teammitglieder von ShopSpinner implementieren ihre Infrastruktur und Test-Stages aber mit Techniken, die es ihnen erlauben, den Anwendungs-Stack-Code ohne eine Instanz des Webserver-Clusters zu testen.

Ich behandle Techniken zum Aufteilen von Stacks und für eine lose Kopplung in Kapitel 15. Wenn Sie Ihre Infrastruktur so aufgebaut haben, können Sie mit Test-Fixtures einen Stack für sich alleine testen – so wie es in »Test-Fixtures für den Umgang mit Abhängigkeiten verwenden« auf Seite 172 beschrieben ist.

Denken Sie zuerst darüber nach, wie die verschiedenen Arten von Online-Stack-Tests arbeiten. Dazu gehört das Previewing von Änderungen, das Verifizieren, dass Änderungen korrekt angewendet werden, und das Kontrollieren der Ergebnisse.

Preview: Prüfen, welche Änderungen vorgenommen werden

Manche Stack-Tools können den Stack-Code mit einer Stack-Instanz abgleichen, um zu zeigen, welche Änderungen sie vornehmen würden, ohne sie tatsächlich umzusetzen. Der Unterbefehl plan von Terraform ist ein gutes Beispiel dafür.

Meist lassen sich Entwicklerinnen und Entwickler Änderungen gegen Produktivinstanzen als Sicherheitsmaßnahme anzeigen, sodass sie die Liste der Änderungen durchgehen können, um sich selbst zu versichern, dass nichts Unerwartetes geschehen wird. Das Anwenden von Änderungen auf einen Stack lässt sich in einer Pipeline in zwei Schritten erledigen. Der erste ist das Ausführen der Preview, danach triggert eine Person den zweiten Schritt, um die Änderungen anzuwenden, nachdem sie die Ergebnisse der Preview begutachtet hat.

Es ist allerdings nicht sehr zuverlässig, Personen die Änderungen begutachten zu lassen. Vielleicht missverstehen sie eine potenzielle Änderung oder sie übersehen sie. Sie können automatisierte Tests schreiben, die die Ausgabe eines Preview-Befehls prüfen. Solche Tests könnten Änderungen mit Vorgaben abgleichen und fehlschlagen, wenn der Code beispielsweise einen veralteten Ressourcen-Typ erzeugen würde. Oder sie prüfen auf disruptive Änderungen und schlagen fehl, wenn der Code eine Datenbankinstanz neu bauen oder zerstören würde.

Ein weiteres Problem ist, dass Stack-Tool-Previews im Allgemeinen nicht sehr tief gehen. Eine Preview sagt Ihnen, dass dieser Code einen neuen Server erzeugen wird:

virtual_machine:

name: myappserver

base_image: "java_server_image"

Aber die Preview sagt Ihnen nicht, dass "java_server_image" gar nicht existiert, auch wenn der apply-Befehl den Server nicht wird erstellen können.

Das Previewen von Stack-Änderungen ist nützlich, um eine begrenzte Zahl von Risiken direkt vor dem Anwenden einer Code-Änderung auf eine Instanz zu prüfen. Aber es ist weniger nützlich, um Code zu testen, den Sie über mehrere Instanzen hinweg wiederverwenden wollen, wie zum Beispiel auf Testumgebungen für das Release-Delivery. Teams, die Copy-Paste Environments verwenden (siehe »Antipattern: Copy-Paste Environments« auf Seite 100), greifen oft auf eine Preview-Stage als minimalen Test für jede Umgebung zurück. Aber Teams, die Reusable Stacks verwenden (siehe »Pattern: Reusable Stack« auf Seite 102), können Testinstanzen für eine sinnvollere Validierung ihres Codes nutzen.

Verifikation: Aussagen über Infrastruktur-Ressourcen treffen

Für eine gegebene Stack-Instanz können Sie in einer Online-Stage Tests ausführen, die Aussagen über die Infrastruktur im Stack treffen. Beispiele für Frameworks zum Testen von Infrastruktur-Ressourcen sind unter anderem:

Ein Satz von Tests für die virtuelle Maschine aus dem Beispiel-Stack-Code weiter oben in diesem Kapitel könnte so aussehen:

given virtual_machine(name: "appserver-testcustomerA-staging") {

it { exists }

it { is_running }

it { passes_healthcheck }

it { has_attached storage_volume(name: "app-storage-testcustomerA-staging") }

}

Die meisten Stack-Test-Tools stellen Bibliotheken bereit, die dabei helfen, Aussagen über die Art von Infrastruktur-Ressourcen zu tätigen, die ich in Kapitel 3 beschreibe. Dieser Beispieltest nutzt eine Ressource virtual_machine, um die VM in der Stack-Instanz für die Staging-Umgebung zu finden. Er prüft eine Reihe von Aussagen über die Ressource, unter anderem darüber, ob sie erstellt wurde (exists), ob sie läuft und nicht beendet wurde (is_running) und ob die Infrastruktur-Plattform sie als gesund betrachtet (passes_healthcheck).

Einfache Aussagen haben oft nur einen geringen Wert (siehe »Herausforderung: Tests für deklarativen Code haben häufig nur einen geringen Wert« auf Seite 143), da sie mehr oder weniger nur den Infrastruktur-Code wiederholen, den sie testen. Ein paar grundlegende Aussagen (wie zum Beispiel exists) helfen zumindest dabei, zu prüfen, ob der Code erfolgreich angewendet wurde. Damit lassen sich schnell grundlegende Probleme mit der Pipeline-Stage-Konfiguration oder dem Test-Setup-Skript erkennen. Tests wie is_running oder passes_healthcheck würden Ihnen verraten, dass das Stack-Tool die VM erfolgreich erstellt hat, sie dann aber abbricht oder andere grundlegende Probleme hat. Einfache Aussagen wie diese ersparen Ihnen viel Zeit bei der Fehlersuche.

Auch wenn Sie Aussagen erstellen können, die die einzelnen Konfigurationselemente der VM im Stack-Code widerspiegeln – wie zum Beispiel die Menge an RAM oder die zugewiesene Netzwerkadresse –, haben sie nur wenig Wert und sorgen für mehr Overhead.

Die vierte Aussage im Beispiel (has_attached storage_volume()) ist interessanter. Sie prüft, ob das im gleichen Stack definierte Storage Volume mit der VM verbunden wurde. Damit wird kontrolliert, ob die Kombination mehrerer Deklarationen korrekt funktioniert (siehe »Kombinationen deklarativen Codes testen« auf Seite 146). Abhängig von Ihrer Plattform und Ihren Tools lässt sich der Stack-Code eventuell erfolgreich anwenden, trotzdem sind Server und Volume danach nicht korrekt miteinander verbunden. Oder Sie machen einen Fehler in Ihrem Stack-Code, und das Anhängen schlägt fehl.

Ein weiterer Fall, bei dem Aussagen nützlich sein können, ist dynamischer Stack-Code. Wenn das Übergeben unterschiedlicher Parameter an einen Stack zu unterschiedlichen Ergebnissen führen kann, wollen Sie vielleicht Aussagen über diese Ergebnisse treffen können. Als Beispiel erzeugt der folgende Code die Infrastruktur für einen Anwendungsserver, der entweder nach außen sichtbar ist oder nur internen Zwecken dienen soll:

virtual_machine:

name: appserver-${customer}-${environment}

address_block:

if(${network_access} == "public")

ADDRESS_BLOCK.public-${customer}-${environment}

else

ADDRESS_BLOCK.internal-${customer}-${environment}

end

Sie könnten eine Test-Stage nutzen, die von jedem Typ eine Instanz erstellt und prüft, ob die Networking-Konfiguration jeweils in Ordnung ist. Sie sollten komplexere Variationen in Module oder Bibliotheken verlagern (siehe Kapitel 16) und diese Module getrennt vom Stack-Code testen. Damit vereinfachen Sie das Testen des Stack-Codes.

Aussagen darüber, dass Infrastruktur-Ressourcen wie erwartet erzeugt wurden, sind bis zu einem gewissen Grad hilfreich. Aber viel wertvoller ist die Prüfung, ob sie das tun, was sie sollen.

Ergebnisse: Prüfen, dass die Infrastruktur korrekt arbeitet

Funktionale Tests sind ein wichtiger Aspekt beim Testen von Anwendungssoftware. Die Analogie bei Infrastruktur ist die Prüfung, ob Sie sie wie gewünscht einsetzen können. Beispiele für Ergebnisse, die Sie mit Infrastruktur-Stack-Code testen können, sind:

Das Testen von Ergebnissen ist komplizierter als eine Prüfung auf Existenz. Ihre Tests müssen nicht nur die Stack-Instanz erstellen oder aktualisieren, was ich in »Lebenszyklus-Patterns für Testinstanzen von Stacks« auf Seite 176 beschreibe, Sie müssen zudem vermutlich auch Test-Fixtures provisionieren. Ein Test-Fixture ist eine Infrastruktur-Ressource, die nur zum Unterstützen eines Tests dient (ich gehe darauf in »Test-Fixtures für den Umgang mit Abhängigkeiten verwenden« auf Seite 172 ein).

Dieser Test erstellt eine Verbindung zum Server, um zu prüfen, ob der Port erreichbar ist und die erwartete HTTP-Response zurückgibt:

given stack_instance(stack: "shopspinner_networking",

instance: "online_test") {

can_connect(ip_address: stack_instance.appserver_ip_address,

port:443)

http_request(ip_address: stack_instance.appserver_ip_address,

port:443,

url: '/').response.code is('200')

}

Das Test-Framework und die Bibliotheken implementieren die Details der Validierung, wie zum Beispiel can_connect oder http_request. Sie werden die Dokumentation Ihres Test-Tools studieren müssen, um herauszufinden, wie Sie echte Tests schreiben können.

Test-Fixtures für den Umgang mit Abhängigkeiten verwenden

Viele Stack-Projekte hängen von Ressourcen ab, die außerhalb des Stacks erstellt wurden, wie zum Beispiel gemeinsames Networking, dass in einem anderen Stack-Projekt erstellt wurde. Ein Test-Fixture ist eine Infrastruktur-Ressource, die Sie nur erzeugen, um dabei zu helfen, selbst eine Stack-Instanz zu provisionieren und zu testen, ohne auf Instanzen anderer Stacks zurückgreifen zu müssen. Test-Doubles, die in »Herausforderung: Abhängigkeiten verkomplizieren die Test-Infrastruktur« auf Seite 148 erwähnt wurden, sind eine Art von Test-Fixture.

Durch den Einsatz von Test-Fixtures wird es viel einfacher, Tests zu managen, Ihre Stacks lose gekoppelt zu halten und schnelle Feedback-Schleifen nutzen zu können. Ohne Test-Fixtures müssen Sie eventuell komplizierte Kombinationen von Test-Infrastruktur erstellen und warten.

Ein Test-Fixture ist nicht Teil des Stacks, den Sie testen. Es handelt sich um zusätzliche Infrastruktur, die Sie anlegen, um Ihre Tests zu unterstützen. Sie verwenden Test-Fixtures, um die Abhängigkeiten eines Stacks zu repräsentieren.

Eine solche Abhängigkeit ist entweder Upstream – der zu testende Stack nutzt Ressourcen anderer Stacks – oder Downstream – andere Stacks nutzen Ressourcen vom getesteten Stack. Manchmal wird ein Stack mit Downstream-Abhängigkeiten als Provider bezeichnet, da er Ressourcen bereitstellt. Ein Stack mit Upstream-Abhängigkeiten ist dann ein Consumer (siehe Abbildung 9-2).

image

Abbildung 9-2: Beispiel eines Provider-Stacks und eines Consumer-Stacks

Ein solcher Stack kann sowohl Provider wie auch Consumer sein, wenn er Ressourcen eines anderen Stacks nutzt und selbst Ressourcen für andere Stacks bereitstellt. Sie können Test-Fixtures sowohl als Ersatz für Upstream- wie auch für Downstream-Integrationspunkte eines Stacks nutzen.

Test-Doubles für Upstream-Abhängigkeiten

Müssen Sie einen Stack testen, der von einem anderen Stack abhängt, können Sie ein Test-Double erstellen. Bei Stacks bedeutet das meist, zusätzliche Infrastruktur zu erzeugen. In unserem Beispiel mit dem gemeinsamen Networking-Stack und dem Anwendungs-Stack muss der Anwendungs-Stack seinen Server in einem Netzwerk-Adressblock erzeugen, der vom Networking-Stack definiert wurde. Ihr Test-Setup kann eventuell einen Adressblock als Test-Fixture erzeugen, um den Anwendungs-Stack für sich zu testen.

Es kann besser sein, den Adressblock als Test-Fixture anzulegen, statt eine Instanz des gesamten Networking-Stacks zu erzeugen. Der Networking-Stack kann zusätzliche Infrastruktur enthalten, die für das Testen nicht notwendig ist. So definiert er möglicherweise Networking-Policies, Routen, Auditing und andere Ressourcen für die Produktivumgebung, die für einen Test zu viel des Guten wären.

Zudem sorgt das Erstellen der Abhängigkeit als Test-Fixture im Consumer-Stack-Projekt für ein Entkoppeln vom Provider-Stack. Arbeitet jemand an einer Änderung im Networking-Stack-Projekt, beeinflusst das nicht die Arbeit am Anwendungs-Stack.

Ein potenzieller Vorteil dieser Art zu entkoppeln ist, dass Stacks besser wiederverwendbar und kombinierbar werden. Das ShopSpinner-Team möchte vielleicht unterschiedliche Networking-Stack-Projekte für verschiedene Zwecke erstellen. Ein Stack erzeugt ein stark kontrolliertes und überwachtes Networking für Services mit strengeren Regulierungsanforderungen, wie zum Beispiel für die Bezahlvorgänge beim PCI-Standard (https://oreil.ly/MuWAM) oder Schutzregelungen für Kundendaten. Ein weiterer Stack erzeugt ein Networking, das nicht PCI-konform sein muss. Durch das Testen von Anwendungs-Stacks ohne einen dieser Stacks wird es für das Team leichter, den Stack-Code mit beiden Networking-Stacks zu nutzen.

Test-Fixtures für Downstream-Abhängigkeiten

Sie können Test-Fixtures auch für die umgekehrte Situation verwenden und einen Stack testen, der Ressourcen für andere Stacks bereitstellt. In Abbildung 9-3 definiert die Stack-Instanz Networking-Strukturen für ShopSpinner, unter anderem mit Segmenten und Routing für das Webserver-Container-Cluster und die Anwendungsserver. Der Networking-Stack provisioniert das Container-Cluster oder die Anwendungsserver nicht – um das Networking zu testen, provisioniert das Setup daher ein Test-Fixture in jedem dieser Segmente.

image

Abbildung 9-3: Testinstanz des Networking-Stacks von ShopSpinner mit Test-Fixtures

Die Test-Fixtures in diesem Beispiel sind ein Paar Container-Instanzen, die jeweils den Netzwerksegmenten im Stack zugewiesen werden. Sie können für das Ergebnis-Testen oft die gleichen Test-Tools verwenden, die Sie auch zum Verifizierungs-Testen einsetzen (siehe »Verifikation: Aussagen über Infrastruktur-Ressourcen treffen« auf Seite 169). Diese Beispieltests nutzen eine fiktive Stack-Testing-DSL:

given stack_instance(stack: "shopspinner_networking",

instance: "online_test") {

can_connect(from: $HERE,

to: get_fixture("web_segment_instance").address,

port:443)

can_connect(from: get_fixture("web_segment_instance"),

to: get_fixture("app_segment_instance").address,

port: 8443)

}

Die Methode can_connect wird in $HERE ausgeführt, was entweder der Agent wäre, auf dem der Testcode läuft, oder eine Container-Instanz. Sie versucht, eine HTTPS-Verbindung an den spezifizierten Port einer IP-Adresse herzustellen. Die Methode get_fixtures() holt sich die Details einer Container-Instanz, die als Test-Fixture erstellt wurde.

Das Test-Framework bietet eventuell die Methode can_connect an, es kann sich aber auch um eine Methode handeln, die das Team schreibt.

Die Verbindungen, die der Beispiel-Testcode herstellt, sehen Sie in Abbildung 9-4.

image

Abbildung 9-4: Die Verbindungen im ShopSpinner-Networking-Stack testen

Die Abbildung zeigt die Wege beider Tests. Der erste Test verbindet sich von außerhalb des Stacks mit dem Test-Fixture im Websegment. Der zweite Test verbindet sich vom Fixture im Websegment zum Fixture im Anwendungssegment.

Komponenten refaktorieren, um sie isolieren zu können

Manchmal lässt sich eine bestimmte Komponente nicht so einfach isolieren. Vielleicht sind Abhängigkeiten zu anderen Komponenten hartcodiert, oder das Ganze ist einfach zu unübersichtlich, um es auseinandernehmen zu können. Einer der Vorteile, Tests während des Designens und Bauens von Systemen zu schreiben statt erst danach, ist, dass es Sie dazu zwingt, Ihre Designs zu verbessern. Eine Komponente, die sich schlecht isoliert testen lässt, ist ein Symptom für ein Designproblem. Ein gut designtes System sollte lose gekoppelte Komponenten besitzen.

Stolpern Sie also über Komponenten, die sich nur schwer isolieren lassen, sollten Sie das Design korrigieren. Sie müssen vielleicht Komponenten komplett neu schreiben oder Bibliotheken, Tools oder Anwendungen ersetzen. Aber wie man so sagt: Das ist ein Feature und kein Bug. Sauberes Design und lose gekoppelter Code ergeben sich als Nebeneffekte, wenn Sie ein System testbar machen.

Es gibt eine Reihe von Strategien, um Systeme zu restrukturieren. Martin Fowler hat über das Refaktorieren (https://oreil.ly/3oSt8) und andere Techniken zum Verbessern der Systemarchitektur geschrieben. So liegt beispielsweise bei der Strangler Application (https://oreil.ly/Lb7Zw) der Fokus darauf, das System vollständig lauffähig zu halten, während Sie es nach und nach restrukturieren.

In »Teil IV« dieses Buchs finden Sie detailliertere Anleitungen und Beispiele für das Modularisieren und Integrieren von Infrastruktur.

Lebenszyklus-Patterns für Testinstanzen von Stacks

Vor der Virtualisierung und der Cloud hat jeder statische und langlebige Testumgebungen betreut. Auch wenn viele Teams immer noch diese Umgebungen nutzen, hat es Vorteile, Umgebungen kurzfristig erstellen und zerstören zu können. Die folgenden Patterns beschreiben die Vor- und Nachteile, die sich ergeben, wenn Sie eine persistente Stack-Instanz nutzen, eine kurzlebige Instanz für jeden Testdurchlauf anlegen oder beide Ansätze kombinieren. Sie können diese Patterns auf Testumgebungen für Anwendungen oder vollständige Systeme anwenden, aber eben auch auf das Testen von Infrastruktur-Stack-Code.

Pattern: Persistent Test Stack

Auch bekannt als: Static Environment.

Eine Test-Stage kann eine Instanz eines Persistent Test Stack nutzen, die immer läuft. Die Stage wendet jede Code-Änderung als Aktualisierung auf die bestehende Stack-Instanz an, führt die Tests aus und belässt den sich daraus ergebenden, angepassten Stack für den nächsten Durchlauf (siehe Abbildung 9-5).

Motivation

Es ist im Allgemeinen viel schneller, Änderungen auf eine bestehende Stack-Instanz anzuwenden, als eine neue Instanz zu erzeugen. Daher kann der Persistent Test Stack schnelleres Feedback liefern – nicht nur für die Stage selbst, sondern auch für die gesamte Pipeline.

Anwendbarkeit

Ein Persistent Test Stack ist nützlich, wenn Sie Ihren Stack-Code zuverlässig auf die Instanz anwenden können. Stellen Sie fest, dass Sie viel Zeit damit verbringen, kaputte Instanzen zu reparieren, damit die Pipeline wieder läuft, sollten Sie sich eines der anderen Patterns in diesem Kapitel anschauen.

image

Abbildung 9-5: Instanz eines Persistent Test Stack

Konsequenzen

Es ist nicht ungewöhnlich, dass sich Stack-Instanzen »verklemmen«, wenn eine Änderung fehlschlägt und sie die Instanz in einem Zustand zurücklässt, in dem ein neuer Versuch, Stack-Code anzuwenden, nicht mehr funktioniert. Oft verkeilt sich eine Instanz so stark, dass das Stack-Tool nicht mal mehr in der Lage ist, den Stack zu zerstören, damit Sie neu beginnen können. Dann verbringt Ihr Team zu viel Zeit damit, kaputte Testinstanzen wieder zum Laufen zu bringen.

Sie können die Häufigkeit verklemmter Stacks oft durch besseres Stack-Design reduzieren. Teilen Sie Stacks in kleinere und einfachere Stacks auf und vereinfachen Sie die Abhängigkeiten zwischen Stacks, dann verringern Sie die Gefahr solcher Hakeleien. In Kapitel 15 finden Sie mehr dazu.

Implementierung

Ein Persistent Test Stack lässt sich einfach implementieren. Ihre Pipeline-Stage führt das Stack-Tool zum Aktualisieren der Instanz mit der relevanten Version des Stack-Codes aus, startet die Tests und belässt die Stack-Instanz dann einfach in ihrem Zustand.

Sie können den Stack ad hoc neu bauen, zum Beispiel durch Ausführen des Tools auf einem lokalen Computer oder durch den Einsatz einer zusätzlichen Stage oder eines Jobs außerhalb des normalen Pipeline-Ablaufs.

Zugehörige Patterns

Das Periodic-Stack-Rebuild-Pattern aus »Pattern: Periodic Stack Rebuild« auf Seite 180 ist eine leichte Variation dieses Pattern, bei der die Instanz am Ende jedes Arbeitstags zerstört und am nächsten Morgen wieder neu gebaut wird.

Pattern: Ephemeral Test Stack

Auch bekannt als: Quick and Dirty plus Slow and Clean.

Mit dem Ephemeral-Test-Stack-Pattern erzeugt die Test-Stage bei jedem Durchlauf eine neue Instanz des Stacks und zerstört sie am Ende wieder (siehe Abbildung 9-6).

image

Abbildung 9-6: Instanz eines Ephemeral Test Stack

Motivation

Ein Ephemeral Test Stack bietet eine saubere Umgebung für jeden Testlauf. Es gibt kein Risiko, dass Daten, Fixtures oder anderer »Kruscht« von einem vorigen Lauf übrig geblieben sind.

Anwendbarkeit

Sie können Ephemeral-Instanzen für Stacks nutzen, die sich schnell von Grund auf provisionieren lassen. »Schnell« ist allerdings relativ zu sehen in Bezug auf die Feedback-Schleife, die Sie und Ihre Teams benötigen. Bei häufigeren Änderungen, wie zum Beispiel Commits von Anwendungscode während hektischer Entwicklungsphasen, ist der Zeitaufwand zum Bauen einer neuen Umgebung vermutlich größer, als das von den Leuten toleriert werden kann. Aber bei weniger häufigen Änderungen, zum Beispiel Betriebssystem-Patches, kann es akzeptabel sein, mit einem vollständigen Rebuild zu testen.

Konsequenzen

Es braucht im Allgemeinen sehr lange, um Stacks von Grund auf zu provisionieren. Daher machen Stages, die Ephemeral-Stack-Instanzen nutzen, Feedback-Schleifen und Delivery-Zyklen langsamer.

Implementierung

Um eine Ephemeral-Testinstanz zu implementieren, sollte Ihre Test-Stage das Stack-Tool nutzen, um die Stack-Instanz zu zerstören, wenn das Testen und Reporting abgeschlossen sind. Eventuell konfigurieren Sie die Stage so, dass die Instanz nach einem fehlgeschlagenen Test nicht zerstört wird, damit der Fehlschlag debuggt werden kann.

Zugehörige Patterns

Das Continuous-Stack-Reset-Pattern (siehe »Pattern: Continuous Stack Reset« auf Seite 182) ist ähnlich, führt das Erstellen und Zerstören des Stacks aber unabhängig von der Stage durch, sodass dieser Zeitaufwand nicht die Feedback-Schleifen beeinträchtigt.

Antipattern: Dual Persistent and Ephemeral Stack Stages

Auch bekannt als: Nightly Rebuild.

Mit Persistent and Ephemeral Stack Stages schickt die Pipeline jede Änderung an einem Stack an zwei verschiedene Stages – eine, die eine Ephemeral-Stack-Instanz nutzt, und eine, die eine persistente Stack-Instanz verwendet. Das kombiniert das Persistent-Test-Stack-Pattern (siehe »Pattern: Persistent Test Stack« auf Seite 176) mit dem Ephemeral-Test-Stack-Pattern (siehe »Pattern: Ephemeral Test Stack« auf Seite 178).

Motivation

Teams implementieren dieses Pattern normalerweise in Kombination, um die Nachteile, die jedes Pattern für sich genommen hat, zu umgehen. Wenn alles funktioniert, liefert die »Quick-and-Dirty«-Stage (die mit der persistenten Instanz) schnelles Feedback. Schlägt diese Stage fehl, weil die Umgebung nicht mehr sauber ist, erhalten Sie später Feedback von der »Slow-and-Clean«-Stage (der mit der kurzlebigen Instanz).

Anwendbarkeit

Es kann sich lohnen, beide Arten von Stages als Zwischenlösung zu implementieren, bis Sie eine zuverlässigere Lösung nutzen können.

Konsequenzen

In der Praxis kombiniert ein Einsatz beider Typen von Stack-Lebenszyklen die Nachteile statt die Vorteile. Ist das Aktualisieren eines bestehenden Stacks unzuverlässig, wird Ihr Team immer noch Zeit damit verbringen, diese Stage zu reparieren, wenn etwas schiefgeht. Und vermutlich warten Sie trotzdem auf die langsamere Stage, um sicher zu sein, dass eine Änderung sauber ist.

Dieses Antipattern ist zudem teuer, da es doppelt so viele Infrastruktur-Ressourcen nutzt – zumindest während des Testlaufs.

Implementierung

Sie implementieren zwei Stages durch das Erstellen von zwei Pipeline-Stages, die jeweils von der vorigen Stage in der Pipeline für das Stack-Projekt ausgelöst werden (siehe Abbildung 9-7). Eventuell legen Sie fest, dass beide Stages erfolgreich durchlaufen werden müssen, bevor Sie die Stack-Version an die nächste Stage weitergeben. Oder Sie reichen sie schon weiter, wenn eine der beiden Stages erfolgreich war.

image

Abbildung 9-7: Persistent- und Ephemeral-Stack-Stages

Zugehörige Patterns

Dieses Antipattern kombiniert das Persistent-Test-Stack-Pattern (siehe »Pattern: Persistent Test Stack« auf Seite 176) mit dem Ephemeral-Test-Stack-Pattern (siehe »Pattern: Ephemeral Test Stack« auf Seite 178).

Pattern: Periodic Stack Rebuild

Beim Periodic Stack Rebuild wird eine persistente Test-Stack-Instanz (siehe »Pattern: Persistent Test Stack« auf Seite 176) für die Stack-Test-Stage genutzt. Dazu gibt es einen Prozess, der unabhängig davon läuft und die Stack-Instanz anhand eines Zeitplans (zum Beispiel nachts) zerstört und neu aufsetzt.

Motivation

Oft kommen periodische Rebuilds zum Einsatz, um Kosten zu sparen. Dabei wird der Stack am Ende des Arbeitstages zerstört und ein neuer zu Beginn des nächsten Tages provisioniert.

Periodische Rebuilds können bei unzuverlässigen Stack-Updates helfen – je nachdem davon, warum die Updates unzuverlässig sind. In manchen Fällen summiert sich der Ressourcen-Einsatz von Instanzen mit der Zeit auf, wie zum Beispiel Speicher oder Storage. Regelmäßige Resets können helfen, das aufzuräumen.

Anwendbarkeit

Das Neuaufbauen einer Stack-Instanz, um den Ressourcen-Einsatz zu verringern, verbirgt im Allgemeinen die eigentlichen Probleme oder Designfehler. Dann ist dieses Pattern im besten Fall ein temporärer Hack und im schlimmsten Fall ein Weg, Probleme anzusammeln, bis sie zu einem Desaster führen.

Das Zerstören einer ungenutzten Stack-Instanz, um Kosten zu sparen, ist durchaus vernünftig – insbesondere, wenn gebührenpflichtige Ressourcen zum Einsatz kommen (beispielsweise von einer Public-Cloud-Plattform).

Konsequenzen

Verwenden Sie dieses Pattern, um ungenutzte Ressourcen freizugeben, müssen Sie sich überlegen, wie Sie sicherstellen können, dass sie tatsächlich nicht gebraucht werden. So kann es beispielsweise sein, dass Teammitglieder außerhalb der Bürozeiten oder in anderen Zeitzonen arbeiten und ohne Testumgebung blockiert werden.

Implementierung

Die meisten Pipeline-Orchestrierungs-Tools machen es leicht, Jobs anzulegen, die zu festen Zeiten Stack-Instanzen zerstören und wieder anlegen. Eine ausgefeiltere Lösung würde anhand der Aktivitätsauslastung vorgehen. Sie könnten beispielsweise einen Job nutzen, der eine Instanz zerstört, wenn die Test-Stage in der vergangenen Stunde nicht gelaufen ist.

Es gibt drei Optionen, das Bauen einer frischen Instanz nach dem Zerstören der vorigen Instanz anzustoßen. Eine ist, sie direkt nach dem Zerstören neu zu erstellen. Dieser Ansatz räumt Ressourcen auf, spart aber keine Kosten.

Eine zweite Option ist, die neue Umgebungsinstanz zu einer festen Uhrzeit neu zu bauen. Das hält aber eventuell die Entwicklung davon ab, flexibel arbeiten zu können.

Die dritte Option ist, für die Test-Stage eine neue Instanz zu provisionieren, wenn sie noch nicht existiert. Erstellen Sie einen eigenen Job, der die Instanz zerstört – entweder anhand eines festen Zeitplans oder nach einer Phase der Inaktivität. Jedes Mal, wenn die Test-Stage läuft, prüft sie zunächst, ob die Instanz schon vorhanden ist. Wenn nicht, wird zuerst eine neue Instanz provisioniert. Mit diesem Ansatz muss man gelegentlich länger als üblich auf die Testergebnisse warten. Sind Sie zum Beispiel die erste Person, die morgens eine Änderung auf den Weg bringt, müssen Sie warten, bis das System den Stack provisioniert hat.

Zugehörige Patterns

Dieses Pattern kann sich so verhalten wie das Persistent-Test-Stack-Pattern (siehe »Pattern: Persistent Test Stack« auf Seite 176) – sind Ihre Stack-Updates unzuverlässig, muss Zeit für das Reparieren kaputter Instanzen aufgewendet werden.

Pattern: Continuous Stack Reset

Mit dem Continuous-Stack-Reset-Pattern zerstört jedes Mal, wenn die Stack-Test-Stage abgeschlossen ist, ein davon unabhängiger Job die Stack-Instanz und baut sie neu auf (siehe Abbildung 9-8).

image

Abbildung 9-8: Pipeline-Flow für den Continuous Stack Reset

Motivation

Wenn Sie die Stack-Instanz jedes Mal zerstören und neu aufbauen, erhalten Sie für jeden Testlauf einen sauberen Status. Es kann automatisch eine kaputte Instanz entfernt werden, sofern sie nicht so kaputt ist, dass das Stack-Tool sie nicht mehr abräumen kann. Und die Feedback-Schleife wird nicht durch den Zeitaufwand zum Erstellen und Zerstören der Stack-Instanz belastet.

Ein weiterer Vorteil dieses Pattern ist, dass Sie den Update-Prozess zuverlässig testen können, der für die gegebene Stack-Code-Version im Produktivumfeld ausgeführt werden würde.

Anwendbarkeit

Das Zerstören der Stack-Instanz im Hintergrund kann gut funktionieren, wenn das Stack-Projekt normalerweise nicht ins Straucheln gerät und manuell korrigiert werden muss.

Konsequenzen

Da der Stack außerhalb des Delivery-Flows zerstört und provisioniert wird, werden Probleme eventuell nicht so schnell erkannt. Die Pipeline kann grün sein, aber die Testinstanz hat hinter den Kulissen Probleme. Erreicht die nächste Änderung die Test-Stage, kann es dauern, bis man erkennt, dass sie wegen des Hintergrund-Jobs und nicht wegen der Änderung selbst fehlschlägt.

Implementierung

Wird die Test-Stage erfolgreich durchlaufen, wird der Stack-Projektcode an die nächste Stage weitergereicht. Zudem wird ein Job getriggert, der die Stack-Instanz zerstört und neu baut. Schiebt jemand eine neue Änderung in die Pipeline, wendet die Test-Stage sie auf die Instanz als Update an.

Sie müssen sich entscheiden, welche Version des Stack-Codes Sie beim erneuten Bauen der Instanz verwenden. Sie können die gleiche Version einsetzen, die gerade eben die Stage durchlaufen hat. Alternativ können Sie die neueste Version des Stack-Codes holen, die auf die Produktivinstanz angewendet wurde. So wird jede Version des Stack-Codes als Aktualisierung der aktuellen Produktivversion getestet. Abhängig davon, wie Ihr Infrastruktur-Code normalerweise bis in die Produktivumgebung fließt, kann das eine exaktere Darstellung des Produktiv-Upgrade-Prozesses sein.

Zugehörige Patterns

Idealerweise ähnelt dieses Pattern dem Persistent-Test-Stack-Pattern (siehe »Pattern: Persistent Test Stack« auf Seite 176) und gibt Feedback, während es gleichzeitig die Zuverlässigkeit des Ephemeral-Test-Stack-Patterns besitzt (siehe »Pattern: Ephemeral Test Stack« auf Seite 178).

Test-Orchestrierung

Ich habe alle Bestandteile beschrieben, die zum Testen von Stacks benötigt werden: die Arten von Tests und Validierungen, die Sie anwenden können, den Einsatz von Test-Fixtures zum Umgang mit Abhängigkeiten und Lebenszyklen für Test-Stack-Instanzen. Aber wie sollten Sie all das zusammenbringen, um Tests aufzusetzen und auszuführen?

Die meisten Teams nutzen Skripte, um ihre Tests zu orchestrieren. Oft sind das die gleichen Skripte, die Sie auch zum Orchestrieren des Ausführens ihrer Stack-Tools verwenden. In »Infrastruktur-Tools durch Skripte verpacken« auf Seite 378 werde ich mich mit diesen Skripten befassen, die sich eventuell um die Konfiguration kümmern, Aktionen über mehrere Stacks hinweg koordinieren und andere Aktivitäten wie zum Beispiel das Testen organisieren.

Zur Test-Orchestrierung kann Folgendes gehören:

Die meisten dieser Themen – wie Test-Fixtures und die Lebenszyklen von Stack-Instanzen – wurden weiter oben in diesem Kapitel schon behandelt. Andere – zum Beispiel das Ausführen der Tests und das Konsolidieren der Ergebnisse – hängen vom jeweiligen Tool ab.

Zwei Grundsätze, die Sie beim Orchestrieren von Tests berücksichtigen sollten, sind das Unterstützen lokalen Testens und das Vermeiden einer engen Kopplung mit den Pipeline-Tools.

Unterstützen Sie lokales Testen

Personen, die an Infrastruktur-Stack-Code arbeiten, sollten dazu in der Lage sein, die Tests selbst ausführen zu können, bevor sie Code in die gemeinsame Pipeline und die Umgebungen befördern. In »Private Infrastruktur-Instanzen« auf Seite 391 werden Ansätze vorgestellt, die dabei helfen, mit persönlichen Stack-Instanzen auf einer Infrastruktur-Plattform zu arbeiten. So können Sie Code schreiben und Onlinetests ausführen, bevor Sie die Änderungen pushen.

Neben der Möglichkeit, mit persönlichen Testinstanzen von Stacks arbeiten zu können, benötigen die Leute die Test-Tools und andere beteiligte Elemente in ihrer lokalen Arbeitsumgebung. Viele Teams nutzen Codegesteuerte Entwicklungsumgebungen mit automatisierten Installationen und Konfigurationstools. Sie können Container oder virtuelle Maschinen verwenden, um Entwicklungsumgebungen zu verpacken, die dann auf den unterschiedlichen Arten von Desktop-Systemen laufen können.1 Alternativ kann Ihr Team gehostete Workstations verwenden (hoffentlich per Code konfiguriert), auch wenn diese eher Latenzprobleme haben können – insbesondere bei verteilten Teams.

Ein Schlüssel, um Test selbst leichter ausführen zu können, ist der Einsatz der gleichen Test-Orchestrierungs-Skripte für die lokale Arbeit und Pipeline-Stages. So stellen Sie sicher, dass die Tests überall konsistent aufgesetzt und ausgeführt werden.

Vermeiden Sie eine enge Kopplung mit Pipeline-Tools

Viele Tools zur CI- und Pipeline-Orchestrierung besitzen Features oder Plug-ins für die Test-Orchestrierung oder sogar die Möglichkeit, die Tests für Sie zu konfigurieren und auszuführen. Das scheint zwar praktisch zu sein, erschwert es aber, Ihre Tests konsistent auch außerhalb der Pipeline aufzusetzen und auszuführen. Das Vermischen von Test- und Pipeline-Konfiguration kann das Vornehmen von Änderungen ebenfalls unbequem machen.

Stattdessen sollten Sie Ihre Test-Orchestrierung in einem eigenen Skript oder Tool implementieren. Die Test-Stage sollte dieses Tool aufrufen und dabei möglichst wenig Konfigurationsparameter mitgeben müssen. So bleiben die Aspekte der Pipeline-Orchestrierung und der Test-Orchestrierung lose gekoppelt.

Tools zur Test-Orchestrierung

Viele Teams schreiben eigene Skripte, um Tests zu orchestrieren. Diese Skripte ähneln den Skripten, die zum Orchestrieren des Stack-Managements zum Einsatz kommen (siehe »Infrastruktur-Tools durch Skripte verpacken« auf Seite 378), oder es handelt sich sogar um die gleichen Skripte. Dabei kommen Bash-Skripte, Batchdateien, Ruby, Python, Make, Rake und anderes zum Einsatz, von dem ich noch nie gehört habe.

Es gibt ein paar Tools, die spezifisch zum Orchestrieren von Infrastruktur-Tests dienen. Zwei mir bekannte sind Test Kitchen (https://kitchen.ci) und Molecule (https://oreil.ly/_CZtn). Bei Test Kitchen handelt es sich um ein Open-Source-Produkt von Chef, das ursprünglich für das Testen von Chef-Cookbooks gedacht war. Molecule ist ein Open-Source-Tool für das Testen von Ansible-Playbooks. Sie können beide Tools verwenden, um Infrastruktur-Stacks zu testen – zum Beispiel mit Kitchen-Terraform (https://oreil.ly/BBfoT).

Die Herausforderung liegt bei diesen Tools darin, dass sie für einen bestimmten Workflow designt sind und sich eventuell nur schlecht für den Workflow anpassen lassen, den Sie benötigen. Manche passen sie an und biegen sie sich zurecht, während es andere einfacher finden, ihre eigenen Skripte zu schreiben.

Zusammenfassung

Dieses Kapitel hat ein Beispiel für das Erstellen einer Pipeline mit mehreren Stages zum Implementieren der zentralen Praktik des kontinuierlichen Testens und Auslieferns von Stack-Code vorgestellt. Eine der Herausforderungen beim Testen von Stack-Code sind die Tools. Auch wenn es schon einige gibt – viele davon habe ich in diesem Kapitel vorgestellt –, kommen TDD, CI und automatisiertes Testen aktuell nicht sehr oft für Infrastruktur zum Einsatz. Sie müssen selbst erst einmal die Tools entdecken, die Sie einsetzen können, und eventuell Lücken durch eigene Skripte füllen. Hoffentlich wird Ihnen das mit der Zeit besser gelingen.