Datenzentrierte Tests mit Talend: Komponenten- und Integrationstests für Talend-Jobs mit JUnit (Teil 2)

Der erste Teil dieser Blogreihe lieferte einen Überblick über Ebenen, auf denen automatisierte Tests durchgeführt werden können. Neben Unit-Tests, für die Talend bereits Möglichkeiten bereitstellt, wurde besonders auf Komponenten-, Integrations- und End-to-End-Tests eingegangen.

Allein die Tatsache, dass sich ein Talend-Job aus JUnit heraus testen lässt, bietet noch keinen Mehrwert gegenüber der Alternative einen dedizierten Test-Talend-Job zu verwenden. In diesem Teil der Blogreihe sollen nun mit DbUnit und WireMock zwei Techniken aus dem Java-Test-Umfeld vorgestellt werden, die das Testen eines Talend-Jobs vereinfachen.

Techniken zum datenzentrierten Testen aus dem Java-Test-Umfeld – Ein Beispiel

Im folgenden Beispiel wird ein einfacher Testjob verwendet, der von einem REST-Service eine Zahl erhält. Im Anschluss fügt er eine Zeile in eine Datenbank ein und inkrementiert die Werte, welche eine bestimmte Bedingung erfüllt, um die vom REST-Service erhaltene Zahl.

Für die Datenbankzugriffe im Talend-Job wurden die JDBC-Komponenten gewählt, da mit diesen auf eine einfache HSQLDB Testdatenbank für die Tests umgeschaltet werden kann. Falls es nicht möglich ist, einen Job mit einer HSQLDB Datenbank aufgrund fehlender Möglichkeiten, welche die Kompetenzen von HSQLDB übersteigen, zu testen, muss eine dedizierte Testinstanz der Produktionsdatenbank verwendet werden.

Die Klasse zum Testen des Beispiel-Jobs schaut folgendermaßen aus:

public class TalendUnitTest extends DBTestCase
{
    private Server server;
    private static final String  URL = "jdbc:hsqldb:hsql://localhost/mydb;sql.syntax_ora=true";
    public TalendUnitTest(String name)
    {
        super( name );
    }

    ...
}

Die Variable server soll den HSQLDB Server vorhalten, dessen URL hier als Konstante vorliegt. Es wird eine setUp Methode benötigt, die den HSQLDB-Server startet:

protected void setUp() throws Exception {

    server = new org.hsqldb.Server()	            
    server.putPropertiesFromString("database.0=db/mydb;dbname.0=mydb");
    server.start();

    System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "org.hsqldb.jdbcDriver" );
    System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL,  URL);
    System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "sa" )  
    System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "" );

    try {
       getConnection().getConnection().createStatement().execute("create table if not exists test (id integer, pos integer)");
    } catch (Exception e) {
       e.printStackTrace();
    }

    super.setUp();
}

In dieser Methode werden nach dem Start des DB-Servers die von DbUnit benötigten Properties gesetzt und die Tabelle angelegt, die für den Test benötigt wird. Bei komplexeren Schemata sollte die Schemaerstellung ausgelagert werden. Die korrespondierende tearDown Methode schaut so aus:

protected void tearDown() throws Exception {
    super.tearDown();
    server.stop();
}

Die Testdaten werden im Java-Projekt üblicherweise unter src/test/resources abgelegt. DbUnit erwartet außerdem die Implementierung der Methode getDataSet:

protected IDataSet getDataSet() throws Exception {
   return new FlatXmlDataSetBuilder().build(new FileInputStream("src/test/resources/dataset.xml"));
}

Diese Methode wird dazu verwendet, um die DB vor jedem Test automatisch mit den gewünschten Testdaten zu befüllen.

Eine mögliche Testmethode schaut dann folgendermaßen aus:

public void testMe() throws Exception {
    TestTesting talendJob = new TestTesting();
    String [] context=new String[] {};
    talendJob.runJob(context);

    IDataSet databaseDataSet = getConnection().createDataSet();
    ITable actualTable = databaseDataSet.getTable("test");

    IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(new File("src/test/resources/expectedDataSet.xml"));
    ITable expectedTable = expectedDataSet.getTable("test");

    Assertion.assertEquals(expectedTable, actualTable);
}

Nach dem Lauf des Talend-Jobs werden die erwarteten Werte in der Datenbank mit den tatsächlichen Werten verglichen, um die Korrektheit des Jobs zu prüfen.

WireMock als Plattform zum Bauen von Abhängigkeiten

Ein offener Punkt ist noch der REST-Service, den der Beispiel-Job aufruft, um einen Wert abzufragen. WireMock bietet hier eine sehr komfortable Möglichkeit, um eine solche Abhängigkeit zu mocken.

Die Klasse wird dazu um folgende Zeilen erweitert:

Eine neue Variable speichert den WireMockServer:

private WireMockServer wireMockServer;

Am Ende der setUp Methode wird der WireMock Server gestartet:

wireMockServer = new WireMockServer(wireMockConfig().port(8089)); 
wireMockServer.start();

Am Ende der tearDown Methode wird der Server wieder gestoppt:

wireMockServer.stop();

Am Anfang der Testmethode kann nun definiert werden, auf welche Anfragen der WireMock-Server vordefinierte Antworten liefern soll:

wireMockServer.stubFor(get(urlEqualTo("/dbincrement"))
                .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")
                     .withBody("{'increment': 1}")
                ));

Der Talend-Job kann nun per Context-Parameter auf die HSQLDB-Instanz und den WireMock-Server umgebogen warden. Auf diese Weise lassen sich externe Abhängigkeiten leicht mocken und somit lässt sich auch ein komplexer Talend-Job einfach testbar machen.

Preview: Talend Jobs mit inkludiertem REST-Service in Teil 3 der Blogreihe

Nachdem in diesem Teil der Blog-Reihe beschrieben wurde, wie mit DbUnit und WireMock Abhängigkeiten eines Talend-Jobs gemocked werden können, um Komponenten- und Integrationstests zu bauen, beschreibt der nächste Teil, wie Talend-Jobs, die selber einen REST-Service bereitstellen, getestet werden können.

Prodato verbindet.

Autor

Dr. Philipp Baumgärtel
Lead Consultant

philipp.baumgaertel@prodato.de