Блог разработчиков

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

Denys Bezsmertnyi
Опубликовано 20.10.2008 в Разработка

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.

Теги: , ,

1 звезда2 звезды3 звезды4 звезды5 звезд (12 голосов, средний: 4.5 из 5)
Загрузка ... Загрузка ...
Распределение голосов

Понравилась статья? Подпишись на обновления по RSS/E-mail

Подписаться, не оставляя комментарий

Все комментарии (10) к “Основы тестирования с помощью Java фреймворка DBUnit” RSS

  1. smith_and_wesson говорит:

    Cool,

    А что же с недостатками DbUnit-а ?? )

  2. smith_and_wesson говорит:

    Лучше всего использовать его с JndiDataSource-ом, т.к. тогда можно будет обернуть тестовые методы в Jta транзакции
    и забыть о том что после теста нужно что-то удалять, а просто откатывать транзакцию (разумеется тестовый код также должен использовать Jta).

  3. Denys Bezsmertnyi говорит:

    smith_and_wesson, у DBUnit конечно же есть недостатки.
    К примеру, могут возникнуть сложности если использовать одну базу данных для всех разработчиков, которая уже содержит данные, которые нельзя трогать.
    Вы правильно заметили о транзакциях, т.к. DBUnit по-умолчанию не работает с транзакциями (т.е. вставка данных в базу данных и последующее их удаление - это 2 различных транзакции),
    поэтому если, к примеру, упадет JVM, данные останутся в базе данных.
    Конечно все можно разрешить (прикрутить транзакции), но для тестирования лучше использовать одну базу данных на одного разработчика.
    См. Best Practices на сайте DBUnit.

  4. smith_and_wesson говорит:

    +пиццот !

    Согласен.
    Однако, это “роскошь”, если проект огромный, обычно есть developer-ская база и база “для тестов”.

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

    Скажем, есть View кот. поднимается из тонны таблиц, следуя DbUnit-у кому-то нужно разобраться в каком
    порядке создать всё эту инфраструктуру чтобы поднять тестовое View. А это - большой головняк.
    В этом случае, наверное лучше идти не от БД, а от объектной модели. Тут нужно получить такой DAO уровень чтобы с помощью
    него можно было поднять состояние базы. Вот тут наверное лучше всего и стоит применить DbUnit, для 1-2-х таблиц.

  5. ai говорит:

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

    Или может есть менее заброшеные проекты?

  6. shadow говорит:

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

  7. Geralt говорит:

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

  8. mind говорит:

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

  9. Denix говорит:

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

  10. kuk говорит:

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

Оставить комментарий

Указать свой сайт могут только зарегистрированные пользователи. Регистрация или вход.

Архив

Комментарии

Последние комментарии