Украинское сообщество программистов

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

Denis
Опубликовано 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 звезд (14 голосов, средний: 4.57 из 5)
Загрузка ... Загрузка ...
Распределение голосов

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

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

Все комментарии (11) к “Основы тестирования с помощью 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, – весьма полезная тулзовина.

  11. СанЁк Баглай

    Когда-то у нас тесты для дао и сервисов были завязаны между собой – ибо общие данные :) .

    Чуть позже решили, что данные должны создаваться и уничтожаться для каждого теста отдельно – ибо атомартность.

    Потом был Рефакторинг – ибо Истина.

    В рузельтате сейчас для каждого ДАО класса свой тестовый класс в котором есть пару статических методов создания и удаления данных. Метод createDummyData класса ATest вызывает такой же метод в ВTest, если ему нужно создать в таблице В запись и так дальше. Если В надо создать С Д, то он это делегирует классам СTest и ДTest…

    Теперь есть DbUnit. Попробуемю Спасибо, Денис, за хорошую статью.

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

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

Архив

Добавить статью

Станьте автором нашего сайта!

Какие материалы подходят для публикации? — Такие.

Присылайте статьи на editors@developers.org.ua.

Подробнее.

Популярные теги

Все теги

Комментарии

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

интернет магазин бытовая техника магазин Laptoper