Рубрики
Без рубрики

Введение в DBUnit

Узнайте, как настроить состояния базы данных для модульных тестов с помощью DBUnit.

Автор оригинала: baeldung.

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() метод Data Source Based DBTestCase для определения исходного набора данных, где мы можем использовать FlatXmlDataSetBuilder для ссылки на наш XML-файл:

data.xml :



    
    
    
    
    
    

3.2. Инициализация подключения к базе данных и Схемы

Теперь, когда у нас есть наша схема, мы должны инициализировать нашу базу данных.

Мы должны расширить класс DBTestCase на основе источника данных и инициализировать схему базы данных в его методе 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 SetUp Operation и 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. Сравнение Ожидаемого и Фактического состояния

Теперь давайте рассмотрим наш реальный тестовый случай. Для этого первого теста мы будем держать его простым – мы загрузим наш ожидаемый набор данных и сравним его с набором данных, полученным из нашего подключения к БД:

@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 ancestor возвращает специфичное для DBUnit представление соединения с источником данных (экземпляр IDatabaseConnection ). Метод |/createQueryTable() IDatabaseConnection может быть использован для извлечения фактических данных из базы данных , для сравнения с ожидаемым состоянием базы данных, используя метод Assertion.assertEquals () . SQL – запрос, переданный в createQueryTable () , является запросом, который мы хотим протестировать. Он возвращает экземпляр Table , который мы используем для создания нашего утверждения.

4.2. Игнорирование Столбцов

Иногда в тестах баз данных мы хотим игнорировать некоторые столбцы фактических таблиц . Обычно это автоматически сгенерированные значения, которые мы не можем строго контролировать, например сгенерированные первичные ключи или текущие временные метки .

Мы могли бы сделать это, опуская столбцы из предложений SELECT в SQL-запросах, но DBUnit предоставляет более удобную утилиту для достижения этой цели. С помощью статических методов класса DefaultColumnFilter мы можем создать новый экземпляр Table из существующего , исключив некоторые столбцы , как показано здесь:

@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 .

В конкретных случаях мы можем использовать класс Diff Collecting FailureHandler , который мы можем передать методу Assertion.assertEquals() в качестве третьего аргумента.

Этот обработчик сбоев будет собирать все сбои вместо того, чтобы останавливаться на первом, что означает, что метод Assertion.assertEquals () всегда будет успешным, если мы используем DiffCollectingFailureHandler . Поэтому нам придется программно проверить, не обнаружил ли обработчик каких-либо ошибок:

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

Кроме того, обработчик предоставляет сбои в виде экземпляров Difference , что позволяет нам форматировать ошибки.

После запуска теста мы получаем отформатированный отчет:

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 .