Основы тестирования с помощью Java фреймворка DBUnit

💡 Усі статті, обговорення, новини про Java — в одному місці. Приєднуйтесь до Java спільноти!

DBUnit logoDBUnit — Java open source фреймворк (расширение JUnit), который приводит базу данных в определенное состояние между вызовами тестов.

Если вам необходимо перед вызовом каждого теста загружать данные в базу данных, а после вызова тестируемой функциональности, которая модифицирует данные в базе данных, проверять правильность изменений — DBUnit будет хорошим выбором для решения подобных задач.

Как использовать DBUnit

Предположим, что нам необходимо протестировать метод, который запускается планировщиком задач в начале нового года на одном из предприятий:

void updateAllSalaries()

Логика данного метода рассчитывает новое значение зарплаты каждого сотрудника предприятия, основываясь на различных данных (к примеру, опыт работы, отзывы коллег, выполнение задач в поставленные сроки), которые запрашиваются из различных таблиц, и изменят зарплату сотрудников, сохраняя новое значение в таблице EMPLOYEE, которая имеет следующую структуру:

Структура таблицы

Для хранения данных, используемых тестируемым методом для анализа, в целях вычисления нового значения заработной платы, добавим таблицу EMPLOYEE_PROGRESS, которая в столбце PROGRESS будет содержать количество выполненых задач в поставленные сроки в процентном выражении:

Структура таблицы

Для простоты, тестируемый метод будет действовать следующим образом при вычислении нового значения зарплаты:

Если прогресс больше 50%, тогда 
  Новая зарплата = SALARY + (SALARY * Progress) / 100.
Иначе
  Новая зарплата = SALARY - (SALARY * Progress) / 100.

Один из самых простых способов создать DBUnit-тест — расширить класс org.dbunit.DBTestCase, который является непрямым наследником класса junit.framework.TestCase:

package com.company.test;

import org.dbunit.DBTestCase;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;

public class SimpleDBTestCase extends DBTestCase {

    @Override
    protected IDataSet getDataSet() throws Exception {
        return new FlatXmlDataSet(getClass().getResourceAsStream(
            "/data/SalaryTestDataSet.xml"));
    }

}

Переопределенный метод getDataSet возвращает объект IDataSet, который был создан на основании источника, данные которого будут загружены в базу данных перед стартом теста. В данном случае загружаемые данные находятся в XML файле SalaryTestDataSet.xml и выглядят следующим образом:

<dataset>
    <employee ID="1" NAME="Sara" SALARY="465.00"/>
    <employee ID="2" NAME="David" SALARY="789.00"/>
    <employee ID="3" NAME="Mariya" SALARY="711.00"/>

    <employee_progress ID="1" EMPLOYEE_ID="1" PROGRESS="90"/>
    <employee_progress ID="2" EMPLOYEE_ID="2" PROGRESS="95"/>
    <employee_progress ID="3" EMPLOYEE_ID="3" PROGRESS="48"/>    
</dataset>

Каждый XML-тег в данном файле представляет строку таблицы базы данных. Название тега — это имя таблицы, а атрибуты и их значения представляют имена столбцов и загружаемые в них данные.

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

public SimpleDBTestCase(String testName) throws Exception {
    super(testName);

    System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS,
        "oracle.jdbc.driver.OracleDriver");
    System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL,
        "jdbc:oracle:thin:@localhost:1521:XE");
    System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME,
        "login");
    System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD,
        "password");
    System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA,
        "DB_SCHEMA");

}

Добавляем метод для тестирования функциональности:

1. public void testUpdateAllSalaries() throws Exception {

2. AccountService accountService = new AccountService();
3. accountService.updateAllSalaries();

4. IDataSet databaseDataSet = getConnection().createDataSet();
5. ITable actualTable = databaseDataSet.getTable("EMPLOYEE");

6. IDataSet expectedDataSet = new FlatXmlDataSet(getClass().getResourceAsStream("/dataSalaryTestDataSet_Expected.xml"));

7. ITable expectedTable = expectedDataSet.getTable("EMPLOYEE");
8. ITable filteredActualTable = DefaultColumnFilter.includedColumnsTable(actualTable, expectedTable.getTableMetaData().getColumns());

9. Assertion.assertEquals(expectedTable, filteredActualTable);
10. }

В строке 2 и 3 мы вызываем функциональность, которую необходимо протестировать.
Как мы помним, необходимые данные для тестирования этой функциональности уже были загружены в базу данных.

В строке 4 мы получаем доступ к базе данных и создаем объект типа IDataSet, который представляет все таблицы из базы данных. А в строке 5 получаем интересующую нас таблицу.

В строке 6 мы загружаем из проверочного источника данных (в данном случае тоже XML файл) в объект типа

IDataSet
 данные, которые, как мы ожидаем, должны быть в базе данных.

Вот как выглядит файл SalaryTestDataSet_Expected.xml:

<dataset>
    <EMPLOYEE ID="1" SALARY="883.5"/>
    <EMPLOYEE ID="2" SALARY="1538.55"/>
    <EMPLOYEE ID="3" SALARY="369.72"/>
</dataset>

Обратите внимание, что здесь мы задаем только те данные, которые мы хотим проверить.

В строке 7 мы получаем таблицу

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

В строке 8 мы фильтруем столбцы таблицы, полученной из базы данных (стока 5), и выбираем только те столбцы, которые мы хотим проверить, т.е. те, которые мы определили в проверочном источнике данных, в данном случае в файле /data/SalaryTestDataSet_Expected.xml.

В строке 9 мы проверяем ожидаемые данные против данных, полученных из базы данных.

Если данные совпадают — тест успешно пройден, иначе — терпит неудачу.

Преимущества DBUnit

Чтобы протестировать метод updateAllSalaries, к примеру, с помощью JUnit необходимо писать код, который перед вызовом теста создаст и сохранит новую запись о сотруднике в таблице EMPLOYEE, а также заполнит друге таблицы необходимыми данными для анализа и изменения зарплаты. После того как тестируемый метод закончит работу, необходимо написать код, который проверит правильность нового значения в поле SALARY таблицы EMPLOYEE для каждого сотрудника. Данный подход становится все более не приемлемым с ростом сложности тестируемой функциональности, т.к. вам нужно писать все больше и больше кода, чтобы проверить все ветви алгоритма, выполняемого тестируемым методом.

Дополнительные свойства

— Помимо XML файлов DBUnit также поддерживает следующие источники данных: CVS файлы, Excel таблицы, базы данных.

— По умолчанию DBUnit удалит все данные из таблиц, в которые буду загружены
данные для теста, а после окончания теста измененные данные останутся в таблицах. Данное поведение можно изменить, переопределив методы:

@Override
protected DatabaseOperation getSetUpOperation() throws Exception {
    return DatabaseOperation.CLEAN_INSERT;
}

@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
    return DatabaseOperation.NONE;
}

и используя другие значения класса DatabaseOperation: UPDATE, INSERT, REFRESH, DELETE, DELETE_ALL, TRUNCATE_TABLE.

Также DBUnit обладает возможностью генерирования XML-данных на основании данных уже находящихся в базе данных, которые после можно будет загружать в базу данных для использования в тестах.

Ресурсы

Более подробную информацию вы найдете на сайте www.dbunit.org.

Статья о том, как трансформировать XML файлы Hibernate в плоские XML файлы DBUnit.

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn



6 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Когда-то у нас тесты для дао и сервисов были завязаны между собой — ибо общие данные:). Чуть позже решили, что данные должны создаваться и уничтожаться для каждого теста отдельно — ибо атомартность. Потом был Рефакторинг — ибо Истина. В рузельтате сейчас для каждого ДАО класса свой тестовый класс в котором есть пару статических методов создания и удаления данных. Метод createDummyData класса ATest вызывает такой же метод в ВTest, если ему нужно создать в таблице В запись и так дальше. Если В надо создать С Д, то он это делегирует классам СTest и ДTest...Теперь есть DbUnit. Попробуемю Спасибо, Денис, за хорошую статью.

Использовали и DBUnit и надстроечку над ним: DDSteps, — весьма полезная тулзовина.

В «Преимущества DbUnit» приведены лишь проблемы, возникающие при создании сложных тестов — для их инициализации необходимо подготовить большой объем данных. С другой стороны, решение DbUnit как-то не блещет красотой и изяществом — куча инициализационного кода для манипулирования данными на уровне таблиц. Если код, работающий с базой, изолирован он остальных частей системы, возникает проблема портирования тестов при переключении на другие источники данных или использование других реализаций слоя работы с БД.Из личного опыта — все же удобнее использовать хороший тестовый фреймворк (я про TestNG), позволяющий разнести создание сущносей (пользователей, кредитных карт, счетов и пр.) в методах инициализации (@Before) и указать зависимости конкретных тестовых методов от первых путем через параметры аннотации (dependsOnGroups, dependsOnMethods). И все-таки использовать возможности ORM (если она есть).Касательно примеров есть замечания — было бы круто не загромождать ненужнымм аннотациями, ненужными throws Exception (там где их в помине нет), хардкод параметров теста тоже жесть (слава TestNG!:)).

Отличная статья, коротко и по сути, пасибоПрисоединяюсь к вопросу о реализации под.Net

Не проникся дох писанины да еще на Java.

Кто-то может прокометнировать аналоги под.NET? Например, NDbUnit или DbUnit.NET.Или может есть менее заброшеные проекты?

Підписатись на коментарі