Въведение в DBUnit

1. Въведение

В този урок ще разгледаме DBUnit, инструмент за модулно тестване, използван за тестване на взаимодействията на релационни бази данни в Java.

Ще видим как това ни помага да стигнем нашата база данни до известно състояние и да отстояваме очакваното състояние.

2. Зависимости

Първо, можем да добавим DBUnit към нашия проект от Maven Central, като добавим зависимостта dbunit към нашия pom.xml :

 org.dbunit dbunit 2.7.0 test 

Можем да потърсим най-новата версия на Maven Central.

3. Пример за Hello World

След това нека дефинираме схема на базата данни:

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. Определяне на първоначалното съдържание на базата данни

DBUnit ни позволява да дефинираме и заредим нашия тестов набор от данни по прост декларативен начин .

Дефинираме всеки ред на таблица с един XML елемент, където името на маркера е име на таблица, а имената на атрибутите и стойностите съответстват съответно на имена и стойности на колони. Данните за редове могат да бъдат създадени за множество таблици. Трябва да приложим метода getDataSet () на DataSourceBasedDBTestCase, за да дефинираме първоначалния набор от данни, където можем да използваме FlatXmlDataSetBuilder за препратка към нашия XML файл:

data.xml :

3.2. Инициализиране на връзката и схемата на базата данни

Сега, когато имаме нашата схема, трябва да инициализираме нашата база данни.

Трябва да разширим класа DataSourceBasedDBTestCase и да инициализираме схемата на базата данни в нейния метод 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")); } }

Тук предадохме SQL файл на база данни H2 в паметта в неговия низ за връзка. Ако искаме да тестваме на други бази данни, ще трябва да предоставим нашето персонализирано изпълнение за него.

Имайте предвид, че , в нашия пример, DBUnit ще преинициализира базата данни с дадените данни от изпитвания преди всяко изпълнение метод за изпитване .

Има няколко начина, за да конфигурирате това чрез GET SetUpOperation и GET TearDownOperation :

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

В REFRESH операцията, казва DBUnit да обновите всички свои данни. Това ще гарантира, че всички кешове са изчистени и нашият модулен тест няма да има влияние от друг модулен тест. На DELETE_ALL осигурява работата, че всички данни, които се премахва в края на всяка единица тест. В нашия случай казваме на DBUnit, че по време на настройката, използвайки метода getSetUpOperation , ще обновим всички кешове. И накрая, казваме на DBUnit да премахне всички данни по време на операцията по разкъсване, използвайки метода getTearDownOperation .

3.3. Сравняване на очакваното състояние и действителното състояние

Сега, нека разгледаме реалния ни тестов случай. За този първи тест ще го улесним - ще заредим очаквания ни набор от данни и ще го сравним с набора от данни, извлечен от нашата 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. Дълбоко потапяне в твърдения

В предишния раздел видяхме основен пример за сравняване на действителното съдържание на таблица с очакван набор от данни. Сега ще открием поддръжката на DBUnit за персонализиране на твърдения за данни.

4.1. Утвърждаване с SQL заявка

Лесен начин за проверка на действителното състояние е чрез SQL заявка .

В този пример ще вмъкнем нов запис в таблицата КЛИЕНТИ, след което ще проверим съдържанието на новосъздадения ред. Определихме очакваната продукция в отделен XML файл и извлекохме действителната стойност на реда чрез 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" }); } }

Методът getConnection () на класа предшественик DBTestCase връща специфично за DBUnit представяне на връзката към източника на данни ( екземпляр IDatabaseConnection ). Методът createQueryTable () на IDatabaseConnection може да се използва за извличане на действителни данни от базата данни , за сравнение с очакваното състояние на базата данни, като се използва методът Assertion.assertEquals () . SQL заявката, предадена на createQueryTable (), е заявката, която искаме да тестваме. Той връща екземпляр на таблица , който използваме, за да направим нашето твърдение.

4.2. Пренебрегване на колони

Понякога в тестовете на база данни искаме да игнорираме някои колони от действителните таблици . Това обикновено са автоматично генерирани стойности, които не можем да контролираме стриктно, като генерирани първични ключове или текущи времеви марки .

Можем да направим това, като пропуснем колоните от клаузите SELECT в SQL заявките, но DBUnit предоставя по-удобна помощна програма за постигане на това. Със статичните методи на класа DefaultColumnFilter можем да създадем нов ITable екземпляр от съществуващ, като изключим някои от колоните , както е показано тук:

@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. Разследване на множество откази

Ако DBUnit открие неправилна стойност, тогава незабавно изхвърля 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)

Важно е да се отбележи, че към този момент очаквахме новият артикул да има цена 199,99, но беше 1000000,0. Тогава виждаме, че датата на производство е 2019-03-23, но в крайна сметка тя е нула. И накрая, очакваният артикул беше Колие и вместо това получихме батерия.

5. Заключение

В тази статия видяхме как DBUnit предоставя декларативен начин за дефиниране на тестови данни за тестване на слоевете за достъп до данни на Java приложения.

Както винаги, пълният изходен код за примерите е достъпен в GitHub.