Einführung in DBUnit

1. Einleitung

In diesem Tutorial werfen wir einen Blick auf DBUnit, ein Unit-Test-Tool zum Testen relationaler Datenbankinteraktionen in Java.

Wir werden sehen, wie es uns hilft, unsere Datenbank in einen bekannten Zustand zu versetzen und gegen einen erwarteten Zustand vorzugehen.

2. Abhängigkeiten

Zunächst können wir DBUnit von Maven Central zu unserem Projekt hinzufügen, indem wir die dbunit- Abhängigkeit zu unserer pom.xml hinzufügen :

 org.dbunit dbunit 2.7.0 test 

Wir können die neueste Version von Maven Central nachschlagen.

3. Hallo Welt Beispiel

Als nächstes definieren wir ein Datenbankschema:

schema.sql :

CREATE TABLE IF NOT EXISTS CLIENTS ( `id` int AUTO_INCREMENT NOT NULL, `first_name` varchar(100) NOT NULL, `last_name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS ITEMS ( `id` int AUTO_INCREMENT NOT NULL, `title` varchar(100) NOT NULL, `produced` date, `price` float, PRIMARY KEY (`id`) ); 

3.1. Definieren des anfänglichen Datenbankinhalts

Mit DBUnit können wir unseren Testdatensatz auf einfache deklarative Weise definieren und laden .

Wir definieren jede Tabellenzeile mit einem XML-Element, wobei der Tag-Name ein Tabellenname ist und Attributnamen und -werte Spaltennamen bzw. -werten zugeordnet werden. Die Zeilendaten können für mehrere Tabellen erstellt werden. Wir müssen die getDataSet () -Methode von DataSourceBasedDBTestCase implementieren , um den anfänglichen Datensatz zu definieren, wobei wir den FlatXmlDataSetBuilder verwenden können , um auf unsere XML-Datei zu verweisen:

data.xml :

3.2. Initialisieren der Datenbankverbindung und des Datenbankschemas

Nachdem wir unser Schema haben, müssen wir unsere Datenbank initialisieren.

Wir müssen die DataSourceBasedDBTestCase- Klasse erweitern und das Datenbankschema in ihrer getDataSource () -Methode initialisieren :

DataSourceDBUnitTest.java :

public class DataSourceDBUnitTest extends DataSourceBasedDBTestCase { @Override protected DataSource getDataSource() { JdbcDataSource dataSource = new JdbcDataSource(); dataSource.setURL( "jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;init=runscript from 'classpath:schema.sql'"); dataSource.setUser("sa"); dataSource.setPassword("sa"); return dataSource; } @Override protected IDataSet getDataSet() throws Exception { return new FlatXmlDataSetBuilder().build(getClass().getClassLoader() .getResourceAsStream("data.xml")); } }

Hier haben wir eine SQL-Datei in ihrer Verbindungszeichenfolge an eine speicherinterne H2-Datenbank übergeben. Wenn wir in anderen Datenbanken testen möchten, müssen wir unsere benutzerdefinierte Implementierung dafür bereitstellen.

Beachten Sie, dass , in unserem Beispiel DBUnit wird die Datenbank mit den gegebenen Testdaten vor jeder Testmethode Ausführung neu zu initialisieren .

Es gibt mehrere Möglichkeiten, dies über get SetUpOperation und get TearDownOperation zu konfigurieren :

@Override protected DatabaseOperation getSetUpOperation() { return DatabaseOperation.REFRESH; } @Override protected DatabaseOperation getTearDownOperation() { return DatabaseOperation.DELETE_ALL; }

Die Operation REFRESH weist DBUnit an, alle Daten zu aktualisieren . Dadurch wird sichergestellt, dass alle Caches geleert werden und unser Komponententest keinen Einfluss von einem anderen Komponententest erhält. Die Operation DELETE_ALL stellt sicher, dass alle Daten am Ende jedes Komponententests entfernt werden. In unserem Fall teilen wir DBUnit mit, dass während der Einrichtung mithilfe der Implementierung der Methode getSetUpOperation alle Caches aktualisiert werden. Schließlich weisen wir DBUnit an, alle Daten während des Teardown-Vorgangs mithilfe der Implementierung der Methode getTearDownOperation zu entfernen .

3.3. Vergleich des erwarteten und des tatsächlichen Zustands

Lassen Sie uns nun unseren tatsächlichen Testfall untersuchen. Für diesen ersten Test halten wir es einfach - wir laden unseren erwarteten Datensatz und vergleichen ihn mit dem Datensatz, der von unserer DB-Verbindung abgerufen wurde:

@Test public void givenDataSetEmptySchema_whenDataSetCreated_thenTablesAreEqual() throws Exception { IDataSet expectedDataSet = getDataSet(); ITable expectedTable = expectedDataSet.getTable("CLIENTS"); IDataSet databaseDataSet = getConnection().createDataSet(); ITable actualTable = databaseDataSet.getTable("CLIENTS"); assertEquals(expectedTable, actualTable); }

4. Tauchen Sie tief in Behauptungen ein

Im vorherigen Abschnitt haben wir ein grundlegendes Beispiel für den Vergleich des tatsächlichen Inhalts einer Tabelle mit einem erwarteten Datensatz gesehen. Jetzt werden wir die Unterstützung von DBUnit für das Anpassen von Datenzusicherungen entdecken.

4.1. Durchsetzen mit einer SQL-Abfrage

Eine einfache Möglichkeit, den aktuellen Status zu überprüfen, ist eine SQL-Abfrage .

In diesem Beispiel fügen wir einen neuen Datensatz in die CLIENTS-Tabelle ein und überprüfen dann den Inhalt der neu erstellten Zeile. Wir haben die erwartete Ausgabe in einer separaten XML-Datei definiert und den tatsächlichen Zeilenwert durch eine SQL-Abfrage extrahiert:

@Test public void givenDataSet_whenInsert_thenTableHasNewClient() throws Exception { try (InputStream is = getClass().getClassLoader().getResourceAsStream("dbunit/expected-user.xml")) { IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is); ITable expectedTable = expectedDataSet.getTable("CLIENTS"); Connection conn = getDataSource().getConnection(); conn.createStatement() .executeUpdate( "INSERT INTO CLIENTS (first_name, last_name) VALUES ('John', 'Jansen')"); ITable actualData = getConnection() .createQueryTable( "result_name", "SELECT * FROM CLIENTS WHERE last_name="Jansen""); assertEqualsIgnoreCols(expectedTable, actualData, new String[] { "id" }); } }

Die Methode getConnection () der DBTestCase-Vorgängerklasse gibt eine DBUnit-spezifische Darstellung der Datenquellenverbindung (eine IDatabaseConnection- Instanz) zurück. Die createQueryTable () -Methode der IDatabaseConnection kann verwendet werden, um mithilfe der Assertion.assertEquals () -Methode tatsächliche Daten aus der Datenbank abzurufen und mit dem erwarteten Datenbankstatus zu vergleichen . Die an createQueryTable () übergebene SQL-Abfrage ist die Abfrage, die wir testen möchten. Es gibt eine Tabelleninstanz zurück , mit der wir unsere Aussage treffen.

4.2. Spalten ignorieren

Manchmal möchten wir in Datenbanktests einige Spalten der tatsächlichen Tabellen ignorieren . Dies sind normalerweise automatisch generierte Werte, die wir nicht streng kontrollieren können, wie generierte Primärschlüssel oder aktuelle Zeitstempel .

Wir könnten dies tun, indem wir die Spalten in den SELECT-Klauseln in den SQL-Abfragen weglassen , aber DBUnit bietet ein bequemeres Dienstprogramm, um dies zu erreichen. Mit den statischen Methoden der DefaultColumnFilter- Klasse können wir eine neue ITable- Instanz aus einer vorhandenen erstellen, indem wir einige der Spalten ausschließen , wie hier gezeigt:

@Test public void givenDataSet_whenInsert_thenGetResultsAreStillEqualIfIgnoringColumnsWithDifferentProduced() throws Exception { Connection connection = tester.getConnection().getConnection(); String[] excludedColumns = { "id", "produced" }; try (InputStream is = getClass().getClassLoader() .getResourceAsStream("dbunit/expected-ignoring-registered_at.xml")) { IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is); ITable expectedTable = excludedColumnsTable(expectedDataSet.getTable("ITEMS"), excludedColumns); connection.createStatement() .executeUpdate("INSERT INTO ITEMS (title, price, produced) VALUES('Necklace', 199.99, now())"); IDataSet databaseDataSet = tester.getConnection().createDataSet(); ITable actualTable = excludedColumnsTable(databaseDataSet.getTable("ITEMS"), excludedColumns); assertEquals(expectedTable, actualTable); } }

4.3. Untersuchung mehrerer Fehler

Wenn DBUnit einen falschen Wert findet, wird sofort ein AssertionError ausgelöst .

In specific cases, we can use the DiffCollectingFailureHandler class, which we can pass to the Assertion.assertEquals() method as a third argument.

This failure handler will collect all failures instead of stopping on the first one, meaning that the Assertion.assertEquals() method will always succeed if we use the DiffCollectingFailureHandler. Therefore, we'll have to programmatically check if the handler found any errors:

@Test public void givenDataSet_whenInsertUnexpectedData_thenFailOnAllUnexpectedValues() throws Exception { try (InputStream is = getClass().getClassLoader() .getResourceAsStream("dbunit/expected-multiple-failures.xml")) { IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is); ITable expectedTable = expectedDataSet.getTable("ITEMS"); Connection conn = getDataSource().getConnection(); DiffCollectingFailureHandler collectingHandler = new DiffCollectingFailureHandler(); conn.createStatement() .executeUpdate("INSERT INTO ITEMS (title, price) VALUES ('Battery', '1000000')"); ITable actualData = getConnection().createDataSet().getTable("ITEMS"); assertEquals(expectedTable, actualData, collectingHandler); if (!collectingHandler.getDiffList().isEmpty()) { String message = (String) collectingHandler.getDiffList() .stream() .map(d -> formatDifference((Difference) d)) .collect(joining("\n")); logger.error(() -> message); } } } private static String formatDifference(Difference diff) { return "expected value in " + diff.getExpectedTable() .getTableMetaData() .getTableName() + "." + diff.getColumnName() + " row " + diff.getRowIndex() + ":" + diff.getExpectedValue() + ", but was: " + diff.getActualValue(); }

Furthermore, the handler provides the failures in the form of Difference instances, which lets us format the errors.

After running the test we get a formatted report:

java.lang.AssertionError: expected value in ITEMS.price row 5:199.99, but was: 1000000.0 expected value in ITEMS.produced row 5:2019-03-23, but was: null expected value in ITEMS.title row 5:Necklace, but was: Battery at com.baeldung.dbunit.DataSourceDBUnitTest.givenDataSet_whenInsertUnexpectedData_thenFailOnAllUnexpectedValues(DataSourceDBUnitTest.java:91)

Es ist wichtig zu beachten, dass wir zu diesem Zeitpunkt einen Preis von 199,99 für den neuen Artikel erwartet haben, der jedoch 1000000,0 betrug. Dann sehen wir, dass das Produktionsdatum 2019-03-23 ​​sein sollte, aber am Ende war es null. Schließlich war der erwartete Gegenstand eine Halskette und stattdessen bekamen wir eine Batterie.

5. Schlussfolgerung

In diesem Artikel haben wir gesehen, wie DBUnit eine deklarative Methode zum Definieren von Testdaten zum Testen von Datenzugriffsschichten von Java-Anwendungen bietet .

Wie immer ist der vollständige Quellcode für die Beispiele auf GitHub verfügbar.