19. Dezember 2019 in code IT

Integrationstests
von MicroServices in Java

Im meinem ersten Artikel habe ich euch Vor- und Nachteile des Test Driven Developments vorgestellt und Möglichkeiten aufgezeigt, welche diese Art der Test-Skalierung für die Softwareentwicklung mit sich bringt. Heute möchte ich mit euch Integrationstests von MircoServices in Java näher beleuchten.

Softwaretests sind zentrale Bestandteile einer kontinuierlichen Qualitätssicherung. Sie lassen sich unterteilen in Unit-, Integrations- und E2E-Tests. Während bei Unittests isolierte Komponenten – das heißt Methoden oder Klassen – getestet werden, wird bei Integrationstests das Zusammenspiel dieser Komponenten geprüft. Martin Fowler beschreibt dies mit den Worten: „Integration tests determine if independently developed units of software work correctly when they are connected to each other“.

Zum Beispiel könnte ein MicroService integriert getestet werden, der eine Datenbank mit den aktuellen Nachrichten ausliest. Dabei besteht der MicroService technisch aus drei Teilen:

  1. Der Ressource, welche die Anfragen entgegennimmt und beantwortet
  2. Dem Service, welcher die Verarbeitungslogik enthält
  3. Dem Client, der die Beschaffung der Daten erledigt

Auf die Fachlichkeit der Nachrichtenauswertung übertragen, gibt es also den Client, der für das Lesen und Schreiben in die Datenbank zuständig ist, den Service, der implementiert wie die Nachrichten ausgewertet werden sollen und der Ressource, die entscheidet, welche Antwort eine entgegengenommene Nachrichtenanfrage bekommt. Diese Komponenten werden beim IntegrationTest in ihrem Zusammenspiel getestet. Dabei können die Tests auch über Systemgrenzen hinweg gehen, wobei oft auch ein TestDouble zum Einsatz kommt.

Den Monolithen mit Middleware erweitern

MicroServices werden eingesetzt, um Monolithen zu zerteilen oder um diese zu erweitern.Werden sie zur Erweiterung eines bestehenden Backends eingesetzt, dann bilden sie eine Middlewarezwischen dem Back- und dem Frontend. Das hat zum einen den Vorteil, dass der historisch gewachsene Monolith bestehen bleiben kann. Zum anderen kann ein zeitgemäßes webbasiertes Frontend mit Technologien wie Angular oder React erstellt werden. Das Frontend fragt dann bei der Middleware die darzustellenden Daten an. Mittels Web-Schnittstellen wird die Benutzeroberfläche mit den Inhalten versorgt. Dabei spielt es keine Rolle, ob die Inhalte in einer Datenbank gehalten, durch einen UpLoad hochgeladen, als Pdf bereitgestellt oder in einer sonstigen Repräsentation vorliegen. Die Middleware beruht auf WebServices als zentrale Technologie. Dabei kann die Anzahl der Schnittstellen, welche die Middleware bereitstellt, rasch anwachsen. Das führt dazu, dass manuelle Tests der WebServices schnell zeitaufwändig werden. Besser eignen sich bei MircoServices somit automatisierte Integrationstests.

Eine Open-Source-Bibliothek, die Integrationstests von Docker-Anwendungen vereinfacht, ist TestContainers. Das von Richard North geführte Projekt ist ein wichtiger Beitrag zum Testen von aktuellen Webanwendungen.

Mit Integrationstests die funktionalen Komponenten testen

Integrationstests werden in geringerer Zahl als Unittests erstellt, da sie für die Ausführung ein Vielfaches an Zeit benötigen. Es können beispielsweise Integrationstests nur für den sogenannten Happy-Path erstellt und Fehlerfälle sowie Edge-Cases ausgespart werden.

Integrationstests werden in der Regel auch nicht so häufig ausgeführt wie Unittests. Erfolgreich durchgeführte Integrationstests sollten nicht Voraussetzung für einen erfolgreichen Build sein. Sie müssen daher separat ausgeführt werden.

Durch Unittests wird das Verhalten der technischen Programmeinheiten geprüft. Durch automatisierte Integrationstests werden die funktionellen Bausteine der Software validiert. Somit werden voneinander abgrenzbare Systemkomponenten getestet, die jede für sich eine Funktion für die Softwareanwendung übernimmt. Durch Integrationstests werden aber auch indirekt system- oder modulweite Konfigurationen getestet. Der Datenbankzugriff kann nur funktionieren, wenn beispielsweise Server- oder andere Konfigurationen der Laufzeitumgebung stimmen.

E2E-Tests sichern die Akzeptanz der Nutzer. Sie stellen sicher, dass der Anwender seine Ziele erreicht. Werden Webservices einer Middleware getestet, dann muss der Anwendungsserver zunächst gestartet werden. Das ist auch der Hauptgrund für die längere Ausführungszeit. Besonders das Starten der Anwendung stellte eine zentrale Herausforderung beim Integrationstest dar. In Docker containerisierte Anwendungen können allerdings einfacher in einer Testumgebung bereitgestellt werden.

Integrationstests von MicroServices mit der TestContainers-Library

Im Folgenden möchte ich euch skizzieren, wie Integrationstests von MicroServices implementiert werden können. Dazu nutze ich die Integrationstest-Bibliothek TestContainers. Obwohl die Website selbst nicht von MicroService-Tests spricht, lassen sich diese ebenfalls perfekt für diesen Fall implementieren. Das Docker Compose Modul der Bibliothek, dient zum Starten von Docker-Services. Das docker-compose.yml kann vereinfacht, wie in Code-Beispiel 1 aussehen:

Dateiname: docker-compose.yml
version: '2'
services:
  app:
    image: open-liberty
    volumes:
      - ./webServices.war:/config/dropins/
  db:
    image: postgres
    volumes:
      - db-data:/var/lib/db
volumes:
  db-data: {}

In der DockerCompose-Konfiguration werden zwei Services angelegt. Der app-Service enthält eine Anwendung webServices.war, der db-Service ist eine relationale Datenbank. Die Anwendung, die im app-Container auf dem Application-Server deployed wird, enthält WebServices.

Code-Beispiel 2 ist eine einfache WebService-Klasse, wie ihr sie in Java mit JAX-RS ganz einfach implementieren könnt:

Dateiname: WebService.java
@ApplicationScoped
@Path("/services")
public class WebService {
    @GET
    @Path("hello")
    public Response sayHello(){
        return Response.ok("Hello WebService!").build();
    }
}

Der WebService kann jetzt zum Beispiel mit Postman oder mit cUrl von der Kommandozeile aufgerufen werden. Besser wird jedoch eine JUnit-Testklasse implementiert, die mit Hilfe der TestContainers-API den Service automatisiert testet. Dazu kann eine Buildkonfiguration zur Ausführung der Testsuite mit einem Tool, wie Maven oder Gradle, erstellt werden.

In Code-Beispiel 3 findet sich eine Implementierung, um den Erfolgsfall des WebServices zu testen. Es wird überprüft, ob der Client eine Antwort mit Statuscode 200 erhält:

Dateiname: WebServiceIntegrationTest.java
public class WebServiceIntegrationTest {
    private String middlewareUrl;
    @ClassRule
    public static DockerComposeContainer environment =
            new DockerComposeContainer(new File("./docker-compose.yml"))
                .withExposedService("app_1", SERVICE_PORT, Wait.forListeningPort());
    @Before
    public void setUp(){
        middlewareUrl = environment.getServiceHost("app_1", SERVICE_PORT)
                        + ":" + environment.getServicePort("app_1", SERVICE_PORT);
    }
    @Test
    public void shouldSayHello(){
        WebTarget target = middlewareUrl;
        Response response = target.path("services/hello")
                                .request(MediaType.APPLICATION_JSON)
                                .get();
        assertThat(response.getStatus() isEqualTo(200));
    }
}

Fazit

Middleware und zeitgemäße Frontends passen gut zusammen. WebServices  werden dabei als zentrale Technologie der Middleware eingesetzt. Dies erschwert im ersten Schritt vor allem Integrationtests.

In meinem Artikel habe ich euch gezeigt, wie sich diese Schwierigkeit mit der TestContainers-Library einfach überwinden lässt und wie ihr die einzelnen WebService als funktionale Komponenten integriert testen könnt. Auch wenn Integrationstests seltener sind als Unittests und weniger häufig zur Ausführung kommen, sind sie dennoch ein probates Mittel, um den Entwicklungsprozess effizienter zu gestalten.

Artikel von Mischa Siebert, Consultant bei objective partner

Mischa Siebert, Entwickler bei objective partner beschäftigt sich mit Cloud und Serverless Computing

Das wird Dich sicherlich auch interessieren:

Zurück zur Übersicht