Der EJB-Standard bringt eine Menge Vorteile mit sich: Concurrency und Security Handling, sowie Skalierbarkeit und deklarative Transaktionen – um nur einige zu nennen. Wenn es allerdings um die Testbarkeit geht, sieht sich der Entwickler einigen Unwägbarkeiten ausgesetzt. Ich möchte hier meine Erfahrungen in Sachen Testen von EJB-Applikationen teilen und verschiedene Optionen nennen und bewerten.
Keep it simple – POJO-Style!
Die sicherlich einfachste Variante und gleichzeitig die effizienteste und offensichtlichste! Seit EJB 3.0 ist eine EJB einfach nur ein POJO – damit ist es möglich, ganz einfache Unittests zu schreiben, um die implementierte Logik zu testen. Diese Art von Tests hat viele Vorteile: Sie sind schnell ausführbar, simpel in der Entwicklung und sie benötigen minimalen Support durch andere Frameworks oder Server. Anders gesagt: Was immer auf dieser Ebene testbar ist, kann man nicht effizienter testen.
Reicht das nicht?
Die Antwort auf die Frage ist einfach: Nein.
Kein Autohersteller der Welt würde, nachdem er die einzelnen Komponenten des Fahrzeugs getestet hat, das Ganze einfach auf gut Glück zusammenbauen und dann darauf hoffen, dass die Teile auch richtig zusammengebaut wurden und wie gewünscht als Gesamtheit funktionieren. Das gilt natürlich ebenfalls für jegliche Software, egal ob sie EJBs verwendet oder nicht. Aber bei EJBs kommt hier noch ein weiterer wichtiger Aspekt hinzu: Sie sind nun mal nicht nur einfache POJOs, sondern sie entfalten, konfiguriert durch Annotationen und Deployment-Deskriptoren, erst auf dem Applikationsserver ihr „wahres Ich“.
Es gibt die Meinung, dass man eben dieses Verhalten nicht testen sollte – schließlich handelt es sich dabei um den EJB-Container und damit um Software, die man weder selbst verantwortet noch verändern kann (zumindest in den meisten Fällen). Weiterhin ist dieser Container zertifiziert und muss daher korrekt funktionieren – Tests im Applikationsserver sind also überflüssig?
Meiner Meinung nach sind diese Tests nötig und nützlich. Das hat vor allem einen Grund: Selbst wenn der Applikationsserver keinen Makel hat, so kann man als Entwickler sehr wohl bei der Verwendung seiner Funktionalität Fehler machen. Das muss nicht einmal ein logischer Fehler sein – vielleicht hat man ein Konzept oder einen Teil der bereitgestellten Funktionalität nicht verstanden und verwendet ihn daher falsch. Ein einfacher Unittest ohne den Applikationsserver deckt eine solche Fehleinschätzung nicht auf. Der andere Grund für den Wert dieser Tests ist klar: Keine Software, die so komplex und funktionsreich ist wie ein Applikationsserver, ist auch fehlerlos. Und auch wenn diese Fehler sicherlich weitaus weniger wahrscheinlich sind als ein Fehler im eigenen Code, so kommen diese Fehler leider doch vor. Und die Aussage: „Eigentlich war es nicht mein Fehler, sondern ein Fehler im Applikationsserver!“ ist dann zwar korrekt, aber hört sich nicht gut an, wenn der Bug es erstmal bis in Produktion geschafft hat.
Die nächste Ebene
Es ist also notwendig, das Verhalten der EJBs im Applikationsserver zu testen. Das einfachste Szenario für einen solchen Test ist es, die komplette Anwendung zu deployen und diese im Test beispielsweise über RMI anzusprechen. Ein solcher Systemtest ist sowieso nötig und ist für kleinere Applikationen völlig ausreichend. Allerdings hat dieser Ansatz auch seine Grenzen: Wenn es sich um eine große Applikation mit sehr vielen EJBs handelt, ist es schwierig, eine hohe Testabdeckung zu erzielen, und ein fehlgeschlagener Test schränkt die Fehlerquelle nur in geringem Maße ein. Darüber hinaus gibt es Funktionalität, die gar nicht von außerhalb des Applikationsservers angesprochen werden sollte. Remote Interfaces nur für Integrationstests zu deployen, ist dann nicht nur unpraktisch, sondern sorgt unter Umständen auch für ungewollte Sicherheitslöcher. Es gibt also durchaus den Bedarf, EJBs nicht nur als einfache POJOs, aber auch nicht gleich innerhalb der kompletten Anwendung zu testen.
Embedded Container
Der Bedarf wurde auch in der Expert Group erkannt. Die Lösung, die dort gefunden und spezifiziert wurde, sieht einen Embedded EJB Container vor, der sich in derselben JVM starten lässt, in der auch die Tests laufen. Dieses Vorgehen hat einen wesentlichen Vorteil: Es ist wesentlich schneller als ein Deployment in einem externen Applikationsserver und die Kommunikation mit demselben. Da nur eine JVM verwendet wird, können die Beans einfach via JNDI gefunden und getestet werden. Leider hat diese Lösung einige schwerwiegende Nachteile:
- Es gibt kaum Applikationsserver, für die ein Embedded Container zur Verfügung steht: Der einzige mir bekannte, populäre Applikationsserver, der einen finalen, stabilen Embedded Container besitzt, ist Glassfish.
- Alle Embedded Container besitzen einen begrenzten Funktionsumfang. Und wenn die genutzte Funktionalität nicht zur Verfügung steht, kann sie natürlich auch nicht getestet werden.
- Der schwerwiegendste Nachteil: Die Tests sind nur unter der Annahme nützlich, dass sich der produktiv verwendete Applikationsserver genauso verhält wie der Embedded Container, den man für die Tests verwendet.
Vor allem aufgrund des letzten Punktes rate ich von der Verwendung eines solchen Embedded Containers ab (Es sei denn, dieser stellt gleichzeitig den Container für die Produktion dar – was normalerweise nicht der Fall sein dürfte).
Testen mit MicroDeployments
Die letzte Option ist das Testen mit MicroDeployments: Statt der ganzen Anwendung werden nur Teile der Applikation deployt – eben jene Beans, die einem Test unterzogen werden sollen. Genau diesen Weg geht Arquillian, ein Projekt aus der JBoss Community. Arquillian lässt sich aber mit allen gängigen Applikationsservern verwenden (eine Liste der unterstützten Container findet sich hier). Ein Beispiel sagt mehr als tausend Worte, also sehen wir uns einmal einen einfachen Test mit Arquillian an:
import javax.ejb.EJB; import org.jboss.arquillian.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(Arquillian.class) public class SimpleArquillianTest { @Deployment public static JavaArchive createTestArchive() { return ShrinkWrap.create(JavaArchive.class, "test.jar") .addClasses(SomeInterface.class, SomeInterfaceBean.class); } @EJB private SomeInterface someBean; @Test public void shouldBeAbleToInjectEJB() throws Exception { // We can use someBean here and test it. } }
Der Testrunner von Arquillian wird beim Starten des Tests zunächst mal das Deployment mit createTestArchive
erstellen, auf dem konfigurierten Applikationsserver deployen und anschließend den Test durchführen. Arquillian nimmt also viel Arbeit beim Testen ab.
Allerdings haben diese Tests zwei Nachteile:
- Der Test ist wesentlich langsamer als ein „normaler“ Unittest, da immer ein Deployment und Undeployment vorgenommen werden müssen.
- Man muss alle Abhängigkeiten für das Deployment angeben, was den Aufwand des Tests erheblich erhöht.
Diese Tests sind also aufwändiger als normale Unittests und können wegen Ihrer längeren Ausführungszeit weniger häufig benutzt werden.
Aber in vielen Fällen können diese Tests trotzdem den Aufwand wert sein, sie zu schreiben. In der Mitte zwischen System- und Unittests sind sie in meinen Augen in den meisten Fällen die einzige sinnvolle Option.
Und sonst?
Vielleicht vermissen Sie als Leser hier die eine oder andere Möglichkeit des Testens oder haben eine andere Ergänzung oder schlichtweg eine andere Meinung zu dem Thema? Dann freue ich mich über Ihren Kommentar!
Da mittlerweile JBoss 7 sowie Glassfish in wenigen Sekunden starten und man die Tests eher auf einem CI Server ausführt, kann man ja gleich mit „vollwertigem“ Server testen und diesen bei Bedarf aus dem Test Job starten und stoppen.
P.S. Und klares JA zu Integrationstest! ;)