Blog

Schnelle und einfache Integrationstests mit Docker und Overcast

Paul van der Ende

Paul van der Ende

Aktualisiert Oktober 22, 2025
9 Minuten

Herausforderungen bei Integrationstests

Angenommen, Sie schreiben einen MongoDB-Treiber für Java. Um zu überprüfen, ob alle implementierten Funktionen korrekt funktionieren, möchten Sie ihn idealerweise mit einem ECHTEN MongoDB-Server testen. Das bringt eine Reihe von Herausforderungen mit sich:

  • Mongo ist nicht in Java geschrieben, daher können wir es nicht einfach in unsere Java-Anwendung einbetten
  • Wir müssen MongoDB irgendwo installieren und konfigurieren und die Installation pflegen oder Skripte schreiben, um sie als Teil unseres Testlaufs einzurichten.
  • Jeder Test, den wir gegen den Mongo-Server laufen lassen, wird den Status ändern und die Tests können sich gegenseitig beeinflussen. Wir möchten unsere Tests so weit wie möglich isolieren.
  • Wir möchten unseren Treiber mit mehreren Versionen von MongoDB testen.
  • Wir möchten die Tests so schnell wie möglich ausführen. Wenn wir Tests parallel laufen lassen wollen, brauchen wir mehrere Server. Wie können wir diese verwalten?

Lassen Sie uns versuchen, diese Herausforderungen zu meistern. Zunächst einmal wollen wir nicht wirklich einen eigenen MonogDB-Treiber implementieren. Es gibt viele Implementierungen und wir werden den Mongo-Java-Treiber wiederverwenden, um uns darauf zu konzentrieren, wie man den Integrationstestcode schreiben würde.

Overcast und Docker

LogoWir werden Docker und Overcast verwenden. Wahrscheinlich kennen Sie Docker bereits. Dabei handelt es sich um eine Technologie zur Ausführung von Anwendungen in Software-Containern. Overcast ist die Bibliothek, die wir verwenden werden, um Docker für uns zu verwalten. Overcast ist eine Open-Source-Java-Bibliothek, die von XebiaLabs entwickelt wurde, um Ihnen zu helfen, Tests zu schreiben, die sich mit Cloud-Hosts verbinden. Overcast bietet Unterstützung für verschiedene Cloud-Plattformen, darunter EC2, VirtualBox, Vagrant, Libvirt (KVM). Kürzlich wurde von mir in Overcast Version 2.4.0 Unterstützung für Docker hinzugefügt.Overcast hilft Ihnen, Ihren Testcode von der Einrichtung des Cloud-Hosts zu entkoppeln. Sie können einen Cloud-Host mit seiner gesamten Konfiguration getrennt von Ihren Tests definieren. In Ihrem Testcode beziehen Sie sich dann nur auf eine bestimmte Overcast-Konfiguration. Overcast kümmert sich um das Erstellen, Starten und Bereitstellen dieses Hosts. Wenn die Tests abgeschlossen sind, wird es den Host auch wieder abbauen. In Ihren Tests werden Sie Overcast verwenden, um den Hostnamen und die Ports für diesen Cloud-Host abzurufen, damit Sie sich mit ihm verbinden können, denn diese werden normalerweise dynamisch ermittelt. Wir werden Overcast verwenden, um Docker-Container zu erstellen, auf denen ein MongoDB-Server läuft. Overcast wird uns dabei helfen, den vom Docker-Host dynamisch bereitgestellten Port abzurufen. Der Host wird in unserem Fall immer der Docker-Host sein. Docker läuft in unserem Fall auf einem externen Linux-Host. Overcast verwendet eine TCP-Verbindung für die Kommunikation mit Docker. Wir ordnen die internen Ports einem Port auf dem Docker-Host zu, um ihn extern verfügbar zu machen. MongoDB läuft intern auf Port 27017, aber Docker ordnet diesen Port einem lokalen Port im Bereich 49153 bis 65535 (von Docker definiert) zu.

Einrichten unserer Tests

Lassen Sie uns beginnen. Zunächst benötigen wir ein Docker-Image mit installiertem MongoDB. Dank der Docker-Community ist dies so einfach wie die Wiederverwendung eines der bereits vorhandenen Images aus dem Docker Hub. Die ganze harte Arbeit der Erstellung eines solchen Images ist bereits für uns erledigt, und dank Containern können wir es auf jedem Host ausführen, der Docker-Container ausführen kann. Wie konfigurieren wir Overcast für die Ausführung des MongoDB-Containers? Dies ist unsere minimale Konfiguration, die wir in einer Datei namens overcast.conf ablegen:

[text]
mongodb {
dockerHost="https://xebia.com/blog:2375"
dockerImage="mongo:2.7"
exposeAllPorts=true
remove=true
command=["mongod", "--smallfiles"]
}
[/text]

Das ist alles! Der dockerHost ist als localhost mit dem Standardport konfiguriert. Dies ist der Standardwert und Sie können ihn weglassen. Das Docker-Image namens mongo Version 2.7 wird automatisch aus der zentralen Docker-Registry gezogen. Wir setzen exposeAllPorts auf true, um Docker mitzuteilen, dass es alle vom Docker-Image freigegebenen Ports dynamisch zuordnen muss. Wir setzen remove auf true, um sicherzustellen, dass der Container automatisch entfernt wird, wenn er gestoppt wird. Beachten Sie, dass wir den Standardbefehl zum Starten des Containers außer Kraft setzen, indem wir einen zusätzlichen Parameter "--smallfiles" übergeben, um die Testleistung zu verbessern. Für unser Setup ist das alles, was wir brauchen, aber Overcast bietet auch Unterstützung für die Definition statischer Port-Zuordnungen, das Setzen von Umgebungsvariablen usw. Werfen Sie einen Blick in die Overcast-Dokumentation, um weitere Einzelheiten zu erfahren. Wie verwenden wir diesen Overcast-Host in unserem Testcode? Werfen wir einen Blick auf den Testcode, der den Overcast-Host einrichtet und den mongodb-Client instanziiert, der von jedem Test verwendet wird. Der Code verwendet die TestNG @BeforeMethod und @AfterMethod Annotationen. [java] private CloudHost itestHost; private Mongo mongoClient; @BeforeMethod public void before() throws UnknownHostException { itestHost = CloudHostFactory.getCloudHost("mongodb"); itestHost.setup(); String host = itestHost.getHostName(); int port = itestHost.getPort(27017); MongoClientOptions Optionen = MongoClientOptions.builder() .connectTimeout(300 * 1000) .build(); mongoClient = new MongoClient(new ServerAddress(host, port), options); logger.info("Mongo-Verbindung: " + mongoClient.toString()); } @NachMethode public void after(){ mongoClient.close(); itestHost.teardown(); } [/java] Es ist wichtig zu verstehen, dass der mongoClient das zu testende Objekt ist. Wie bereits erwähnt, haben wir uns diese Bibliothek ausgeliehen, um zu demonstrieren, wie man eine solche Bibliothek integriert testen kann. Der itestHost ist der Overcast CloudHost. In before() instanziieren wir den CloudHost mit Hilfe der CloudHostFactory. setup() zieht die erforderlichen Images aus der Docker-Registry, erstellt einen Docker-Container und startet diesen Container. Wir erhalten den Host und den Port vom itestHost und verwenden sie, um unseren Mongo-Client zu erstellen. Beachten Sie, dass wir in den Verbindungsoptionen ein hohes Verbindungs-Timeout eingestellt haben, um sicherzustellen, dass der Mongodb-Server rechtzeitig gestartet wird. Besonders beim ersten Durchlauf kann es einige Zeit dauern, bis die Bilder abgerufen werden. Sie können die Bilder natürlich auch schon vorher abrufen. In der @AfterMethod schließen wir einfach die Verbindung mit mongoDB und fahren den Docker-Container herunter.

Einen Test schreiben

Das Vorher und Nachher wird für jeden Test ausgeführt, so dass wir für jeden Test einen völlig sauberen Mongodb-Server erhalten, der auf einem anderen Port läuft. Dadurch werden unsere Testfälle vollständig isoliert, so dass sich keine Tests gegenseitig beeinflussen können. Es steht Ihnen frei, Ihre eigene Teststrategie zu wählen. Die gemeinsame Nutzung eines Cloud-Hosts durch mehrere Tests ist ebenfalls möglich. Werfen wir einen Blick auf einen der Tests, die wir für den Mongo-Client geschrieben haben: [java] @Test public void shouldCountDocuments() throws DockerException, InterruptedException, UnknownHostException { DB db = mongoClient.getDB("mydb"); DBCollection coll = db.getCollection("testCollection"); BasicDBObject doc = new BasicDBObject("name", "MongoDB"); for (int i=0; i < 100; i++) { WriteResult writeResult = coll.insert(new BasicDBObject("i", i)); logger.info("writing document " + writeResult); } int count = (int) coll.getCount(); assertThat(count, equalTo(100)); } [/java] Auch ohne Kenntnisse von MongoDB sollte dieser Test nicht allzu schwer zu verstehen sein. Er erstellt eine Datenbank, eine neue Sammlung und fügt 100 Dokumente in die Datenbank ein. Schließlich überprüft der Test, ob die Methode getCount die richtige Anzahl von Dokumenten in der Sammlung zurückgibt. Viele weitere Aspekte des Mongodb-Clients können auf diese Weise in zusätzlichen Tests getestet werden. In unserem Beispiel-Setup haben wir zwei weitere Tests implementiert, um dies zu demonstrieren. Unser Beispielprojekt enthält 3 Tests. Wenn Sie die 3 Beispieltests nacheinander ausführen (unter der Annahme, dass das Mongo-Docker-Image gezogen wurde), werden Sie feststellen, dass es nur wenige Sekunden dauert, bis alle Tests ausgeführt sind. Das ist extrem schnell.

Testen mit mehreren MongoDB-Versionen

Außerdem möchten wir alle unsere Integrationstests mit verschiedenen Versionen des MongoDB-Servers durchführen, um sicherzustellen, dass es keine Regressionen gibt. Mit Overcast können Sie mehrere Konfigurationen definieren. Lassen Sie uns die Konfiguration für zwei weitere Versionen von MongoDB hinzufügen:

[text]
defaultConfig {
dockerHost="https://xebia.com/blog:2375"
exposeAllPorts=true
remove=true
command=["mongod", "--smallfiles"]
}
mongodb27=${defaultConfig}
mongodb27.dockerImage="mongo:2.7"
mongodb26=${defaultConfig}
mongodb26.dockerImage="mongo:2.6"
mongodb24=${defaultConfig}
mongodb24.dockerImage="mongo:2.4"
[/text]

Die Standardkonfiguration enthält die Konfiguration, die wir bereits gesehen haben. Die anderen drei Konfigurationen gehen von der defaultConfig aus und definieren eine bestimmte MongoDB-Image-Version. Lassen Sie uns auch unseren Testcode ein wenig ändern, damit die Overcast-Konfiguration, die wir im Test-Setup verwenden, von einem Parameter abhängt:

[java]
@Parameters("overcastConfig")
@BeforeMethod
public void before(String overcastConfig) throws UnknownHostException {
itestHost = CloudHostFactory.getCloudHost(overcastConfig);
[/java]

Hier haben wir die Funktion für parametrisierte Tests von TestNG verwendet. Wir können nun eine TestNG-Suite definieren, um unsere Testfälle zu definieren und festzulegen, wie sie in den verschiedenen Overcast-Konfigurationen ablaufen sollen. Werfen wir einen Blick auf die Definition unserer TestNG-Suite:

[xml]
<suite name="MongoSuite" verbose="1">
<test name="MongoDB27tests">
<parameter name="overcastConfig" value="mongodb27"/>
<classes>
<class name="mongo.MongoTest" />
</classes>
</test>
<test name="MongoDB26tests">
<parameter name="overcastConfig" value="mongodb26"/>
<classes>
<class name="mongo.MongoTest" />
</classes>
</test>
<test name="MongoDB24tests">
<parameter name="overcastConfig" value="mongodb24"/>
<classes>
<class name="mongo.MongoTest" />
</classes>
</test>
</suite>
[/xml]

Mit dieser Testsuite-Definition definieren wir 3 Testfälle, die eine unterschiedliche Overcast-Konfiguration an die Tests weitergeben. Die Overcast-Konfiguration und die TestNG-Konfiguration ermöglichen es uns, extern zu konfigurieren, gegen welche Mongodb-Versionen wir unsere Testfälle ausführen möchten.

Parallele Testausführung

Bis zu diesem Zeitpunkt werden alle Tests sequentiell ausgeführt. Aufgrund der dynamischen Natur von Cloud-Hosts und Docker steht uns nichts im Wege, mehrere Container gleichzeitig auszuführen. Lassen Sie uns die TestNG-Konfiguration ein wenig ändern, um parallele Tests zu ermöglichen:

[xml]
<suite name="MongoSuite" verbose="1" parallel="tests" thread-count="3">
[/xml]

Diese Konfiguration führt dazu, dass alle 3 Testfälle aus unserer Testsuite-Definition parallel ausgeführt werden (mit anderen Worten unsere 3 Overcast-Konfigurationen mit verschiedenen MongoDB-Versionen). Lassen Sie die Tests jetzt von IntelliJ aus laufen und sehen Sie, ob alle Tests erfolgreich sind:

Bildschirmfoto 2014-10-08 um 8.32.38 PM

Wir sehen 9 ausgeführte Tests, denn wir haben 3 Tests und 3 Konfigurationen. Alle 9 Tests haben bestanden. Die gesamte Ausführungszeit betrug weniger als 9 Sekunden. Das ist ziemlich beeindruckend! Während der Testausführung können wir sehen, wie Docker mehrere Container startet (siehe nächster Screenshot). Wie erwartet werden 3 Container mit unterschiedlichen Image-Versionen gleichzeitig ausgeführt. Außerdem werden in der Spalte "PORTS" die dynamischen Port-Zuordnungen angezeigt:

Bildschirmfoto 2014-10-08 um 8.50.07 PM

Das war's!

Zusammenfassung

Zusammengefasst sind die Vorteile der Verwendung von Docker mit Overcast für Integrationstests folgende:

  1. Minimale Einrichtung. Zur Durchführung der Tests ist nur ein Docker-fähiger Host erforderlich.
  2. Sparen Sie Zeit. Dank der Docker-Community ist nur ein minimaler Aufwand für die Konfiguration und Einrichtung der Infrastruktur erforderlich, um die Integrationstests durchzuführen.
  3. Isolierung. Alle Tests können in einer isolierten Umgebung ausgeführt werden, so dass sich die Tests nicht gegenseitig beeinflussen.
  4. Flexibilität. Verwenden Sie mehrere Overcast-Konfigurationen und parametrisierte Tests zum Testen mit mehreren Versionen.
  5. Geschwindigkeit. Der Docker-Container startet sehr schnell, und mit overcast und testng können Sie die Tests sogar parallelisieren, indem Sie mehrere Container gleichzeitig ausführen.

Der Beispielcode für unser Integrationstestprojekt ist hier verfügbar. Sie können Boot2Docker verwenden, um einen Docker-Host auf Mac oder Windows einzurichten. Viel Spaß beim Testen! Paul van der Ende

Hinweis: Aufgrund eines Fehlers im Gradle Parallel Test Runner kann es zu diesem zufälligen Fehler kommen, wenn Sie den Beispiel-Testcode selbst ausführen. Sie können diesen Fehler umgehen, indem Sie die Parallelität deaktivieren oder einen anderen Testausführer wie IntelliJ oder Maven verwenden.

 

Verfasst von

Paul van der Ende

Product developer at Xebialabs. Continuous Delivery Expert.

Contact

Let’s discuss how we can support your journey.