Introduzione a DBUnit

1. Introduzione

In questo tutorial, daremo un'occhiata a DBUnit, uno strumento di unit test utilizzato per testare le interazioni del database relazionale in Java.

Vedremo come ci aiuta a portare il nostro database a uno stato noto e ad affermarlo rispetto a uno stato previsto.

2. Dipendenze

Innanzitutto, possiamo aggiungere DBUnit al nostro progetto da Maven Central aggiungendo la dipendenza dbunit al nostro pom.xml :

 org.dbunit dbunit 2.7.0 test 

Possiamo cercare la versione più recente su Maven Central.

3. Hello World Example

Successivamente, definiamo uno schema di database:

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. Definizione del contenuto iniziale del database

DBUnit ci consente di definire e caricare il nostro set di dati di test in un semplice modo dichiarativo .

Definiamo ogni riga della tabella con un elemento XML, in cui il nome del tag è un nome della tabella e i nomi ei valori degli attributi vengono associati rispettivamente ai nomi e ai valori delle colonne. I dati della riga possono essere creati per più tabelle. Dobbiamo implementare il metodo getDataSet () di DataSourceBasedDBTestCase per definire il set di dati iniziale, dove possiamo usare FlatXmlDataSetBuilder per fare riferimento al nostro file XML:

data.xml :

3.2. Inizializzazione della connessione al database e dello schema

Ora che abbiamo il nostro schema, dobbiamo inizializzare il nostro database.

Dobbiamo estendere la classe DataSourceBasedDBTestCase e inizializzare lo schema del database nel suo metodo getDataSource () :

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")); } }

Qui, abbiamo passato un file SQL a un database in memoria H2 nella sua stringa di connessione. Se vogliamo testare su altri database, avremo bisogno di fornire la nostra implementazione personalizzata per questo.

Tieni presente che , nel nostro esempio, DBUnit reinizializzerà il database con i dati di test forniti prima dell'esecuzione di ogni metodo di test .

Esistono diversi modi per configurarlo tramite get SetUpOperation e get TearDownOperation :

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

L' operazione REFRESH indica a DBUnit di aggiornare tutti i suoi dati. Ciò garantirà che tutte le cache siano svuotate e il nostro unit test non abbia alcuna influenza da un altro unit test. L' operazione DELETE_ALL garantisce che tutti i dati vengano rimossi alla fine di ogni unit test. Nel nostro caso, stiamo dicendo a DBUnit che durante la configurazione, utilizzando l' implementazione del metodo getSetUpOperation , aggiorneremo tutte le cache. Infine, diciamo a DBUnit di rimuovere tutti i dati durante l'operazione di smontaggio utilizzando l' implementazione del metodo getTearDownOperation .

3.3. Confronto tra lo stato previsto e lo stato effettivo

Ora, esaminiamo il nostro vero caso di test. Per questo primo test, manterremo le cose semplici: caricheremo il nostro set di dati previsto e lo confronteremo con il set di dati recuperato dalla nostra connessione DB:

@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. Immergiti profondamente nelle asserzioni

Nella sezione precedente, abbiamo visto un esempio di base di confronto del contenuto effettivo di una tabella con un set di dati previsto. Ora scopriremo il supporto di DBUnit per la personalizzazione delle asserzioni dei dati.

4.1. Asserire con una query SQL

Un modo semplice per verificare lo stato effettivo è con una query SQL .

In questo esempio, inseriremo un nuovo record nella tabella CLIENTS, quindi verificheremo il contenuto della riga appena creata. Abbiamo definito l'output previsto in un file XML separato ed abbiamo estratto il valore effettivo della riga da una query SQL:

@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" }); } }

Il metodo getConnection () della classe antenata DBTestCase restituisce una rappresentazione specifica di DBUnit della connessione dell'origine dati ( un'istanza IDatabaseConnection ). Il metodo createQueryTable () di IDatabaseConnection può essere utilizzato per recuperare i dati effettivi dal database , per il confronto con lo stato del database previsto, utilizzando il metodo Assertion.assertEquals () . La query SQL passata a createQueryTable () è la query che vogliamo testare. Restituisce un'istanza di Table che usiamo per fare la nostra affermazione.

4.2. Ignorare le colonne

A volte nei test del database, vogliamo ignorare alcune colonne delle tabelle effettive . Di solito si tratta di valori generati automaticamente che non possiamo controllare rigorosamente, come chiavi primarie generate o timestamp correnti .

Potremmo farlo omettendo le colonne dalle clausole SELECT nelle query SQL, ma DBUnit fornisce un'utilità più conveniente per ottenere ciò. Con i metodi statici della classe DefaultColumnFilter possiamo creare una nuova istanza ITable da una esistente escludendo alcune delle colonne , come mostrato qui:

@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. Indagare su più guasti

Se DBUnit trova un valore errato, genera immediatamente un AssertionError .

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)

È importante notare che a questo punto ci aspettavamo che il nuovo articolo avesse un prezzo di 199,99 ma era 1000000,0. Quindi vediamo che la data di produzione sarà il 23/03/2019, ma alla fine è stata nulla. Infine, l'oggetto previsto era una collana e invece abbiamo ottenuto una batteria.

5. conclusione

In questo articolo, abbiamo visto come DBUnit fornisce un modo dichiarativo per definire i dati di test per testare i livelli di accesso ai dati delle applicazioni Java.

Come sempre, il codice sorgente completo per gli esempi è disponibile su GitHub.