Tools für End-to-End-Tests

Hallo zusammen,

nachdem ich schon gefühlt ewigkeiten nichts mehr geschrieben habe, wollte ich mal eine Diskussion starten und fragen, mit welchen Werkzeugen ihr End-to-End-Tests schreibt.

Konkret geht es mir momentan um eine Springanwendung, die verschiedene Systeme anspricht, aber selbst keine nennenswerte Oberfläche hat. Daten kommen von Sensoren über Netzwerkschnittstellen in die Anwendung und werden in verschiedenen Datenbanksystemen persistiert. Weiterhin gibt es HTTP-Endpunkte (~REST), die JSON-Daten liefern. Das ganze läuft in einem eingebetteten Tomcat.

Grundsätzlich erscheint mir JUnit für die Aufgabe durchaus geeignet. Abweichend vom Unittesting-Vorgehen dürfte die Test-Fixture (Anwendungsstart und Datenbankinitialisierung) aber nicht vor jedem einzelnen Test ausgeführt werden, weil ansonsten zuviel Zeit verloren geht und die Tests müssten in Gruppen ausgeführt werden.

Bevor ich weiter Energie in die Recherche stecke, ob sich JUnit auf diese Weise sinnvoll nutzen lässt, würde ich aber gerne eure Anregungen und Erfahrungen aufnehmen und ggf. ein geeigneteres Toolset evaluieren.

Viele Grüße
Christian

ich gestalte sowas alles über die normale Unittest Geschickte von Sprint + Junit

Du kannst den Tests ja sagen welche deine Daten versauen und welche nichts ändern, so sollte das zügiger laufen ( @DirtiesContext)

Ok, meinem Verständnis nach bewegt man sich da aber noch im Bereich von Integrationstests. D. h., dass du den Servlet Container oder aber auch eine reale Datenbank nicht mit testest, oder? Mit bspw. Spring MockMvc werden ja keine echten Requests gesendet, sondern Mock-Requests direkt in die Anwendung gegeben, ohne dass der Servlet Container die Anfragen vorher prozessiert hat. Insbesondere ist es schwierig, die reale Konfiguration korrekt darzustellen. Also inklusive aller registrierten Filter.

Mir schwebt vor, die Anwendung real zu starten (das geht aufgrund der Konfiguration als Embedded-Anwendung ganz gut und auch parallel, da zufällige Ports verwendet werden können) und dann gegen containerisierte Datenbanken laufen zu lassen, die für die Test-Cases präparierte Daten enthalten. Dann sollen für dieses Szenario die Tests ausgeführt werden und dann soll das ganze wieder abgebaut werden. Die Requests können dann ganz klassisch mit dem Spring WebClient oder Squareup OkHttp an die Anwendung geschickt und mit den regulären Werkzeugen ausgewertet werden.

Auf der anderen Seite ist es schon ziemlich gut, nur den Persistenzlayer und den Servlet Container wegzulassen. Es fehlt aber noch die letzte Meile.

also ich benutz das im Spring Boot Umfeld, Klar es wird gefaked. Aber der Controller erhält normal den Request rein. Was mit der DB ist ist deine Sache, ich würde eine möglichst nahe Datenbank dahinter hängen zb in Memory oder so. Wenns komplexer wird dann ruhig eine dedizierte für die Tests.

Klar du kannst sie auch normal starten und dann zb mit cypress Testfälle schreiben oder nen eigenes Tool was die Testfälle abbildet. Aber von so weit außen ists schwer etwas zu faken. Gerade um so mehr externe Abhängigkeiten du hast.
Auch der Zustand der Datenbank muss ja für jeden Durchlauf einen gewissen Zustand haben.

Am Ende hängt es in meinen Augen von deinem Anwendungsfall ab, ich benutz idr wie gesagt einfach die normalen Tests wo ich im Testcase halt den ganzen Kram starten lasse und dagegen feuer. So kann ich auch je nach darf mal paar Sachen mocken. Weil du schreibst ja oben was von Sensoren, da kannst du deren Aktionen bequem mocken (wenn die nicht auch per REST kommen) und du kannst vorallem in dem Zuge bequem die DB steuern

Hallo,

Eine Verwendung einer JUnit Jupiter Extension (aka JUnit 5) ist hier extrem hilfreich… Auf der FOSDEM 2021 habe ich eine Grundlegenden Ansatz gezeigt

Das Ganze in etwas ausgeweiteter Form wird in Projekten eingesetzt.

Vollständig mit Datenbank im Container (Oracle), verschiedenen anderen Diensten (JMS, http Zugriffen externer Art) usw.

Die Verwendung einer In Memory DB (z.B. H2) kommt irgendwann an Grenzen (haben wir auch schon durchgespielt…) seit ca. 1.5 Jahren weg von in Memory… selbst für Integration tests…

Gruß
Karl Heinz

Danke für deine die Anregungen und Erfahrungsberichte :slight_smile:

In Gedanken geht es mir erstmal nur um die Kernkomponente der Anwendung, die tatsächlich nur über das Netzwerk kommuniziert (REST-Schnittstellen und MQTT), ein Mocken von physikalischen Schnittstellen ist (hier) nicht erforderlich. Ich habe aber auch Module, die per serieller USB-Schnittstelle Daten lesen. Dort ist das dann schon interessant. Ich denke, das lässt sich im Gegensatz zu Netzwerkverkehr wirklich schwer simulieren.

Für viele, wenn nicht die meisten, Testfälle wird es wahrscheinlich auch reichen, die Anwendung ohne Servlet Container und reale Datenbanken zu testen. Wenn es ans Testen des Gesamtsystemes geht, fehlt aber eben noch ein Stückchen.

Das was du schreibst, @kama, klingt auf jeden Fall ziemlich spannend und nach genau dem, was ich suche. Ich werd mir deinen Talk mal ansehen. JUnit Jupiter ist auf jeden Fall als Testframework im Einsatz, was ja schonmal passt.

sorry für Thread kapern :smiley:
wenn du keine inmemory mehr nutzt, wie machst du es dann mit der DB? Sorgst du dafür dass deine Tests damit klar kommen dass die DB irgendeinen Zustand hat oder wie machst du das?

@eagleeye das ist genau die Diskussion, die ich haben wollte, von daher sehe ich das nicht als Thread kapern an :wink:

Ich habe da verschiedene Ideen, wie das mit einer persistenten DB gelöst werden könnte. Zum einen gibt es ja den Transaktionsansatz von Spring (siehe hier: Testing ) und zum anderen könnte man das über Container lösen, die bei Bedarf entsprechend mit Daten befüllt und dann wieder weggeworfen werden.

Die Datenbank wird leer mit allen Tabellen usw. aufgesetzt und als Container zur Verfügung gestellt… der E2E führt dann den gesamten Prozess zum Auffüllen der Daten in DB durch…

Für die Integration Tests gehen den gleichen Weg …clean setup (also Leere DB; natürlich mit allen Tabellen und Initialdaten aufgesetzt die Nötig sind)… im Rahmen des IT’s werden dann u.U. per SQL (zusätzliche Testdaten eingespielt) speziell für den entsprechenden IT. Jeder IT’s läuft für sich alleine (Verwendung eines eigenen DB Containers)…somit keine Beeinflussung untereinander… IT’s laufen parallelisiert…

Der Zustand ist im DB Container immer nach dem Initial Setup…

Gruß
Karl Heinz

ich habs jetzt nur überflogen, aber die Transaktionen setzen ja nur die aktuellen Änderungen ggf zurück aber dadurch bekommst du deine DB ja nicht in einen definierten Zustand und ich frage mich wie das mit normalen Transaktionen die du so schon in deinen Repos & Co zusammen passen wird (nicht alle DBs unterstützen Named Transaktionen)

Container wären def ne lösung

ah ok interessant :+1:

Hallo,

der Punkt ist, dass wir auch noch andere Komponenten in Containern im Rahmen des E2E starten und nutzen…Somit könnte man meiner Meinung nach auch einen Weg finden Daten via USB zu schicken…oder eventuell eine Zwischenschicht wegzulassen und dann direkt an die Anwendung zu schicken… (aus einem Container)…

Gruß
Karl Heinz

Hallo,

wir nutzen die DB innerhalb einen Docker Containers…die Anwendung wird gestartet via java -jar anwendung.jar mit entsprechender Verbindungskonfigration zum Container…nach dem E2E wird der Container wieder weg geschmissen…einfach wieder den Container starten und schon habe ich wieder einen sauberen Stand…

Das ist genau der Weg den wir auch im IT gehen…nach dem Test wird einfach der Docker container weg geschmissen und dann wird für den nächsten IT eben wieder ein neuer gestartet…

Mit entsprechender Konfiguration ist das mit den IT’s und den E2E auch auf einer CI/CD Umgebung kein Problem… die Ports für die Anwendung muss man halt vorgeben… (selektieren lassen) damit es nicht zu Kollisionen kommt wenn unterschiedliche Branches auf der gleichen Maschine bauen.

Der Code GitHub - khmarbaise/fosdem2021: Content of the talk for FOSDEM 2021

Gruß
Karl Heinz

2 Likes

Das klingt wirklich genau nach dem, was ich suche. Ich hatte bspw. auch diesen Blogeintrag gefunden, der vom Ansatz her aber deutlich sperriger wirkt: Docker with Gradle: Integration testing using containers

Die ganze Anwendung „lebt“ sowieso in einer dockerisierten Umgebung, auch das passt daher perfekt.

Das Portproblem sollte doch bei einer Buildinfrastruktur unter Nutzung von docker in docker nicht existieren, oder? Denn dann hat man ein isoliertes Netzwerk. Ansonsten kann man ja auch immer noch zufällige Ports nutzen.

na dein Code musst doch die DB finden und wenn sie doppelt starten kanns zu Problemen führen

In der Docker-Umgebung kannst du mehrere Anwendungen (in unterschiedlichen Containern) auf dem gleichen Port lauschen lassen, sofern sie unterschiedliche virtuelle Netze nutzen. Die verschiedenen Container sehen sich gegenseitig und können sich auf den Standardports ansprechen. Unterschiedliche Netze beeinflussen sich aber nicht.

ja das fällt in „kann zu Problemen führen“ und nicht muss wenn man es gescheit löst :wink:

1 Like

Das Port Problem hängt davon ab, wie ich meine zu testende Anwendung starte. Starte ich die innerhalb eines Containers dann existiert das Problem nicht…Docker vergibt die Ports…

Bei uns war aber der Port die Zielumgebung keine Docker Umgebung also muss der E2E genauso laufen (selbst selektieren der Port für die Anwendung; alles andere DB, andere Komponenten laufen in Docker (via testcontainers) da gibt es kein Problem.

Zufällige Ports funktionieren nicht 100% verlässlich (Die Verwendung von SocketUtils auch nicht) … Ich suche noch einen einfachen Weg mit via Testconainers oder mithilfe einer Java lib, die direkt mit dem Docker Daemon spricht, einen Port vergeben zu lassen…

Weiterhin ist DoD das Problem bzgl. Testconainers mit Worm-Hole Ansatz zu beachten… (wg. Durchgriff auf die sockets etc.) ist aber der Doku zu Testcontainers beschrieben…

Der dort beschriebene Ansatz versucht mal wieder nach Gradle Mannier alles in den Build zu legen als es da zu machen, wo es hingehört nämlich in den Test Code… (IT/E2E Test code).

Wenn ich eine DB starte via Testcontainers, kann ich mir den Port einfach holen und an meine Anwendung (z.B. spring boot per Command Line übergeben)…