Pull to refresh

Тестирование Spring приложений. Транзакции в тестировании

Reading time 6 min
Views 22K
spring-overview

Про полезность подхода TDD (разработка через тестирование, test driven development) не слышал только ленивый или глухой. Но сегодня мы не будем обсуждать всю его полезность и красоту, а также проблемы и недостатки. Сегодня мы попробуем посмотреть, как разрабатывать unit-тесты для spring приложений. Также мы немного тронем ручное управление транзакциями в unit-тестах.

Небольшое замечание: иногда тесты spring приложений это не совсем unit-тесты, потому что мы можем в них поднять и задействовать очень сложное окружение (БД, WebService и так далее). Подобные тесты это скорее интеграционные тесты, но я думаю что сейчас философские вопросы мы поднимать не будем.

Для начала предлагаю согласовать некоторые термины и понятия:
  • Unit-тест — тест, который проверяет поведение небольшой части приложения. Эта часть может быть одним классом, одним методом или набором классов, который реализуют какое-то архитектурное решение, и это решение необходимо проверить на работоспособность. За подробностями обращайтесь сюда или туда.
  • Application context config — конфигурационный файл в xml формате для описания структуры spring приложения. Про spring читаем тут или там.
  • DAO — объект доступа к данным или data acess object. Основное предназначение этого шаблона проектирования: связать вместе БД и наше приложение. За подробностями идем сюда или туда.
  • Транзакция — группа последовательных операций, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще и тогда она не должна произвести никакого эффекта. Про транзакции читаем тут или там.

Все остальные термины и понятия стандартны и давно устоялись или их описание тут некритично. Например, такое понятие как IoC (инверсия управления, inversion of control).

Совсем забыл, про написание unit-тестов для spring-приложений я пишу не первый, немного есть тут и там.

Итак, у нас стоит цель: протестировать поведение класса в spring-приложении, дополнительно необходимо вручную управлять транзакциями. Для этого мы создадим простое spring-приложение и напишем unit-тест. Наш unit-тест при запуске будет инициализировать application context config нашего spring приложения и после этого вызывать методы у тестируемого нами класса. Также мы разработаем отдельный тест, в котором будем управлять транзакциями вручную.

Для начала создадим приложение, которое будем тестировать. Технологии в приложении будут следующие:
  1. Средство сборки и компиляции — Apache Maven.
  2. База данных — HSQLDB.
  3. Средство для отображения классов в базу данных (Object relation mapping) — Hibernate.
  4. Средство для конфигурирования приложения — Spring framework.
  5. Средство для создания unit-тестов — JUnit.

Конечная структура файлов в приложении будет выглядеть следующим образом:structure
Кстати вы можете загрузить себе (из SVN) и посмотреть в браузере исходные коды работающего приложения.

Начнем?

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

Во вторых, мы создадим java persistente entity класс — ru.intr13.example.springTransactionalTest.Data. Данный класс у нас будет описывать модель данных и при помощи библиотеки hibernate он будет отображаться в БД (будет автоматически создана таблица в базе данных). Также на этом шаге мы создадим файл hibernate.cfg.xml, где сделаем ссылку на разработанный нами класс.

В третьих, мы создадим интерфейс — ru.intr13.example.springTransactionalTest.DataDao. В котором опишем основные методы для работы с нашей БД. Методы checkpoint и shutdown предназначены для работы с hsqldb и их наличие связано с особенностью работы данной БД (подробности тут).

В четвертых, мы создадим реализацию разработанного нами интерфейса — ru.intr13.example.springTransactionalTest.DataHibernateDao. Где реализуем все методы описанные в интерфейсе DataDao. Стоит отметить, что данный класс наследуется от класса org.springframework.orm.hibernate3.support.HibernateDaoSupport, который в свою очередь является часть библиотеки Spring Dao и в нем уже реализованы методы для удобной работы с БД.

В пятых, мы создадим application context config файл для конфигурирования нашего приложения. В котором мы опишем:
  • Источник данных (dataSource), в котором мы опишем параметры подключения к нашей hsqldb базе данных.
  • Фабрику для работы с подключениями к базе данных и для отображения нашей модели в БД (sessionFactory). При конфигурировании фабрики мы укажем ссылку на файл hibernate.cfg.xml, где описаны все классы нашей модели. Также мы пропишем параметры создания и работы с базой данных, ссылку на источник данных.
  • Разработанный нами сервис (dataDao). При конфигурировании мы укажем ссылку на sessionFactory.
  • Менеджер транзакций (transactionManager). При конфигурировании которого мы укажем ссылку на sessionFactory и также укажем: на какие методы нам надо начинать новую транзакцию.

В шестых, создадим тестовое приложение, которое проинициализирует application context config и немного поработает с разработанным нами сервисом. Результаты работы сохраняться в нашу локальную БД, что можно наблюдать в файле data/test.db.script (данный файл содержит данные нашей БД):
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE MEMORY TABLE DATA(ID BIGINT NOT NULL PRIMARY KEY,TEXT VARCHAR(255))
CREATE MEMORY TABLE HIBERNATE_SEQUENCES(SEQUENCE_NAME VARCHAR(255),SEQUENCE_NEXT_HI_VALUE INTEGER)
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 10
SET SCHEMA PUBLIC
INSERT INTO DATA VALUES(1,'one')
INSERT INTO DATA VALUES(2,'two')
INSERT INTO DATA VALUES(3,'three')
INSERT INTO HIBERNATE_SEQUENCES VALUES('Data',1)

Итак, тестовое приложение создано и теперь надо разработать unit-тест. Для этого мы создаем класс ru.intr13.example.springTransactionalTest.DataDaoTest. Данный класс является наследником класса org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests, что позволяет при запуске тестов поднимать application context config. Делается это при помощи аннотации для класса-теста:
@ContextConfiguration(locations = { "/applicationContext.xml" }).

Но для полноценного тестирования нам требуется чтобы у нашего теста была возможность получить разработанный нами сервис. Для этого мы прописываем в нашем тесте поле со ссылкой на сервис и ставим для этого поля аннотацию — @Autowired:
@Autowired
private DataDao dataDao;

Теперь при запуске тестов в данное поле будет установлена ссылка на разработанный ранее сервис.

И в конце остается написать текст unit-теста:
@Test
public void simpleTest() {
  String text = UUID.randomUUID().toString();
  dataDao.save(new Data(text));
  Collection result = dataDao.find(text);
  Assert.assertEquals(1, result.size());
  Assert.assertEquals(text, result.iterator().next().getText());
}

Данный тест создает объект Data, сохраняет его в БД, и потом ищет объект Data по содержимому.

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

Для ручного управления транзакциями в тестах, нам нужно получить описанный в application context config менеджер транзакций (transactionManager), что делается через создание поля в нашем тесте:
@Autowired
private PlatformTransactionManager transactionManager;

Далее мы просто создаем новую транзакцию:
TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));

и потом делаем для нее commit (сохранение):
transactionManager.commit(transaction);

или rollback (откат):
transactionManager.rollback(transaction);

Зная все выше приведенное, мы можем написать следующий тест, который создает несколько транзакций вручную:
@Test
public void comlexTest() {
  TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
  String text = UUID.randomUUID().toString();
  dataDao.save(new Data(text));
  transactionManager.commit(transaction);
  transaction = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
  Collection result = dataDao.find(text);
  Assert.assertEquals(1, result.size());
  Assert.assertEquals(text, result.iterator().next().getText());
  transactionManager.rollback(transaction);
}

Конечный вариант теста можно посмотреть здесь.

Итого: мы создали тестовое приложение на базе spring framework (исходные коды лежат здесь). Был продемонстрирован способ тестирования отдельных сервисов в spring framework. Также был показан способ ручного управления транзакциями в unit-тестах. В результате мы увидели что создание простых unit-тестов для spring framework довольно простая задача. Вопрос о целесообразности и необходимости разработки подобных тестов рассмотрен не был, это тема для отдельной беседы.

p/s
Это отредактированная версия поста из моего личного блога. Не сочтите за рекламу:)

p/s/s
Картинка найдена здесь. Кстати внимательный человек заметит одну забавную вещь:)
Tags:
Hubs:
+21
Comments 45
Comments Comments 45

Articles