Suchen Sie nach einer Möglichkeit, Tests auf einfachere Weise zu erstellen? Wir haben Sie! Lesen Sie den folgenden Artikel und erfahren Sie, wie Sie es möglich machen.
Die moderne Anwendungsentwicklung basiert auf einer einfachen Regel:
Zusammensetzung verwenden
Wir fügen Klassen, Funktionen und Dienste zu größeren Softwareteilen zusammen. Dieses letzte Element ist die Grundlage von Microservices und der hexagonalen Architektur. Wir möchten bestehende Lösungen nutzen, sie in unsere Software integrieren und direkt auf die Markt.
Möchten Sie die Kontoregistrierung abwickeln und Benutzerdaten speichern? Sie können einen der OAuth-Dienste wählen. Vielleicht bietet Ihre Anwendung eine Art von Abonnement oder Zahlung an? Es gibt viele Dienste, die Ihnen bei dieser Aufgabe helfen können. Benötigen Sie Analysen für Ihre Website, verstehen aber die GDPR nicht? Nehmen Sie doch einfach eine der fertigen Lösungen.
Etwas, das die Entwicklung aus geschäftlicher Sicht so einfach macht, kann Ihnen Kopfschmerzen bereiten - nämlich dann, wenn Sie einen einfachen Test schreiben müssen.
Die Phantastischen Tierwesen: Warteschlangen, Datenbanken und wie man sie testet
Unit-Tests sind ziemlich einfach. Wenn Sie nur die Regeln befolgen, dann werden Ihre Testumgebung und Code gesund sind. Welche Regeln sind das?
Leicht zu schreiben - ein Unit-Test sollte einfach zu schreiben sein, weil man viele davon schreibt. Weniger Aufwand bedeutet, dass mehr Tests geschrieben werden.
Lesbar - der Testcode sollte leicht zu lesen sein. Der Test ist eine Geschichte. Er beschreibt das Verhalten der Software und kann als Abkürzung für die Dokumentation verwendet werden. Ein guter Unit-Test hilft Ihnen, Fehler zu beheben, ohne den Code zu debuggen.
Zuverlässig - der Test sollte nur dann fehlschlagen, wenn das getestete System einen Fehler aufweist. Offensichtlich? Nicht immer. Manchmal bestehen Tests, wenn Sie sie einzeln ausführen, aber sie schlagen fehl, wenn Sie sie als Gruppe ausführen. Sie funktionieren auf Ihrem Rechner, aber nicht auf dem CI (Funktioniert auf meinem Rechner). Bei einem guten Einheitstest gibt es nur einen Grund für ein Scheitern.
Schnell - Tests sollten schnell sein. Die Vorbereitung, der Start und die Testausführung selbst sollten sehr schnell gehen. Andernfalls werden Sie sie zwar schreiben, aber nicht ausführen. Langsame Tests bedeuten, dass man den Fokus verliert. Man wartet und schaut auf den Fortschrittsbalken.
Unabhängig - Schließlich sollte der Test unabhängig sein. Diese Regel ergibt sich aus den vorangegangenen. Nur wirklich unabhängige Tests können eine Einheit bilden. Sie stören sich nicht gegenseitig, können in beliebiger Reihenfolge ausgeführt werden und mögliche Fehler hängen nicht von den Ergebnissen anderer Tests ab. Unabhängig bedeutet auch, dass sie nicht von externen Ressourcen wie Datenbanken, Nachrichtendiensten oder dem Dateisystem abhängig sind. Wenn Sie mit externen Ressourcen kommunizieren müssen, können Sie Mocks, Stubs oder Dummys verwenden.
Kompliziert wird es, wenn wir einige Integrationstests schreiben wollen. Es ist nicht schlimm, wenn wir ein paar Dienste zusammen testen wollen. Aber wenn wir Dienste testen müssen, die externe Ressourcen wie Datenbanken oder Nachrichtendienste nutzen, dann ist das ein Problem.
Um den Test durchführen zu können, müssen Sie die...
Vor vielen Jahren, als wir Integrationstests durchführen und z. B. Datenbanken verwenden wollten, hatten wir zwei Möglichkeiten:
Wir können eine Datenbank lokal installieren. Richten Sie ein Schema ein und verbinden Sie sich von unserem Test aus;
Wir können eine Verbindung zu einer bestehenden Instanz "irgendwo im Weltraum" herstellen.
Beide hatten Vor- und Nachteile. Aber beide führen zusätzliche Ebenen der Komplexität ein. Manchmal handelte es sich um technische Komplexität, die sich aus den Merkmalen bestimmter Tools ergab, z. B. die Installation und Verwaltung der Oracle-DB auf Ihrem lokalen Rechner. Manchmal war es eine Unannehmlichkeit im Prozess, z. B. müssen Sie mit dem Test einverstanden sein Team über die JMS-Nutzung... jedes Mal, wenn Sie Tests durchführen wollen.
Container als Retter in der Not
In den letzten 10 Jahren hat sich die Idee der Containerisierung in der Branche durchgesetzt. Daher war es naheliegend, die Container als Lösung für unser Integrationstestproblem zu wählen. Dies ist eine einfache, saubere Lösung. Sie führen einfach Ihren Prozess-Build aus und alles funktioniert! Sie können es nicht glauben? Werfen Sie einen Blick auf diese einfache Konfiguration eines Maven-Builds:
com.dkanejs.maven.plugins
docker-compose-maven-plugin
4.0.0
up
test-kompilieren
auf
${Projekt.basedir}/docker-compose.yml
true
down
Post-Integrationstest
abwärts
${project.basedir}/docker-compose.yml
true
Und die docker-compose.yml Die Datei sieht auch ziemlich gut aus!
Das obige Beispiel ist sehr einfach. Nur eine Postgres-Datenbank, pgAdmin und das ist alles. Wenn Sie
bash
$ mvn clean verify
dann startet das Maven-Plugin die Container und schaltet sie nach den Tests wieder aus. Die Probleme beginnen, wenn das Projekt wächst und unsere Compose-Datei ebenfalls wächst. Jedes Mal müssen Sie alle Container starten, und sie bleiben während des gesamten Builds aktiv. Sie können die Situation ein wenig verbessern, indem Sie die Konfiguration der Plugin-Ausführung ändern, aber das reicht nicht aus. Im schlimmsten Fall erschöpfen Ihre Container die Systemressourcen, bevor die Tests beginnen!
Und das ist nicht das einzige Problem. Sie können nicht einen einzigen Integrationstest von Ihrer IDE aus durchführen. Vorher müssen Sie die Container von Hand starten. Darüber hinaus wird der nächste Maven-Lauf diese Container abbauen (sehen Sie sich unten Ausführung).
Diese Lösung ist also wie ein großes Frachtschiff. Wenn alles gut funktioniert, dann ist alles in Ordnung. Jedes unerwartete oder ungewöhnliche Verhalten führt zu einer Art Katastrophe.
Testcontainer - Ausführen von Containern aus Tests
Aber was wäre, wenn wir unsere Container von Tests aus starten könnten? Diese Idee sieht gut aus, und sie wird bereits umgesetzt. TestcontainerDa wir gerade über dieses Projekt sprechen, hier eine Lösung für unsere Probleme. Nicht ideal, aber niemand ist perfekt.
Dies ist eine Java Bibliothek, die JUnit- und Spock-Tests unterstützt und eine leichtgewichtige und einfache Möglichkeit bietet, den Docker-Container auszuführen. Werfen wir einen Blick darauf und schreiben wir etwas Code!
Voraussetzungen und Konfiguration
Bevor wir beginnen, müssen wir unsere Konfiguration überprüfen. Test-Container brauchen:
Docker in Version v17.09,
Java Mindestversion 1.8,
Zugang zum Netzwerk, insbesondere zum docker.hub.
Weitere Informationen über die Anforderungen für bestimmte Betriebssysteme und CIs finden Sie unter in Dokumentation.
Nun ist es an der Zeit, ein paar Zeilen zu pom.xml.
org.testcontainers
testcontainers-bom
${testcontaines.version}
pom
import
org.postgresql
postgresql
Laufzeit
org.testcontainers
postgresql
test
org.testcontainers
junit-jupiter
test
Ich benutze Test-Container Version 1.17.3Sie können aber gerne die neueste Version verwenden.
Tests mit Postgres-Container
Der erste Schritt besteht darin, unsere Instanz eines Containers vorzubereiten. Sie können dies direkt im Test tun, aber eine unabhängige Klasse sieht besser aus.
public class Postgres13TC extends PostgreSQLContainer {
private static final Postgres13TC TC = new Postgres13TC();
private Postgres13TC() {
super("postgres:13.2");
}
public static Postgres13TC getInstance() {
return TC;
}
@Override
public void start() {
super.start();
System.setProperty("DB_URL", TC.getJdbcUrl());
System.setProperty("DB_USERNAME", TC.getUsername());
System.setProperty("DB_PASSWORD", TC.getPassword());
}
@Override
public void stop() {
// nichts tun. Dies ist eine gemeinsam genutzte Instanz. Lassen Sie die JVM diese Operation durchführen.
}
}
Zu Beginn der Tests erstellen wir eine Instanz von Postgres13TC. Diese Klasse kann Informationen über unseren Container verwalten. Das Wichtigste sind hier die Datenbankverbindungszeichenfolgen und die Anmeldeinformationen. Nun ist es an der Zeit, einen sehr einfachen Test zu schreiben.
Ich verwende hier JUnit 5. Anmerkung @Testcontainers ist ein Teil der Erweiterungen, die Container in der Testumgebung kontrollieren. Sie finden alle Felder mit @Container und Start- bzw. Stopp-Container.
Tests mit Spring Boot
Wie ich bereits erwähnt habe, verwende ich Spring Boot in diesem Projekt. In diesem Fall müssen wir ein wenig mehr Code schreiben. Der erste Schritt besteht darin, eine zusätzliche Konfigurationsklasse zu erstellen.
Diese Klasse überschreibt die vorhandenen Eigenschaften mit Werten aus der Prüfbehälter. Die ersten drei Eigenschaften sind Standard-Spring-Eigenschaften. Die nächsten fünf sind zusätzliche, benutzerdefinierte Eigenschaften, die verwendet werden können, um andere Ressourcen und Erweiterungen wie z.B. liquibase zu konfigurieren:
@SpringBootTest(webUmgebung = RANDOM_PORT) - markiert den Test als Spring Boot-Test und startet den Spring-Kontext.
@AutoConfigureTestDatabase(replace = NONE) - Diese Anmerkungen besagen, dass die Spring-Test-Erweiterung die Postgres-Datenbankkonfiguration nicht durch H2 in der Speicherkonfiguration ersetzen soll.
@Kontextkonfiguration(initializers = ContainerInit.class) - einen zusätzlichen Frühlingskontext Konfiguration, in der wir die Eigenschaften von Test-Container.
@Testcontainers - wie bereits erwähnt, steuert dieser Vermerk den Lebenszyklus des Containers.
In diesem Beispiel verwende ich reaktive Repositories, aber es funktioniert genauso mit gängigen JDBC- und JPA-Repositories.
Jetzt können wir diesen Test ausführen. Wenn es der erste Lauf ist, muss die Engine Images von docker.hub ziehen. Das kann einen Moment dauern. Danach werden wir sehen, dass zwei Container gelaufen sind. Einer ist Postgres und der andere ist Testcontainers Controller. Dieser zweite Container verwaltet die laufenden Container, und selbst wenn die JVM unerwartet stoppt, schaltet er die Container ab und räumt die Umgebung auf.
Wir fassen zusammen
Test-Container sind sehr einfach zu bedienende Tools, die uns helfen, Integrationstests zu erstellen, die Docker-Container verwenden. Das gibt uns mehr Flexibilität und erhöht die Entwicklungsgeschwindigkeit. Die ordnungsgemäße Einrichtung der Testkonfiguration verringert den Zeitaufwand für die Einarbeitung neuer Entwickler. Sie müssen nicht alle Abhängigkeiten einrichten, sondern nur die geschriebenen Tests mit ausgewählten Konfigurationsdateien ausführen.