Suche
Unit- und Integrationstests von CDI-Komponenten

Qualitätssicherung im Container: Leichtgewichtiges Testen von CDI-Komponenten

Christian Laboranowitsch, Philipp Bayer, Tanja Schmidt

© Shutterstock.com / Shai_Halud

Bei der Entwicklung von Java-EE-Anwendungen vermisst der Spring-verwöhnte Entwickler ein integriertes Testframeworks schmerzlich. Denn während mit Spring das Spring-Test-Modul mitgeliefert wird, ist man bei Java-EE-Applikationen auf Third Party Libraries und einen gewissen Erfindergeist der Entwickler und Architekten im Projekt angewiesen. Neben den allseits bekannten Größen wie Arquillian steht mit CDI-Unit ein vielversprechendes Framework in den Startlöchern, wenn es um das Testen von komplexen Java-EE-6-/CDI-Applikationen geht. Im Folgenden werden wir die Vor- und Nachteile der einzelnen Ansätze darstellen und exemplarische Anwendungsfälle aufzeigen.

Ein wichtiger Erfolgsfaktor für Softwareentwicklungsprojekte ist das frühzeitige Testen durch die Entwickler. Aber wie kann man eine zufriedenstellende Testabdeckung mit angemessenem Aufwand erreichen? Dafür müssen die gewählten Frameworks und Werkzeuge die Erstellung und Ausführung von Tests bestmöglich unterstützen und gleichzeitig zum gewählten Technologiestack passen. Werkzeuge, die Hand in Hand greifen, vereinfachen die Erstellung und Ausführung der Tests und steigern nicht zuletzt die Motivation des Entwicklers, was ein nicht zu unterschätzender Aspekt ist.

Doch Testen ist in vielen Projekten leider immer noch eine Aufgabe mit C-Priorität. Es wird erledigt, wenn es die Zeitplanung oder die Budgetsituation zulässt. Häufig bleibt es auch noch heute auf der Strecke (eine Auswirkung ist beispielsweise, dass fehlgeschlagene Tests mit @Ignore annotiert werden, um sie dann „später“ mal in Ordnung zu bringen). Ein Grund dafür mag sein, dass die Bedeutung von Entwicklertests für Nichtentwickler oft schwer zu greifen ist. Denn immerhin gibt es in nahezu allen Projekten Testphasen, in denen von Testern getestet wird. Dabei ist schon lange bekannt, dass ein Bug umso weniger Kosten verursacht, je früher er gefunden wird (siehe: Boehm, Barry W.; Papaccio, Philip N.: „Understanding and Controlling Software Costs“, IEEE Transactions on Software Engineering, v. 14, no. 10, October 1988). Man kann also argumentieren, dass Entwicklertest einen Return-on-Investment bieten, der umso höher ist, je früher die notwendigen Tests zur Anwendung kommen. Wenn also Entwicklertests nur nebenbei und irgendwann gemacht werden, leidet die Qualität der Software. Dies führt häufig zu Budgetbelastungen und Zeitdruck, gerade in der Endphase eines Projekts. Nicht zu vernachlässigen sind die hohen Belastungen der einzelnen Projektmitglieder, wenn es in der Auslieferungsphase zu Qualitätsproblemen mit langen Buglisten kommt. Auch wenn dies eine bereits bekannte Argumentationskette darstellt, gilt es immer noch bei dem einen oder anderen Stakeholder Überzeugungsarbeit zu leisten. Daneben bieten Entwicklertests noch eine Reihe anderer Vorteile:

  • Fehler, die durch spätere Refactorings entstehen, werden entdeckt. Ohne Entwicklertests ist es meist schwer zu sagen, ob das Refactoring erfolgreich durchgeführt werden konnte, wenn nicht sogar unmöglich (Regressionstests).
  •  Spätere Änderungswünsche können ungewollte Auswirkungen auf Bereiche des Systems haben, die so nicht absehbar waren und nur durch das Fehlschlagen der Entwicklertests frühzeitig identifiziert werden.
  • Das Schreiben von Entwicklertests bewirkt häufig eine intensivere Auseinandersetzung mit den fachlichen Anforderungen und der technischen Umsetzung.
  • Entwicklertests dokumentieren das gewollte und ungewollte Verhalten von Code. So ist es häufig eine gute Idee, zur Einarbeitung in unbekannten Code bei den Tests zu starten.

Welche Art von Test darf es sein?

In der Softwareentwicklung gibt es für den richtigen Einsatzzweck unterschiedliche Arten von Tests (Abb. 1). Bei unserer Betrachtung wollen wir uns auf Entwicklertests beschränken.

Abb. 1: Testpyramide

Abb. 1: Testpyramide

Unter Entwicklertests verstehen wir Unit und Integrationstests. Da diese beiden Begriffe nicht immer eindeutig verwendet werden, möchten wir kurz auf diese Testarten eingehen und die Unterschiede und Einsatzzwecke aufzeigen.

Unit Test

Unit Tests dienen dazu, die Module einer Software auf korrekte Funktionalität zu testen. Synonyme Bezeichnungen sind auch die Begriffe Modultest und Komponententest. Merkmal eines Unit Tests ist, dass er ein Modul isoliert von anderen Modulen testet. Unter einem Modul beziehungsweise einer Komponente wird dabei der kleinstmögliche Teil funktionalen Codes verstanden; in der Regel also eine Methode oder Klasse. Die Isolation der Komponenten von externen Abhängigkeiten stellt dabei häufig eine Herausforderung dar, die es zu lösen gilt. Abhängigkeiten zu anderen Komponenten erweitern den Scope des Tests und machen es damit schwerer, die Fehler zu isolieren. Eine verbreitete Lösung für dieses Problem ist das Mocking. Dabei werden Abhängigkeiten durch Attrappen, so genannte Mocks, ersetzt. Auf dieses Thema werden wir im weiteren Verlauf noch zu sprechen kommen. Der Aufbau von Unit Tests ist simpel und lässt sich generell in drei Phasen unterteilen:

  • Setup: Während der Setup-Phase werden Abhängigkeiten vorbereitet, Objekte erstellt und andere Vorbereitungen getroffen, die zur Durchführung des Tests notwendig sind.
  • Ausführung: Das zu testende Modul wird in der zuvor vorbereiteten Umgebung aufgerufen.
  • Verifikation: Das Ergebnis der Ausführung wird mit den erwarteten Ergebnissen verglichen.

Abschließend erfreuen wir uns als Entwickler an einem hoffentlich grünen Balken in unserer Entwicklungsumgebung.

Integrationstest

Integrationstests dienen dazu, das Zusammenspiel von Komponenten zu testen und kommen damit in der Regel nach Unit Tests zur Anwendung. Im Gegensatz zum Unit Test sollen dabei in einem einzelnen Testfall unterschiedliche Komponenten und Schichten im Zusammenspiel betrachtet werden. So sind oft nicht nur die unterschiedlichen Softwarekomponenten Teil des Integrationstests, sondern auch die angebundene Datenbank, der Server, auf dem der Test läuft und häufig auch der Browser, der zum Testen verwendet wird. In der idealen Welt entspricht die Testumgebung der späteren Produktionsumgebung. In der realen Welt ist dies leider nicht immer so einfach zu realisieren, und Entwicklerteams stehen häufig vor der Aufgabe, hier einen angemessenen Trade-off zu finden. Letztendlich ist es das Ziel von Integrationstests, das Zusammenspiel der einzelnen Komponenten und Schichten zu überprüfen. Wenn komplexe externe Schnittstellen angesprochen werden, kann es notwendig sein, diese in einer Integrationstestumgebung ebenfalls zu berücksichtigen. Integrationstestumgebungen benötigen normalerweise einen (Java-EE-)Container und eine Datenbank. Damit sind längere Laufzeiten im Vergleich zu Unit Tests die Regel. Moderne Java-EE-Container liegen mit ihren Start-up-Zeiten zwar im Sekundenbereich, die Ausführungszeiten der Integrationstests sind aber dennoch zu lange, um diese mit der notwendigen Häufigkeit aus der IDE auszuführen. Es empfiehlt sich, diese im Gegensatz zu Unit Tests lokal nur bei Bedarf auszuführen.

Basics aus dem Testlabor

Ein Unit Test sollte immer so isoliert wie nur irgend möglich laufen. Die Herausforderung dabei ist es, eine Komponente von ihrer Umgebung herauszulösen. Stubs, Spys, Fakes und Mocks sind probate Mittel, um diese externen Abhängigkeiten zu ersetzen und werden auch ganz allgemein Dubletten genannt. Tabelle 1 zeigt die einzelnen Typen, die Unterscheidungsmerkmale und die jeweiligen spezifischen Einsatzmöglichkeiten.

Tabelle 1: Verschiedene Testtypen im Überblick

Tabelle 1: Verschiedene Testtypen im Überblick

Herausforderungen beim Testen von Java-EE-Anwendungen

Java-EE-Anwendungen besitzen meist eine komplexe Struktur mit speziellen Anforderungen an die Laufzeitumgebung (Container). Der Container stellt Infrastrukturdienste zur Verfügung, z. B.: JNDI, Transaktionsbehandlung, Persistenz, Logging, Mailanbindung und Dependency Injection. Infrastrukturdienste und Abhängigkeiten lassen sich in der Theorie zwar mit Dubletten ersetzen, häufig wird es aber in der Praxis sehr aufwändig sein, diese Dubletten den einzelnen Komponenten zur Verfügung zu stellen. Die Überprüfung der korrekten Interaktion einer Komponente mit den Basisdiensten kann im Einzelfall zu einer echten Herausforderung werden. Wird z. B. ein CDI-Observer beim richtigen Event getriggert, auf das er reagieren soll, und bei anderen nicht? Solche Fälle lassen sich nicht ohne einen Java-EE-/CDI-Container testen; hier können auch Dubletten nicht helfen. In der grauen Vorzeit des J(2)EE-Zeitalters waren Applikationsserver noch schwerfällige Dampfer, Embedded-Container nicht verfügbar und CDI-Container wie zum Beispiel Weld noch unbekannt. Das Testen war kein Zuckerschlecken – schon alleine das Starten und Deployen auf dem Server verursachte einen erheblichen zeitlichen Aufwand. Für den Entwickler ist es da heute schon einfacher, nicht zuletzt durch die Verfügbarkeit geeigneter Frameworks wie Arquillian und CDI-Unit. Auch hat sich die Toolunterstützung erheblich verbessert.

Anforderungen an ein Testframework

Betrachtet man den Wunschzettel eines Entwicklers zum Thema Testframeworks, so finden sich darauf häufig Dinge wie:

  • Das Testframework soll Container starten und stoppen
  • Benötigte Klassen und Ressourcen in Archive bündeln
  • Diese Archive in einem Container deployen
  • Möglichkeiten bereitstellen, um in Testklassen auf EJBs und andere Ressourcen zuzugreifen
  • Testfälle im Container ausführen
  • Testresultate an Entwicklungsumgebung und Build-System weitergeben

Im Folgenden möchten wir neben dem allseits bekannten Arquillian mit CDI-Unit einen jungen Herausforderer vorstellen.

Arquillian – kleine Außerirdische in der Java-Welt ganz groß

Für alle, die eine Allzweckwaffe zur Durchführung von Entwicklertests innerhalb von Containern suchen, ist Arquillian sicherlich die erste Wahl. Mit der derzeitigen Version 1.1.5.Final blickt es schon auf ein bewegtes Leben zurück. Hervorgegangen ist Arquillian aus einer Codebasis, die im Rahmen der CDI-Spezifikation (JSR-299) im Jahr 2009 entwickelt wurde. In der Zwischenzeit ist das Framework unter der Ägide von Red Hat und der JBoss-Community weiter gewachsen und erfreut sich einer ungebrochenen Beliebtheit. Die Hauptprämissen bei der Entwicklung sind dabei:

  • Tests sollen unabhängig von der gewählten Containerimplementierung lauffähig sein.
  • Tests sollen in der IDE ohne explizites Bauen lauffähig und einfach zu debuggen sein.
  • Eine einfache Integration mit den existierenden Testwerkzeugen der Java-Welt sein.

Anhand der Prämissen lässt sich bereits erkennen, dass der Fokus dabei auf Integrationstests liegt. Die Liste der unterstützten Container ist lang und reicht dabei vom reinen CDI-Container wie z. B. Weld bis hin zu „Full-Blown“-Applikationsservern wie JBoss, Oracles Weblogic und sogar der Websphere-Familie. Mit der Arquillian Suite ist somit das Testen von CDI-Komponenten, Enterprise Java Beans und allen anderen technologischen Artefakten der JEE-Welt möglich.

Eine Besonderheit dabei stellt die Existenz von Containeradaptern für die Durchführung so genannter Remote-, Managed- oder Embedded-Tests direkt aus der IDE oder aus einem Build-Tool wie Maven oder Gradle dar. Die Durchführung von Tests in einem Embedded-Container ist eine bequeme und vor allem performante Art und Weise des Vorgehens. Allerdings darf dabei nicht vergessen werden, dass Embedded-Container ein abweichendes Verhalten zu „echten Standalone-Containern“ zeigen können. In einem solchen Fall ist dann der „Remote-Container“ das richtige Mittel der Wahl. Der Remote-Container läuft in einem eigenen Prozess und bildet die Ablaufumgebung auch dann korrekt ab, wenn der Embedded-Container an seine Grenzen kommt. Auf den automatisierten Start des Containers kann in diesem Fall verzichtet werden, was die Turnaround-Zeiten auf ein erträgliches Maß reduziert.

Die Grundstruktur einer Arquillian-Umgebung besteht aus dem eigentlichen Container, dem passenden Containeradapter, dem Arquillian Core, dem Testframework, dem ShrinkWrap und natürlich dem eigentlichen Testfall (Abb. 2).

Abb. 2: Komponenten in einem Arquillian-Test

Abb. 2: Komponenten in einem Arquillian-Test

Anhand eines kleinen Beispiels (Listing 1) möchten wir die Anatomie eines Testfalls mit Arquillian und das Zusammenspiel der einzelnen Komponenten zeigen. Getestet werden soll eine reine CDI-Komponente, als Container der Wahl wird ein Weld-Embedded-Container genutzt.

Vor dem Test ist das Verpacken der Artefakte in ein Archiv notwendig, das so genannte „Shrinkwrapping“.

Listing 1: Testfall mit Arquillian
// 1. Arquillian Testrunner
@RunWith(Arquillian.class)
public class ContainerTest {

  // 2. Testobjekt injizieren
  @Inject
  private MyCdiBean myCdiBean;
  
  // 3. Archiv erzeugen 
  @Deployment
  public static WebArchive createDeployment() {
    WebArchive archive = ShrinkWrap
  .create(WebArchive.class, "test.war")
  .addPackages(true, "de.bit")
  .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
  File[] libs = Maven.resolver()
  .loadPomFromFile("pom.xml")
  .importRuntimeDependencies()
  .resolve().withTransitivity().asFile();

  for (File file : libs) {
    archive.addAsLibrary(file);
  }
  return archive;
}

// 4. Hier kommen die eigentlichen Testfälle

Wird der Testfall dann ausgeführt, startet Arquillian den konfigurierten Container, deployt das Archiv und führt zuletzt die Testfälle aus. Zu erwähnen bleibt dabei noch, dass mittels Maven-Profile auf elegante Art und Weise zwischen unterschiedlichen Container umgeschaltet werden kann (Listing 2).

Listing 2: Maven-Container-Profile 
<profile>
    <id>arquillian-weld-ee-embedded</id>
    <dependencies>
      <dependency>
        <groupId>org.jboss.arquillian.container</groupId>
        <artifactId>arquillian-weld-ee-embedded-1.1</artifactId>
        <version>1.0.0.CR8</version>
        <scope>test</scope>
      </dependency>
...
  <profile>
    <id>arquillian-glassfish-embedded</id>
    <dependencies>
      <dependency>
        <groupId>org.jboss.arquillian.container</groupId>
        <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
        <version>1.0.0.CR4</version>
        <scope>test</scope>
      </dependency>

Zur Konfiguration eines konkreten Adapters benötigt Arquillian noch zusätzlich eine Konfigurationsdatei (arquillian.xml). Diese muss ebenfalls deployt werden. Die für den gewählten Container gültigen Optionen sind der detaillierten Dokumentation zu entnehmen.

Mit Arquillian werden die Anforderungen an ein Testframework hervorragend umgesetzt. Besonders auf dem Gebiet der Integrationstests im Container stellt es eine gute Wahl dar. Mit Kenntnissen in JUnit oder TestNG fühlt man sich schnell heimisch. Es eignet sich nicht nur für die Nutzung während der Entwicklung, sondern empfiehlt sich auch für den Einsatz auf dem Continuous-Integration-Server. Mit ShrinkWrap existiert eine leistungsfähige Zusatzkomponente zum Schnüren von deploybaren Archiven. Es werden nahezu alle gängigen Container unterstützt. Für viele Anwendungsfälle stehen bereits Erweiterungen bereit. Die angestrebte Offenheit in Richtung existierender Testframeworks bleibt dabei nicht nur ein leeres Versprechen. Arquillian punktet, wenn es um das automatisierte Testen von Web-Frontends geht. Im Zusammenspiel mit Selenium und einer Erweiterung wie Graphene ist das Testspektrum über das für Entwicklertests übliche Maß hinaus erweiterbar (siehe: Schneller, Maria; Kuhn, Tilmann: „Arquillian Graphene 2.0 im Einsatz“, in Java Magazin 1.2015). Zu guter Letzt soll die umfangreiche Dokumentation nicht unerwähnt bleiben.

Der Herausforderer: CDI-Unit

Mit CDI-Unit möchten wir ein recht junges Framework vorstellen. Es wird seit 2013 von einer kleinen Gruppe rund um Bryn Cooke entwickelt und ist unter der Apache-2.0-Lizenz verfügbar. Es ermöglicht das Testen von CDI-Applikationen in einem CDI-Container. Dabei unterstützt es den Entwickler durch automatisches Deployen der benötigten Klassen auf dem CDI-Container und das Starten und Stoppen des Containers. Daneben bietet es Unterstützung für Mocking-Frameworks und hilft dabei, Testdubletten in den CDI-Container zu platzieren. CDI-Unit bietet keine Containeradapter; es kann ausschließlich der Weld-Container verwendet werden.

Das Erstellen von Testfällen mit CDI-Unit ist denkbar einfach. Die benötigten Klassen und Mocks werden per Annotationen konfiguriert. Im Gegensatz zu Arquillian wird keine Konfiguration benötigt, auch das Verpacken in ein Archiv entfällt. Damit der Test auch von CDI-Unit durchgeführt wird, muss die Testklasse mit @RunWith(CdiRunner.class) annotiert werden. Jeder einzelne Testfall läuft dabei in einem eigens für ihn erstellten CDI-Container. CDI-Unit verfolgt bei der Erstellung des CDI-Containers einen minimalistischen Ansatz und fügt nur die Klassen hinzu, die tatsächlich zur Ausführung des aktuellen Testfalls benötigt werden. Dabei wird mit den in den Testfall injizierten Klassen angefangen, und die Abhängigkeiten werden rekursiv aufgelöst. CDI-Unit bietet die Möglichkeit, Klassen manuell dem CDI-Container hinzuzufügen. Darauf werden wir später noch eingehen.

In Listing 3 möchten wir zeigen, wie mit CDI-Unit ein Observer getestet werden kann. Es reicht, den benötigten Observer in die Testklasse zu injizieren. Um alles Weitere kümmert sich CDI-Unit. Die Klasse wird automatisch vom CDI-Container als Observer erkannt, und alle eventuell benötigten Abhängigkeiten werden ebenfalls zur Verfügung gestellt.

Listing 3: Testen eines Observers
@RunWith(CdiRunner.class)
public class MyObserverTest {

  @Inject
  MyObserver myObserver;

  @Inject
  Event unnamedEvent;

  @Test
  public void testObserveString() throws Exception {
    unnamedEvent.fire("unnamedEventFired");
    assertThat...

Es bleibt zu erwähnen, dass die Klassenerkennung allerdings auch ihre Grenzen hat. So kann ein Interface als Injection Point nicht automatisch aufgelöst werden, es sei denn, eine Implementierung des Interface wird an einer anderen Stelle des Testfalls verwendet.

Ebenso werden Interceptoren, Decoratoren und Event-Listener nicht automatisch dem Container hinzugefügt, es sei denn, sie werden explizit an einem Injection Point benötigt. In solchen Fällen schaffen die Annotation @AdditionalClasses und ihre großen Brüder @AdditionalPackage und @AdditionalClasspath Abhilfe. Mit ihnen lassen sich Klassen dem CDI-Container manuell hinzufügen. So würde der Test in Listing 4 ohne die @AdditionalClasses-Annotation fehlschlagen. Alternativ könnte man auch die FileReader-Implementierung des Reader-Interface in den Testfall injizieren (Listing 4).

Listing 4
@RunWith(CdiRunner.class)
@AdditionalClasses(CdiTest.FileReader.class)
public class CdiTest {
  @Inject
  Parser parser;

// Alternative Möglichkeit, um den Testfall auch ohne @AdditionalClasses zum laufen zu bringen
// @Inject
// FileReader fileReader;

  @Test
  public void testParser () {
    parser.parse();
  }

  static class Parser {
    @Inject
    Reader reader;
    public void parse() {
      ...
    }
  }

  interface Reader {
    void read();
  }

  static class FileReader implements Reader {
    public void read() {
...
    }
  }
}

Listing 5 zeigt, wie ein Interceptor mithilfe von @AdditionalClasses getestet werden kann. InterceptedClass ist dabei eine Testklasse, die mit einem Interceptor Binding annotiert wurde. Ohne die @AdditionalClasses-Annotation würde der entsprechende Interceptor nie aufgerufen werden.

Listing 5: Testen eines Interceptors mithilfe von „@additionalClasses“
@RunWith(CdiRunner.class)
@AdditionalClasses(MyInterceptor.class)
public class MyInterceptorTest {

  @Inject
  InterceptedClass interceptedClass;

  @Test
  public void testInterception() {
    Object result = testClass.testInterceptedMethod();
    assertThat...
  }
}

Ein gutes Mittel, um in den zu testenden Klassen Abhängigkeiten zu ersetzen, sind die von CDI bekannten so genannten Alternativen. Mithilfe von Alternativen lassen sich Klassen ändern, die in einen Injection Point injiziert werden. Die als Alternativen deklarierten Klassen müssen dabei entweder eine Subklasse oder eine Implementierung der Klasse am Injection Point sein. Für Unit Tests lassen sich beispielsweise Stubs injizieren, indem man diese Stubs als Alternative für die eigentliche Klasse am Injection Point deklariert. Während Alternativen normalerweise per @Alternative-Annotation deklariert und über die beans.xml aktiviert werden, reicht für CDI-Unit eine Annotation. Mittels der @ActivatedAlternatives-Annotation werden Alternativen für CDI-Unit-Tests in einem Schritt sowohl deklariert als auch aktiviert. In Listing 6 wird gezeigt, wie sich für einen Testfall die eigentlich injizierte Klasse durch eine alternative Klasse ersetzen lässt.

Listing 6
@RunWith(CdiRunner.class)
@ActivatedAlternatives(CdiTest.AlternativeFileReader.class)
public class CdiTest {
  @Inject
  Parser parser;

  @Test
  public void testParserWithAlternative() {
    parser.parse();
  }

  static class Parser {
    // Dank der @ActivatedAlternatives wird zur Laufzeit hier der
    // AlternativeFileReader injected statt des eigentlichen FileReaders.
    @ Inject
    FileReader reader;

    public void parse() {
      ...
    }
  }

  @Alternative
  static class AlternativeFileReader extends FileReader {
    public void read() {
    }
  }
}

CDI-Unit bietet zusätzliche Möglichkeiten, um für Tests so genannte „On-the-Fly-Mocks“ zu erstellen. Derzeit werden sowohl Mockito als auch EasyMock unterstützt. Dazu muss die entsprechende Referenzvariable in der Testklasse mit @Produces und @Mock annotiert werden. Das Setup des Mocks kann dann wie gewohnt innerhalb der Testklassen erfolgen. In Listing 7 zeigen wir, wie die tatsächliche Implementierung einer Klasse durch einen Mock ersetzt werden kann. In seltenen Fällen kann es dabei allerdings passieren, dass es mehrere mögliche Kandidaten zum Injizieren gibt und der CDI-Container den Injection Point nicht eindeutig auflösen kann. In diesen Fällen bietet CDI-Unit die Möglichkeit, zusätzlich die @ProducesAlternative-Annotation zu verwenden. Damit wird der Konflikt aufgelöst.

Listing 7: Testen mit Mocks
@RunWith(CdiRunner.class)
public class CdiTest {

  @Inject
  Parser parser;

// In manchen Fällen benötigt
// @ProducesAlternative
@Produces
  @Mock
  FileReader mockedFileReader;
  
  @Before
  public void setUp(){
    when(mockedFileReader...
  }

  @Test
  public void testWithMock() {
    parser.parse();
  }

  static class Parser {
    // zur Laufzeit wird hier die mockedFileReader injected. 
    @Inject
    FileReader reader;

    public void parse() {
...
    }
  }
}

Mit CDI-Unit steht ein leichtgewichtiges Framework zum Testen von Java-EE- und CDI-Anwendungen zur Verfügung. Es werden keine externen Konfigurationen und XML-Dateien benötigt, die gesamte Konfiguration erfolgt durch Annotationen. Gefallen hat uns die einfache und intuitive Nutzung der angebotenen Möglichkeiten. Die Dokumentation beschränkt sich auf ein Mindestmaß, genügt aber normalen Ansprüchen.

Fazit

Für Entwicklertests im Kontext von Java-EE-/CDI-Anwendungen stehen geeignete Kandidaten in den Startlöchern. Die vorgestellten Frameworks unterstützen den Entwickler gut und schlagen die Brücke zwischen den in der Java-Welt üblichen Testframeworks und dem eingesetzten Technologiestack. Eine Entscheidung müssen die Projektbeteiligten anhand konkreter Randbedingungen fällen.

Mit den beiden vorgestellten Kandidaten ist das Spektrum zwischen einem leichtgewichtigen Ansatz mit CDI-Unit und dem „Arquillian Way of Testing“ mit seinem Grundprinzip „What You Test Is What You Run“ (WYTIWYR) gut abgedeckt. Der Vollständigkeit halber möchten wir eine weitere leichtgewichtige Variante nicht unerwähnt lassen. Mockito bietet die Möglichkeit, mittels @Mock und @InjectMocks Abhängigkeiten zu injizieren. Dieser Weg eignet sich ebenfalls, um CDI-Komponenten ohne Container zu testen, bietet allerdings bei Weitem nicht die Möglichkeiten der anderen Kandidaten.

Abb. 3: Testen in der Java-EE-Welt

Abb. 3: Testen in der Java-EE-Welt

Alle vorgestellten Ansätze (Abb. 3) haben ihre Berechtigung, und wir glauben, dass alle Frameworks ihren Platz im Testkonzert von Java-EE finden. Wer die Wahl hat, hat die Qual. Dieser Satz behält auch hier seine Gültigkeit. Einige Anhaltspunkte, um Entscheidungen zu treffen, möchten wir an dieser Stelle trotzdem geben.

Eine wichtige Frage ist die Menge und die fachliche Komplexität der Geschäftslogik (z. B. Anwendungen in der naturwissenschaftlichen oder technischen Domäne). Viele fachliche Tests mit schneller Rückmeldung an den Entwickler können die Entscheidung zugunsten eines leichtgewichtigen Ansatzes bewegen. Vorteile sind hier das schnelle Feedback und die einfachere Anatomie der Testfälle. Schlussendlich sollte in diesen Fällen der konkrete Container keine Auswirkung auf die Testergebnisse haben. Für Anwendungen, die im Wesentlichen CRUD-Funktionalitäten zur Verfügung stellen, können Tests mit Arquillian direkt gegen die Datenbank eine gute Wahl sein. Das isolierte Testen der Datenzugriffskomponenten mittels eines leichtgewichtigen Ansatzes bringt aus unserer Sicht keine zusätzlichen Vorteile. Ein weiteres gutes Argument ist die Nutzung von Infrastrukturdiensten des Containers (injizierte Mail-Sessions, die im Container konfiguriert werden).

Die Welt ist selten perfekt, und so kann es häufig sinnvoll sein, eine Mischform zu wählen und das richtige Werkzeug auf den jeweils passenden Fall anzuwenden. Mit den vorgestellten Frameworks sollte für jeden Geschmack etwas Passendes dabei sein.

Aufmacherbild: Picture of human hand with magnifying glass via Shutterstock.com / Urheberrecht: Shai_Halud

Geschrieben von
Christian Laboranowitsch
Christian Laboranowitsch
Christian Laboranowitsch ist als Softwareentwickler und Architekt beim IT Beratungsunternehmen BridgingIT GmbH angestellt und schon seit mehr als fünfzehn Jahren in der Softwareentwicklung mit und ohne Java unterwegs.
Philipp Bayer
Philipp Bayer
Philipp Bayer ist als Softwareentwickler und Consultant beim IT-Beratungsunternehmen BridgingIT GmbH angestellt. Er beschäftigt sich seit acht Jahren mit Fragestellungen rund um Java und Webentwicklung, dabei war er in den unterschiedlichsten fachlichen Umfeldern tätig.
Tanja Schmidt
Tanja Schmidt
Tanja Schmidt ist als Softwareentwicklerin und Consultant beim IT-Beratungsunternehmen BridgingIT GmbH angestellt. Sie ist seit neun Jahren in der Softwareentwicklung mit den Schwerpunkten Java und Webtechnologien tätig. E-Mail: tanja.schmidt@bridging-it.de
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: