Тестирование БД с помошью DBUnit

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

DBUnit — open source фреймворк который помогает с решением таких проблем как заполнение баз данных, таблиц, сравнение таблиц и наборов данных с БД. Он же, является расширением для JUnit.

Шаг 0. Знакомство с DBUnit

Где и зачем используется данная технология?

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

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

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

Шаг 1. Добавление зависимостей

Займемся тем, что для начала использования, нам необходимо подключить данную библиотеку. Используем для этого зависимость в Maven.

<dependency>
	<groupId>org.dbunit</groupId>
	<artifactId>dbunit</artifactId>
	<version>2.4.9</version>
</dependency>

Так же, DBUnit для своей работы требует наличие логгера. Добавляем его в наш проект.

<dependency>
        <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
       <version>1.7.5</version>
</dependency>

После этого подключение DBUnit закончена.  Теперь осталось добавить остальные необходимые библиотеки в наш проект. Нам понадобится библиотеки для наше БД — Mysql, JPA, Hibernate, а также JUnit. Ниже конечный вариант конфигурационного файла Maven со всеми подключенными библиотеками.

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.26</version>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>4.2.7.Final</version>
</dependency>

<dependency>
    <groupId>org.hibernate.javax.persistence</groupId>
    <artifactId>hibernate-jpa-2.0-api</artifactId>
    <version>1.0.1.Final</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
</dependency>

<dependency>
    <groupId>org.dbunit</groupId>
    <artifactId>dbunit</artifactId>
    <version>2.4.9</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.5</version>
</dependency>

Шаг 2. Создание сущностей для тестирования

Создадим сущность которую будем тестировать с помощью DBUnit. Я выбрал Pеrson. Для понятия этого шага могут понадобится знания с JPA. Детально об этом можно посмотреть в уроке JPA работа с базой данных.

@Entity
@Table(name = "person")
@NamedQuery(name = "Person.getAll", query = "select p from Person p")
public class Person {
    @Id
    @GeneratedValue
    private int id;
    private String name;
    private String surname;

    public Person() {
    }

//getters and setters
}

Шаг 3. Создание сервиса для управления сущностью

Для управления создадим сервис. Он будет удалять, добавлять, редактировать и получать объект(ы) нашего класса с базы данных.

public class PersonService {
    private EntityManager em = Persistence.createEntityManagerFactory("DBUnitEx").createEntityManager();

    public void save(Person person){
        em.getTransaction().begin();
        em.persist(person);
        em.getTransaction().commit();
    }

    public void delete(Person person) {
        em.getTransaction().begin();
        em.remove(person);
        em.getTransaction().commit();
    }

    public Person get(int id) {
        return em.find(Person.class, id);
    }

    public void update(Person person) {
        em.getTransaction().begin();
        em.merge(person);
        em.getTransaction().commit();
    }

    public List<Person> getAll() {
        TypedQuery<Person> namedQuery = em.createNamedQuery("Person.getAll",Person.class);

        return namedQuery.getResultList();
    }

}

По окончанию настройки проекта у нас в должна получится в main такая вот структура:

Шаг 4. Знакомство со структурой теста

Рассмотрим, как выглядят структура нашего теста.

PersonTest — тесты для нашей «персоны»

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

db.config.data — содержит внутри настройки к базе данных

person-data.xml — внутри содержит набор данных (dataset) для инициализации базы.

person-data-*.xml — содержит набор данных который должен быть получен после выполнения каких-либо операций с БД.

Шаг 5. Конфигурация DBUnit

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

public class DBUnitConfig extends DBTestCase {
    protected IDatabaseTester tester;
    private Properties prop;
    protected IDataSet beforeData;

    @Before
    public void setUp() throws Exception {
        tester = new JdbcDatabaseTester(prop.getProperty("db.driver"),
                                        prop.getProperty("db.url"),
                                        prop.getProperty("db.username"),
                                        prop.getProperty("db.password"));
    }

    public DBUnitConfig(String name) {
        super(name);
        prop = new Properties();
        try {
            prop.load(Thread.currentThread()
                      .getContextClassLoader().getResourceAsStream("db.config.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, prop.getProperty("db.driver"));
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, prop.getProperty("db.url"));
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, prop.getProperty("db.username"));
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, prop.getProperty("db.password"));
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA, "");
    }

    @Override
    protected IDataSet getDataSet() throws Exception {
        return beforeData;
    }

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

}

IDatabaseTester tester — объект, функционалом которого мы будем проводить сравнивание табличек и бд.

Properties prop — здесь мы будем хранить наши данные для БД. Настройки подключения

IDataSet beforeData — объект, который содержит наши данные для инициализации бд перед выполнением теста.

setUp() — в этом методе мы инициализируем данные, необходимые перед выполнением теста. Здесь мы определяем наш тестер

public DBUnitConfig(String name) — конструктор инициализирует нашу БД в системе для дальнейшего получения доступа и возможности в дальнейшем осуществлять взаимодействия.

getDataSet() — возвращает наш набор данных

DatabaseOperation getTearDownOperation() — очищает БД после выполнения тестов

Содержание db.config.properties:

#configuration for db
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/test
db.username=root
db.password=root

Шаг 6. Создание тестового класса для заполнение базы

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

public class PersonTest extends DBUnitConfig{

    private PersonService service = new PersonService();
    private EntityManager em = Persistence.createEntityManagerFactory("DBUnitEx").createEntityManager();

    @Before
    public void setUp() throws Exception {
        super.setUp();
        beforeData = new FlatXmlDataSetBuilder().build(
                     Thread.currentThread().getContextClassLoader()
                     .getResourceAsStream("com/devcolibri/entity/person/person-data.xml"));

        tester.setDataSet(beforeData);
        tester.onSetup();
    }

    public PersonTest(String name) {
        super(name);
    }

    @Test
    public void testGetAll() throws Exception {
        List<Person> persons = service.getAll();

        IDataSet expectedData = new FlatXmlDataSetBuilder().build(
                                Thread.currentThread().getContextClassLoader()
                                .getResourceAsStream("com/devcolibri/entity/person/person-data.xml"));

        IDataSet actualData = tester.getConnection().createDataSet();
        Assertion.assertEquals(expectedData, actualData);
        Assert.assertEquals(expectedData.getTable("person").getRowCount(),persons.size());
    }

    @Test
    public void testSave() throws Exception {
        Person person = new Person();
        person.setName("Lilia");
        person.setSurname("Vernugora");

        service.save(person);

        IDataSet expectedData = new FlatXmlDataSetBuilder().build(
                                Thread.currentThread().getContextClassLoader()
                                .getResourceAsStream("com/devcolibri/entity/person/person-data-save.xml"));

        IDataSet actualData = tester.getConnection().createDataSet();

        String[] ignore = {"id"};
        Assertion.assertEqualsIgnoreCols(expectedData, actualData, "person", ignore);
    }

    //others tests

}

setUp() — переопределяем метод для того, чтобы передать в него наш набор данных для конкретного теста.

FlatXmlDataSetBuilder().build(…) — создает набор данных с переданного ему в потоке файла xml который содержит в себе описание данных, которые должны будут записаны в соответствующие таблички. Состоит с тега верхнего уровня dataset внутри которого находятся теги с атрибутами. Название тега —  имя таблички, атрибуты внутри — атрибуты таблички в БД.

Файл person-data.xml:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <person id="1" name="Oleg" surname="Kril"/>
    <person id="2" name="Alex" surname="Barchuk"/>
</dataset>

Шаг 7. Сравнивание значений

На этом шаге мы проведем тестирование результатов используя возможности DBUnit. Эту возможность нам даст интерфейс IDatabaseTester. Этот интерфейс позволяет проводить тестирование БД без использования наследования, как делали мы.  Библиотека DBUnit имеет в себе класс (Assertion) для проведения сравнивания таблиц или целых наборов данных между собой. Мы будем сравнивать целый набор данных.

Рассмотрим пример проверки правильного заполнения БД в методе testGetAll()

Первым делом создаем набор ожидаемых данных:

IDataSet expectedData = new FlatXmlDataSetBuilder().build(
                        Thread.currentThread().getContextClassLoader()
                        .getResourceAsStream("com/devcolibri/entity/person/person-data.xml"));

Создаем набор данных с существующих данных в БД:

IDataSet actualData = tester.getConnection().createDataSet();

Делаем сравнивание наборов данных:

String[] ignore = {"id"};
Assertion.assertEqualsIgnoreCols(expectedData, actualData, "person", ignore);

Примечание

В случаях, когда сравниваются таблички или наборы данных в которых присутствует поле типа auto increment могут быть ошибки при прямом сравнивании так как, при удалении одного поля и добавления нового с БД,  индекс будет расти, что не будет соответствовать  индексу с набора данных в xml файле. Поэтому, одним решением есть игнорирование поля, как мы делали выше.

Screenshot_4

Итог

Мы рассмотрели 2 типа использования DBUnit для проведения тестирования.

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

Второй, использование IDatabaseTester. Дает нам возможность также заполнять данные базы с xml файла, используя код ниже:

DatabaseOperation.CLEAN_INSERT.execute(getConnection(), expectedData);
Урок создан: 02 декабря 2013 | Просмотров: 31606 | Автор: Олег Криль | Правила перепечатки


Добавить комментарий

Добавить комментарий

Ваш e-mail не будет опубликован.

Комментарии:

  • 16 декабря 2014 в 15:49

    Илья

    Спасибо, Очень помогла ваша статья!

    • 10 апреля 2015 в 16:23

      Дмитрий

      Есть вопрос, скачал Ваш исходник, но при запуске пишет
      WARN: HHH000436: Entity manager factory name (DBUnitEx) is already registered. If entity manager will be clustered or passivated, specify a unique value for property ‘hibernate.ejb.entitymanager_factory_name’ и в базу не сохраняет данные

  • 02 января 2016 в 23:45

    Kostyan

    Отличная статья!!! Спасибо!
    Очень помогло при первом знакомстве с DB Unit.
    Добавив мавен зависимость можно избавиться от ворнингов засветившихся на финальном скриншоте

    org.slf4j
    slf4j-nop
    1.7.13

  • 12 апреля 2016 в 04:13

    Vladimir

    Спасибо за пример. А как сделать то же самое для SessionFactory?