Eine wesentliche Herausforderung beim Schreiben von Integrationstest für integrierte Software-Systeme ist die Kommunikation mit externen Systemen. In machen Fällen bieten die externen Systeme eine Test- bzw. QS-Umgebung gegen die getestet werden kann. Aber auch in diesem Fall kann nicht sichergestellt werden, dass die externen Systeme immer auf die gleiche Weise reagieren. Die geschriebenen Testfälle sind somit nicht stabil und neigen dazu fehlzuschlagen, auch wenn kein Fehler im der eigenen Software existiert – die Ergebnisse sind nicht deterministisch.
Lange Zeit habe ich nach einem Lösungsansatz für dieses Problem gesucht und bin schließlich auf das Citrus Framework gestossen. Vor Allem im Zusammenhang mit einer größeren Integrationsprojekt (i.e.S. Enterprise Service Bus / ESB) hat sich dieses Framework für mich als unentbehrlich herausgestellt. Ich möchte darum hier einen kurzen Überblick geben.
Wie funktioniert Citrus?
Typischer Weise sieht ein Integrationsszenario an dem externe Systeme beteiligt sind so aus:
Mit Hilfe von Citrus wird es möglich, die Verhaltensweise und die Schnittstellen des externen System komplett zu simulieren. Citrus bietet diverse Protokolle und Formate an:
- Protokolle
- SOAP
- JMS
- REST
- Formate
- JSON
- XML
- CSV
Citrus kann sowohl die Rolle des Clients – aufrufendes System – als auch die Rolle des Servers – aufgerufenes System – einnehmen.
Einrichtung mit Maven
Das Citrus-Framework kann am einfachsten mit Hilfe einer einfachen Maven-Dependency eingebunden werden.
Zunächst müssen die Repositories eingebunden werden:
Dann werden die Dependencies hinzugefügt:
Testfälle
Ein Testfall ist immer nach dem folgenden Muster aufgebaut:
Zunächst wird eine Nachricht an zutestende System geschickt und dann von einem externen Zielsystem empfangen. Sowohl der aufrufende Client als auch das externe System wird durch Citrus-Endpunkte simuliert. Zumeist werden mehrere Endpunkte in einem Ablauf verwendet.
Die Implementierung von Testfälle ist mittels XML-Konfiguration oder Java möglich. Ich favorisiere die Java-Implementierung auf Grund der Code-Überprüfung.
Implementierung eines Testfalls
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.testng.annotations.Test; <span class="hl-keyword">import</span> com.consol.citrus.dsl.testng.TestNGCitrusTestDesigner; import com.consol.citrus.dsl.annotations.CitrusTest; import com.consol.citrus.http.client.HttpClient; import com.consol.citrus.jms.endpoint.JmsSyncEndpoint; import com.consol.citrus.message.MessageType; public class HTTPTest extends TestNGCitrusTestDesigner { @Autowired // HTTP-Client; eine Anfrage wird an einer HTTP-Endpunkt gesendet und eine Antwort empfangen private HttpClient serviceClient; @Autowired // Synchroner JMS-Endpunkt; eine Nachricht wird in aus einer Queue empfangen und über eine Reply-Queue wird die Antwort synchron gesendet protected JmsSyncEndpoint jmsQueue; @Test @CitrusTest public void testArtikelstammNormal() { // Senden: Die Anfrage an einen HTTP-Enpunkt senden send(servicesHttpClientDez).http().method(HttpMethod.POST).path("/services/testService").messageType(MessageType.PLAINTEXT) .fork(true).payload("This is the request"); // Empfangen: Die Anfrage in einer JMS-Queue empfangen receive(queue) .messageType(MessageType.PLAINTEXT) .payload("This is the request") .extractFromHeader("citrus_jms_correlationId", "correlationId"); // Senden: Die Antwort synchron an die JMS-Reply-Queue senden send(queue).messageType(MessageType.PLAINTEXT).header("citrus_jms_correlationId", "${correlationId}").payload("This is the response"); // Empfangen: Die Antwort über den HTTP-Client empfangen receive(servicesHttpClientDez) .messageType(MessageType.PLAINTEXT) .payload("This is the response"); } }