Тестирование приложений с базами данных в Spring с DbUnit.

Рано или поздно каждый разработчик, который уже научился основам модульного тестирования приходит к следующему этапу: как тестировать взаимодействие с базами данных, то есть слой доступа к данным — обычно это DAO или Repository-слой. К счастью для этих целей уже существуют нужные библиотеки. И мы в этой статье познакомимся с одной из них, а именно — с DbUnit.

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

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

Здесь я предполагаю, что вы уже знакомы с основами использования баз данных в Spring, а также с проектом Spring Data JPA.

Конфигурация проекта Spring.

В контексте приложения Spring мы описываем те бины, которые нам нужны как для продакшена, так и для тестирования.

Файл application.xml:

Если вы уже работали с базами данных в Spring, то этот код должен быть вам знакомым. Обратите внимание на указание профиля dev в описании источника данных (бин dataSource). Для тестирования будет использоваться другой конфиг и другой источник данных, тогда как все остальные бины будут доступны для обоих профилей.

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

Файл services.xml:

Написание слоя данных.

Итак, давайте вместе напишем наш первый интеграционный тест. Предположим, что у нас есть проект, содержащий справочную информацию о различных банках. Класс сущности — Bank — выглядит следующим образом:

Создадим наследника интерфейса CrudRepository из проекта Spring Data JPA.

Мы расширяем базовый интерфейс репозитория, добавляя в него только один новый метод — для поиска банка по имени.

Написание слоя сервисов.

Теперь опишем интерфейс сервиса.

Файл BankService.java:

И его реализацию. Все вызовы на получение данных сервис делегируют соответствующему репозиторию.

Файл BankServiceImpl.java:

Все методы сервиса являются транзакционными(класс сервиса помечен аннотацией @Transactional).

Подключаем библиотеки.

Теперь воспользуемся поддержкой тестирования в Spring для написания тестов. Добавим в файл проекта зависимости для подпроекта spring-test и библиотек JUnit и DbUnit:

Перед тем, как мы приступим к написанию собственно теста, я покажу получившуюся у меня структуру каталогов в папке test, после чего мы подробно рассмотрим назначение каждого из файлов.

Настройка тестовой конфигурации.

На скриншоте выше вы можете видеть файл TestConfig — это еще один файл определения контекста Spring, но предназначенный исключительно для нужд тестирования. И  создан он не с помощью XML, а в стиле JavaConfig(обычный файл Java c аннотациями). Давайте взглянем на его определение и поймем, что к чему.

Файл TestConfig.java:

Аннотация @Configuration говорит о том, что этот файл является определением контекста Spring. Здесь точно также, как и в случае XML, определяются все нужные бины. Но теперь это набор методов с аннотациями @Bean, в которых мы явно создаем все нужные нам объекты. Этих объектов всего два — экземпляр класса DataSourceDatabaseTester из DbUnit, который будет выполнять основную работу по тестированию и собственно сам источник данных(еще один), который будет использоваться уже только с активированным профилем test, на что указывает аннотация @Profile.

С помощью аннотации @ImportResource мы вкладываем в наш контекст другой — основной, описанный нами ранее в файле application.xml, так как в нем содержатся нужные нам описания бинов для использования JPAEntityManagerFactory, TransactionManager и список репозиториев.

Наконец, аннотация @ComponentScan говорит Spring о том, что нужно искать компоненты для тестирования в директории services — там лежит тот сервис, который мы собираемся протестировать.

Создаем слушатель выполнения тестов в Spring.

Благодаря поддержке проекта Spring Test мы получаем в свое распоряжение возможность добавлять слушатели выполнения тестов. Перед и после выполнения тестового метода можно выполнить какие-нибудь действия, например, в нашем случае мы можем наполнять базу данных тестовыми данными. И все, что для этого необходимо — создать класс слушателя, реализовав интерфейс TestExecutionListener и затем зарегистрировав его в Spring.

Но перед тем, как мы это сделаем — давайте создадим собственную аннотацию @DataSet, которой мы будем помечать тестовые методы, нуждающиеся в загрузке исходных данных. Ее определение приведено ниже:

Данная аннотация, кроме того, позволяет задавать имена файлов для загрузки тестовых данных в нашу базу. В принципе для разных тестов мы можем захотеть иметь разные наборы исходных данных. Библиотека DbUnit дает возможность загружать тестовые данные из файла .xml в класс DataSourceDatabaseTester.

Библиотеке нужно лишь передать нужный файл — все остальное она сделает за вас.

Вот как выглядят тестовые данные для нашей таблицы:

Здесь просто описан набор данных, которые будут добавлены DbUnit в базу перед выполнением тестового метода. Работу по передаче тестовых данных объекту DataSourceDatabaseTester возьмет на себя слушатель тестов Spring. Давайте реализуем его:

Класс реализует интерфейс TestExecutionListener и содержит несколько методов с говорящими названиями. Нас прежде всего интересует метод beforeTestMethod(), который, как несложно догадаться из названия, вызывается как раз перед вызовом каждого тестового метода. В нем мы проверяем, нужны ли для нашего тестового сценария исходные данные, то есть помечен ли он созданной нами аннотацией @DataSet. Если нет, то никаких действий не производится. Если да — то мы получаем имя файла с данными и загружаем его. Объект FlatXmlDataSet превращает загруженный файл в объект IDataSet, который мы передаем классу тестирования из DbUnit. Вызов onSetup() инициализирует загрузку данных.

В методе afterTestMethod() мы освобождаем данные вызовом onTearDown().

Реализуем класс теста BankServiceTest.

Наконец, напишем наш интеграционный тест.

Файл BankServiceTest.java:

Все методы, кроме testAddBank() помечены нашей кастомной аннотацией @DataSet для загрузки исходных данных перед выполнением тестов. Всю загрузку за нас выполнит реализованный нами ServiceTestExecutionListener.

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

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *