Testen von Mule-ESB-Applikationen (Teil 3/3): System-End-To-End-Tests mit Docker

Keine Kommentare

Abstrakt

Im allgemeinen Konsens wird das Testen von Software als integraler Bestandteil des Software-Entwicklungsprozesses gesehen. Tests sollten in allen Phasen der Softwareentwicklung eingesetzt werden: von Unit- bis zu Akzeptanztests. Vor allem im Software Engineering bilden zusammenhängende und automatisierte Tests ein Sicherheitsnetz gegen regressive und inkompatible Änderungen.

In Integrationsprojekten mit Mule ESB sind diese Aspekte auch von Belang. Komponenten in Mule Flows, die Flows selber und deren Integration müssen intensiv getestet werden.

Dieser Artikel ist der letzte in einer Reihe von Artikeln zum Thema Testen von Mule-ESB-Projekten auf allen Ebenen (Teil 1, Teil 2). Der Fokus in diesem Artikel liegt auf auf einem übergreifenden System-End-to-End-Test in einem Mule-Projekt, welches sich durch das Aufsetzen der Infrastruktur mit dem ESB und einem Mock Server mit der Hilfe von Docker auszeichnet.

Infrastruktur

Um einen System-End-to-End-Test für eine Mule-Applikation durchzuführen, benötigen wir drei Systemkomponenten:

  • App: Zuerst brauchen wir die zu testende Mule-Applikation.
  • Tester: Der Tester ist für das Testen der Anwendung verantwortlich. Das Testen kann durch einfache Tests, die die API-Aufrufe durchführen und das Ergebnis verifizieren, oder durch Test-Tools, welche komplexe orchestrierte Aufrufe durchführen, wie z. B. JMeter, durchgeführt werden.
  • Mock: Zusätzlich brauchen wir einen oder mehrere System-Mocks, die Systeme darstellen, von denen die Anwendung abhängig ist. Mountebank kann solch eine Funktionalität bereitstellen.

Solch ein System-End-to-End-Test-Setup würde wie folgt aussehen:

system ed-to-end

Docker

Docker ist eine Open-Source-Technologie, die Virtualisierung von Maschinen in isolierten Containern auf einem Betriebssystem ermöglicht. Durch die Verwendung von Linux-Technologien wie Cgroups und Namespaces erlaubt es das schnelle und ressourceneffiziente Erzeugen von Containermaschinen, womit man eine portable, nachvollziehbare und konsistente Infrastruktur aufbauen kann. Dies ist vor allem für die Erstellung, Durchführung und Nachvollziehbarkeit von Testszenarien, die Infrastruktur-basiert sind, ein großer Vorteil.

Um eine bessere Integration solch eines System-End-to-End-Tests in eine Continuous Integration Pipeline sicherzustellen, ist die Verwendung von Container-Technologie von Vorteil. Die Verwendung von Docker zum Beispiel erlaubt das beschleunigte Starten einer isolierten Mule-Instanz mit der zu testenden Anwendung und dem Mock Server.

Zu testende Anwendung

Wir nehmen das folgende einfache Szenario als Beispiel. Eine Mule-Applikation stellt eine REST-API auf Port 8080 zur Verfügung und ruft intern einen REST-Backend-Service auf Port 9000 auf. Solch eine Applikation könnte wie folgt aussehen:

Bildschirmfoto 2015-06-09 um 22.24.31

In diesem Beispiel sehen wir einen HTTP-Endpoint, der auf Port 8080 lauscht und der alle Anfragen an den REST-API-Router weiterleitet. Die Anfrage an /myResource wird im unteren Sub Flow landen und einen auswärtigen HTTP-Aufruf zum Server auf Port 9000 auslösen. Das Ergebnis wird in einen String transformiert und an den Aufrufer zurückgegeben. Im Falle von Exceptions greift eine Exception-Strategie und wird ein entsprechend passendes Ergebnis zurückliefern.

Wir nehmen an, wir haben unsere Mule-Anwendung bereits als einzelne Applikation in einem Docker-Container vorliegen, wie in diesem Blog-Artikel beschreiben.

Mock Server

Um der Mule-Applikation Aufrufe zu einem potenziellen Backend-Service in einem System-End-to-End-Szenario zu ermöglichen, kann eine Technologie wie Mountebank verwendet werden.

Mountebank ist ein Open-Source-Tool, das plattformunabhängige Multi-Protokoll-Testdoubletten auf der Netzwerkschicht bereitstellt. Eine zu testende Applikation muss nur auf die IP oder URL der Mountebank-Instanz verweisen statt auf die reale Abhängigkeit. Das ermöglicht, die Applikation durch alle Applikationsschichten zu testen, wie man es sonst traditionell mit Stubs und Mocks tun würde. Unterstützte Protokolle sind HTTP, HTTPS, TCP und SMTP.

Für unser Szenario würde der Mountebank Imposter wie folgt definiert werden, um eine gemockte Antwort auf Port 9000 zurückzugeben:

{
  "port": 9000,
  "protocol": "http",
  "name": "My Mock",
  "mode": "text",
  "stubs": [
    {
      "responses": [
        {
          "is":
          {
            "statusCode": 200,
            "headers": {
              "Content-Type": "application/json"
            },
            "body": "{ \"message\": \"You got mocked data\" }"
          }
        }
      ],
      "predicates": [
        {
          "equals": {
            "path": "/anotherResource"
          }
        }
      ]
    }
  ]
}

Wir nehmen an, dass der Mock Server ebenfalls in einem Docker-Container aufgesetzt wurde, wie in diesem Blog-Artikel beschrieben.

Test-Definition

Nun zu unserem Test. Wir benutzen eine einfache JUnit-Integration unter Verwendung der rest-assured Bibliothek, integriert in einem Maven-Build. Der Test ruft die REST-API auf und verifiziert, dass die Antwort die gemockten Daten des Mock Servers enthält. An diesem Punkt könnte man auch direkt über die Mountebank-REST-API die Anfragen an den Mock Server verifizieren.

Solch ein Test könnte wie folgt aussehen:

public class SystemIT {
 
  @Test
  public void testMyResource() {
 
    RestAssured.baseURI = System.getProperty("system.url");
    RestAssured.defaultParser = Parser.JSON;
 
    // Verify an system end-to-end call
    given()
            .param("mimeType", "application/json")
            .get("/api/myResource")
            .then().assertThat()
            .header("content-type", containsString("application/json"))
            .body("message", equalTo("You got mocked data"));
  }
}

Test Konfiguration

Die Automatiserung dieses Szenarios wird mithilfe von Maven und des docker-maven-plugin erreicht. Zu diesem Zweck werden zwei Docker Images definiert, eines für die Mule-Anwendung und eines für den Mock Server:

<plugin>
  <groupId>org.jolokia</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.11.5</version>
 
  <configuration>
    <dockerHost>${boot2docker.url}</dockerHost>
 
    <images>
      <!-- Mule app container configuration -->
      <image>
        <name>mule-app</name>
        <alias>mule-app</alias>
        <run>
          <ports>
            <port>${webservice.port}:${webservice.port}</port>
          </ports>
          <links>
            <link>rest-mock:backend</link>
          </links>
          <wait>
            <!-- The plugin waits until this URL is reachable via HTTP ... -->
            <log>Server startup</log>
            <url>${boot2docker.address}:${webservice.port}/api/console</url>
            <time>8000</time>
            <shutdown>500</shutdown>
          </wait>
          <log>
            <prefix>Mule</prefix>
            <date>ISO8601</date>
            <color>blue</color>
          </log>
        </run>
        <build>
          <from>cpoepke/muledocker:latest</from>
          <tags>
            <tag>mule-app</tag>
          </tags>
          <command>/opt/mule-standalone-3.6.1/bin/mule -M-Dbackend.host=$BACKEND_PORT_9000_TCP_ADDR 
-M-Dbackend.port=$BACKEND_PORT_9000_TCP_PORT</command>
          <assembly>
            <mode>dir</mode>
            <basedir>/</basedir>
            <descriptor>assembly-app.xml</descriptor>
          </assembly>
        </build>
      </image>
      <!-- Backend mock container configuration -->
      <image>
        <name>rest-mock</name>
        <alias>rest-mock</alias>
        <run>
          <ports>
            <port>2525:2525</port>
            <port>9000:9000</port>
          </ports>
          <log>
            <prefix>Mock</prefix>
            <date>ISO8601</date>
            <color>yellow</color>
          </log>
          <wait>
            <!-- The plugin waits until this URL is reachable via HTTP ... -->
            <log>Server startup</log>
            <url>${boot2docker.address}:2525</url>
            <time>2000</time>
            <shutdown>500</shutdown>
          </wait>
        </run>
        <build>
          <from>cpoepke/mountebank-basis:latest</from>
          <tags>
            <tag>rest-mock</tag>
          </tags>
          <command>mb --configfile /mb/imposters.ejs --allowInjection</command>
          <assembly>
            <mode>dir</mode>
            <basedir>/</basedir>
            <descriptor>assembly-mock.xml</descriptor>
          </assembly>
        </build>
      </image>
    </images>
  </configuration>

In diesem Beispiel sind das Port Mapping und die Docker-Links zwischen den Containern erkennbar.

Um die Container für einen Test zu starten und zu stoppen, muss die folgende Integration-Test-Konfiguration aufgesetzt werden, um die Maven-Phasen zu konfigurieren:

  <!-- Connect start/stop to pre- and
       post-integration-test phase, respectively if you want to start
       your docker containers during integration tests -->
  <executions>
    <execution>
      <id>start</id>
      <phase>pre-integration-test</phase>
      <goals>
        <!-- "build" should be used to create the images with the
             artefacts -->
        <goal>build</goal>
        <goal>start</goal>
      </goals>
    </execution>
    <execution>
      <id>stop</id>
      <phase>post-integration-test</phase>
      <goals>
        <goal>stop</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Dies startet die Docker-Container mit docker:start vor der Maven-pre-integration-test-Phase und stoppt diese mit docker:stop in der Maven-post-integration-test-Phase.

Um diese Integrationstests auszuführen, benötigen wir das failsafe-Plugin, welches unsere System-End-to-End Tests in der Maven-integration-test-Phase inklusive Environment-Variablen ausführt.

<!-- fails-safe-plugin should be used instead of surefire so that the container gets stopped even
     when the tests fail -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.18.1</version>
  <executions>
    <execution>
      <id>integration-test</id>
      <phase>integration-test</phase>
      <goals>
        <goal>integration-test</goal>
      </goals>
    </execution>
    <execution>
      <id>verify</id>
      <phase>verify</phase>
      <goals>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <systemPropertyVariables>
      <!-- Needs to be repeated here (the following two lines strangely doesn't work when the next line is omitted although)
           Maven, you little sneaky beast ... -->
      <!--<system.port>${webservice.port}</system.port>-->
 
      <!-- Map maven variables to system properties which in turn can be used in the test classes -->
      <system.url>http://${boot2docker.ip}:${webservice.port}</system.url>
    </systemPropertyVariables>
  </configuration>
</plugin>
 
<!-- Tell surefire to skip test, we are using the failsafe plugin -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.18.1</version>
  <configuration>
    <skip>true</skip>
  </configuration>
</plugin>

Anmerkung: Bitte vergessen Sie nicht, das VM Port Forwarding auf Mac und Windows für boot2docker!

Test-Ausführung

Die Ausführung der Tests und ihre Integration in eine Continuous Integration oder Delivery Pipeline kann durch das „mvn verify“-Kommando durchgeführt werden. In dem Log ist zu sehen, dass alle Container starten, die Ausführung wartet, bis der Start durchgeführt wurde, die System-End-to-End-Tests durchgeführt werden und wie die Container gestoppt werden:

cpoepke:sys-test cpoepke$ mvn verify
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building System Test - Mule End to End Test Demo 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] --- docker-maven-plugin:0.11.5:build (start) @ sys-test ---
[INFO] Reading assembly descriptor: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/src/main/docker/assembly-app.xml
[INFO] Copying files to /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/mule-app/build/maven
[INFO] Building tar: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/mule-app/tmp/docker-build.tar
[INFO] DOCKER> Created image [mule-app] "mule-app"
[INFO] DOCKER> Tagging image [mule-app] "mule-app": mule-app
[INFO] Reading assembly descriptor: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/src/main/docker/assembly-mock.xml
[INFO] Copying files to /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/rest-mock/build/maven
[INFO] Building tar: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/docker/rest-mock/tmp/docker-build.tar
[INFO] DOCKER> Created image [rest-mock] "rest-mock"
[INFO] DOCKER> Tagging image [rest-mock] "rest-mock": rest-mock
[INFO] 
[INFO] --- docker-maven-plugin:0.11.5:start (start) @ sys-test ---
[INFO] DOCKER> Starting container 4ee608ab49b9
[INFO] DOCKER> Creating and starting container 4ee608ab49b9 [rest-mock] "rest-mock"
2015-06-09T22:49:36.349+02:00 Mock> mountebank v1.2.122 now taking orders - point your browser to http://localhost:2525 for help
[INFO] DOCKER> Waited on url https://192.168.59.103:2525 and on log out 'Server startup' 2091 ms
[INFO] DOCKER> Starting container b7069c9653cd
[INFO] DOCKER> Creating and starting container b7069c9653cd [mule-app] "mule-app"
2015-06-09T22:49:38.634+02:00 Mule> MULE_HOME is set to /opt/mule-standalone-3.6.1
2015-06-09T22:49:38.642+02:00 Mule> Running in console (foreground) mode by default, use Ctrl-C to exit...
2015-06-09T22:49:38.649+02:00 Mule> MULE_HOME is set to /opt/mule-standalone-3.6.1
2015-06-09T22:49:39.845+02:00 Mule> Running Mule...
...
[INFO] DOCKER> Waited on url https://192.168.59.103:8080/api/console and on log out 'Server startup' 8114 ms
[INFO] 
[INFO] --- maven-failsafe-plugin:2.18.1:integration-test (integration-test) @ sys-test ---
[INFO] Failsafe report directory: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/failsafe-reports
 
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running de.cpoepke.mule.demo.SystemIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.871 sec - in de.cpoepke.mule.demo.SystemIT
 
Results :
 
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
 
[INFO] 
[INFO] --- docker-maven-plugin:0.11.5:stop (stop) @ sys-test ---
[INFO] DOCKER> Stopped and removed container b7069c9653cd [mule-app] "mule-app"
[INFO] DOCKER> Stopped and removed container 4ee608ab49b9 [rest-mock] "rest-mock"
[INFO] 
[INFO] --- maven-failsafe-plugin:2.18.1:verify (verify) @ sys-test ---
[INFO] Failsafe report directory: /Volumes/Projects/Current/Mule-ESB/mule-end-to-end-test-demo/sys-test/target/failsafe-reports
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.396 s
[INFO] Finished at: 2015-06-09T22:49:50+02:00
[INFO] Final Memory: 22M/206M
[INFO] ------------------------------------------------------------------------

Fazit

Allumfassendes Testen wird gemeinhin als essentieller Bestandteil eines guten Software-Entwicklungsprozesses verstanden. Diese Tests automatisiert und auf allen Ebenen der Testpyramide durchzuführen ist erstrebenswert. Folglich ist auch das End-to-End-Testen einer Mule-Anwendung von Bedeutung.

Wir haben in diesem Artikel gezeigt, wie man eine voll automatisierte System-End-to-End-Test-Infrastruktur aufbauen kann. Wir haben dies am Beispiel vom Testen von Mule-Anwendungen mit Docker und Mountebank gemacht. Es ist aber auch möglich, dieses Test-Setup für andere Szenarien und Applikationstypen wiederzuverwenden, falls ein End-to-End-Test gewünscht ist.

Eine volles lauffähiges Beispiel dieses Szenarios befindet sich auf Github als Demo.

Serie

Dieser Artikel ist Teil einer Mule-ESB-Serie zum Thema Testen von Mule-Applikationen:

Conrad ist Senior IT Consultant bei der codecentric AG. Er sieht sich selbst als „Coding-Software-Architekt“, Entwickler und in allen anderen Rollen, die für die erfolgreiche Durchführung eines Projekts vonnöten sind. Sein persönliches Ziel ist es, nach neuem Wissen in der IT-Industrie zu streben.

In seiner Freizeit macht er sich in Berlin für die Microservices-Community stark, als Gründer und Hauptorganisator des Microservices Meetups Berlin und als Mitglied im Organisations-Komitee der MicroXchg Konferenz.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.