Suche
Kolumne: EnterpriseTales

Testen im Kontext: Integration Testing in Java EE

Lars Röwekamp, Arne Limburg

© Software & Support Media

„Der Begriff Integrationstest bezeichnet […] eine aufeinander abgestimmte Reihe von Einzeltests, die dazu dienen, verschiedene voneinander abhängige Komponenten eines komplexen Systems im Zusammenspiel miteinander zu testen.“ (Wikipedia). Wie kann ich aber eine Menge an abhängigen Komponenten (also in Java EE z. B. EJBs oder CDI-Beans) gemeinsam testen, ohne das Ganze gleich auf meinem Testserver deployen zu müssen? Das Problem ist klar: Moderne Enterprise-Anwendungen basieren auf Dependency Injection. Das bedeutet, dass der Container die Abhängigkeit zwischen den Komponenten auflöst und gegenseitig in die Komponenten injiziert.

Befinde ich mich in einem Testing-Framework wie JUnit oder TestNG, gibt es out of the box keinen Container, der diese Aufgabe übernimmt. Es werden also Add-ons benötigt, um Dependency Injection in Tests verwenden zu können. Wenn man diese hat, stößt man auf das nächste Problem: Man möchte zwar das Zusammenspiel mehrerer Komponenten testen, aber eventuell haben die zu testenden Komponenten zusätzlich Abhängigkeiten, die nicht interessieren oder die man durch Mock-Implementierungen ersetzten möchte. In dieser Kolumne werden wir das Thema Integration Testing und Mocking in Java EE einmal näher betrachten und verfügbare Frameworks vorstellen.

Wir gehen hier davon aus, dass dem Leser gängige Mocking-Frameworks wie z. B. Mockito bekannt sind. Das Ziel dieser Frameworks ist es zu ermöglichen, dass Abhängigkeiten der zu testenden Komponente durch Mock-Implementierungen ersetzt werden. Mit dem MockitoJUnitRunner ist dies sogar per Dependency Injection möglich. Alle Abhängigkeiten der Komponente werden mit Mocks initialisiert (Listing 1). Auf diese Weise lässt sich ein echter Unit Test (Test genau einer Komponente) sehr leicht realisieren. Was aber, wenn man nicht alle, sondern nur einige Abhängigkeiten der Komponente mocken und für andere abhängige Komponenten die tatsächliche Implementierung verwenden möchte? Wie also soll man vorgehen, wenn man einen Integrationstest schreiben möchte, bei dem das Zusammenspiel mehrerer Komponenten überprüft wird?

Listing 1
@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock
    private MyDepenendency myDependency;
    @InjectMocks
    private MyComponentToTest myComponent;

    @Test
    public void myComponentTest() {
        ...
    }
    ...
}

Testen im Container

Das Testing-Framework Arquillian bietet CDI-Injection in Tests. Möglich wird dies, indem der zu testende Code in einem „echten“ CDI-Container deployt wird. Die Kommunikation zwischen Test und Container übernimmt das Framework. Dabei ist es mit der Zusatzbibliothek ShrinkWrap möglich, programmatisch nur genau die Komponenten zu deployen, die man auch testen möchte, indem man ein Deployment-Artefakt (JAR, WAR oder EAR) erstellt, das eben nur genau diese Komponenten enthält. Je nach Konfiguration kann Arquillian für den Test einen reinen CDI-Container (Weld oder OpenWebBeans) oder sogar einen kompletten Java-EE-Server verwenden. Durch Letzteres ist es sogar möglich, die kompletten Java-EE-Features (wie z. B. Injection des EntityManagers über @PersistenceContext) im Test zu nutzen.

Die Sache hat allerdings einen kleinen Haken: Der CDI-Container achtet bereits beim Hochfahren darauf, dass jede Abhängigkeit aller Komponenten auch erfüllt werden kann. Ist das nicht der Fall, gibt es einen Fehler zum Startup-Zeitpunkt. Wenn man also ein Set von Komponenten testen möchte und diese mit ShrinkWrap in ein Archiv verpackt, um sie über Arquillian zu deployen, so schlägt das Deployment fehl, falls eine der Komponenten noch eine weitere Abhängigkeit hat, die man nicht testen möchte.

In Arquillian haben wir nun keine andere Wahl, als die Abhängigkeit mit zu deployen, was natürlich zu weiteren Abhängigkeiten führen kann. Ehe man es sich versieht, kann es passieren, dass man sein gesamtes Projekt für jeden Test deployen muss. An dieser Stelle wäre es wünschenswert, den Mocking-Ansatz aus dem ersten Absatz mit dem Arquillian-Ansatz verbinden zu können.

Listing 2
@RunWith(Arquillian.class)
public class MyTest {

    @Deployment
    public static WebArchive createDeployment() {
        PomEquippedResolveStage pom
            = Maven.resolver().loadPomFromFile("pom.xml");
        BeansDescriptor beansXml = Descriptors.create(BeansDescriptor.class)
            .addDefaultNamespaces().getOrCreateAlternatives()
            .stereotype(Mock.class.getName()).up();
        return ShrinkWrap.create(WebArchive.class)
            .addAsLibrary(pom.resolve("org.mockito:mockito-core)
                .withTransitivity().asFile()))
            .addClasses(MyComponentToTest.class, MyDependency.class)
            .addClasses(Mocks.class, Mock.class)
            .addAsWebInfResource(beansXml.exportAsString(), "beans.xml")
    }

    @Inject
    private MyDepenendency myDependency;
    @Inject
    private MyComponentToTest myComponent;

    @Test
    public void myComponentTest() {
        ...
    }
    ...
}

@ApplicationScoped
public class Mocks {
    @Produces @Mock
    private MyDependency = Mockito.mock(MyDependency.class);
}

@Stereotype
@Alternative
@Retention(RetentionPolicy.RUNTIME)
public @interface Mock {}

Mocking im Container

Tatsächlich ist es möglich, Mockito mit Arquillian im Container zu deployen. Am einfachsten geht das über ShrinkWraps MavenResolver. Wenn man sein Projekt mit Maven baut, kann man dabei sogar die Mockito-Version aus der Projekt-POM auslesen (Listing 2).

Die Mock-Objekte können dann über CDI-Producer zur Verfügung gestellt und im Test konfiguriert werden (Listing 2). Problematisch wird es, wenn die zu mockenden Abhängigkeiten keine Interfaces sind, sondern echte Klassen.

Welches Problem tritt dann auf? Die Klassen müssen natürlich mit Arquillian deployt werden. Das macht sie allerdings automatisch zu CDI-Beans. Zusammen mit unserem Mock gibt es dann zwei Instanzen dieses Typs im Container. Der Container quittiert dies beim Start mit einer AmbigouosResolutionException und bricht den Start ab.

Mocken von Klassen

Um in Arquillian Klassen mocken zu können, gibt es einen einfachen Trick. Man bedient sich des CDI-Features @Alternative. Um es für @Produces-Felder verwenden zu können, muss man einen Stereotype verwenden, den man in der beans.xml aktiviert (Listing 2). Annotiert man nun ein @Produces-Feld mit diesem Stereotype, so wird der Mock eine Alternative im CDI-Sinn, das heißt, er wird an jeder Stelle verwendet, an der sonst die tatsächliche Implementierung verwendet werden würde. Dadurch kann der Mock auch direkt in den Test injiziert und wie gewohnt über Mockito konfiguriert werden.

Bei der Konfiguration über Mockito gilt es lediglich zu beachten, dass Mockito zur Konfiguration ThreadLocal-Variablen verwendet. Das bedeutet, dass die Konfiguration nicht in verschiedenen Threads erfolgen sollte. Da Arquillian für jeden Test aber ein neues Deployment erzeugt, ist man auf der sicheren Seite, wenn man die Konfiguration im Test selbst vornimmt, selbst wenn man die Tests parallel ausführt.

Fazit

In dieser Kolumne wurden die beiden Frameworks Mockito und Arquillian vorgestellt. Während Mockito ein Framework ist, mit dem sich recht elegant Mock-Objekte für Tests erzeugen und deren Verhalten spezifizieren lassen kann, kann man mit Arquillian Tests in einem Dependency-Injection-Container laufen lassen, wodurch CDI-Features im Test zur Verfügung stehen.

Wir haben hier gezeigt, wie man beide Frameworks verbinden kann und sich so Mocks in CDI-Tests injizieren lassen kann.

Neben der Integration der beiden Frameworks ist dazu aktuell noch etwas eigener Code notwendig, nämlich ein Stereotype, um aus den Mocks @Alternatives zu machen, der Code, der diese über die beans.xml aktiviert und eine Bean, die die Mocks zur Verfügung stellt. Mit diesem Setup lassen sich in Arquillian-Tests Mockito-Mocks injizieren.

Geschrieben von
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare
  1. Matthias Huber2015-06-10 11:02:32

    Es ist in Listing 2 etwas verwirrend die @Mock Annotation zu sehen, so dass diese mit der @Mock Annotation vom Mockito Framework verwechselt werden kann...

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.