Was ist ein MicroService? In unserem Kontext ist es ein (kleines) Modul der Gesamtanwendung, welches seinen Dienst als Webapplikation (.war) anbietet. Das konkrete Schnittstellen-Protokoll ist für diesen Artikel nicht entscheidend. Möglich ist klassisch SOAP, REST aber auch Spring HttpInvoker. Ein MicroService ist eine gute Möglichkeit Sollbruchstellen in die Gesamtanwendung einzubauen.

Nun zum eigentlichen Thema: Wie teste ich einen solchen Service? Im Grunde wie immer, d.h. viele Unit-Tests und ein paar ausgewählte Integration-Tests. Die Integration-Tests sollen zeigen, dass die angebotenen Schnittstellen des Services funktionieren und alles richtig konfiguriert ist. Die Integration-Tests, die die Gesamtanwendung, d.h. das Netz aller Services testet, ist hier nicht Bestandteil.

Wie macht man das jetzt mit Maven? Eine Variante ist die Unit-Tests und Integration-Tests zu trennen. Vor der integration-test Phase einen Tomcat zu starten, dann seine Integration-Tests auszuführen und anschließend wieder den Tomcat zu stoppen. Tomcat, weil wir diesen nicht nur lokal einsetzen und somit „gleicher“ zur Produktion sind. Eine echte Plugin-Auswahl fand nicht statt, wir sind einfach mit dem tomcat7-maven-plugin gestartet.

Mit Maven Tomcat automatisch starten und stoppen

<build>
  <plugins>
    ..
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.0-beta-1</version>
      <configuration>
        <contextFile>${project.basedir}/src/test/resources/test-context.xml</contextFile>
      </configuration>
      <executions>
        <execution>
          <id>tomcat-run</id>
          <goals>
            <goal>run-war-only</goal>
          </goals>
          <phase>pre-integration-test</phase>
          <configuration>
            <fork>true</fork>
          </configuration>
        </execution>
        <execution>
          <id>tomcat-shutdown</id>
          <goals>
            <goal>shutdown</goal>
          </goals>
          <phase>post-integration-test</phase>
        </execution>
      </executions>
    </plugin>

  </plugins>
</build>

Das Starten des Tomcats binden wir an die pre-integration-test Phase (Zeile 17). Wichtig ist fork auf true zu setzen (Zeile 19), da sonst Maven nach Start des Tomcats stehen bleibt. Das Beenden binden wir entsprechend an die post-integration-test Phase (Zeile 27). Wenn keine context.xml in der webapp enthalten ist, muss eine per contextFile angeben werden (Zeile 9).

So, unser Tomcat startet und stoppt nun beim Ausführen von mvn integration-test.

Mit Maven innerhalb eines Modules Unit- und Integration-Tests trennen

Nächster Schritt ist die Unit- und Integration-Tests zu trennen. Im Netz empfehlen einige dies in separaten Maven-Modulen zu tun. Wir finden es sinnvoller dies über die Test-Phasen zu steuern:

<build>
  <plugins>
    ..
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
        <skip>true</skip>
        <trimStackTrace>false</trimStackTrace>
      </configuration>
      <executions>
        <execution>
          <id>unit-tests</id>
          <phase>test</phase>
          <goals>
            <goal>test</goal>
          </goals>
          <configuration>
            <skip>false</skip>
            <includes>
              <include>**/*Test.java</include>
            </includes>
            <excludes>
              <exclude>**/*IntegrationTest.java</exclude>
            </excludes>
          </configuration>
        </execution>
        <execution>
          <id>integration-tests</id>
          <phase>integration-test</phase>
          <goals>
            <goal>test</goal>
          </goals>
          <configuration>
            <skip>false</skip>
            <includes>
              <include>**/*IntegrationTest.java</include>
            </includes>
          </configuration>
        </execution>
      </executions>
    </plugin>

  </plugins>
</build>

In Zeile 8 wird das Default-Test-Verhalten vom maven-surefire-plugin ausgeschaltet. Durch zwei einzelne <execution/>'s können wir unterschiedliche <configuration/>'s an unterschiedliche Phasen binden (Zeile 14 und 30). In der test Phase werden alle Klassen mit Endung Test.java ausgeführt außer jene mit Endung IntegrationTest.java. In der integration-test Phase werden nun lediglich alle Klassen mit Endung IntegrationTest.java ausgeführt. Vielen Dank an HDave, der letzteres auf Stackoverflow schön erklärt.

Hast du ähnliche Anwendungsfälle? Wie hast du diese gelöst?