2 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Интеграционные тесты для Java с помощью TestContainers.

Тестирование сервисов на Spring с Testcontainers

Нередко даже для тестирования небольших проектов требуется наличие определённой инфраструктуры: баз данных, брокеров очередей сообщений, кеш-серверов и т.д. Можно использовать какие-то встраиваемые решения, но не факт, что они на 100% соответствуют требованиям проекта. Так, например, популярная встраиваемая СУБД H2 не имеет полной поддержки всех типов PostgreSQL. Можно развёртывать для тестирования всю необходимую инфраструктуру, но это несёт дополнительные затраты, в том числе и на сопровождение. Кроме того разработчикам, возможно, придётся разворачивать тестовую инфраструктуру локально на своих ПК, что тоже не всегда удобно.

Но все эти проблемы были действительно актуальны до повсеместного внедрения инструментов контейнеризации, в частности Docker. С появлением Docker появилась возможность разворачивать компоненты тестового окружения в контейнерах и отказаться от встраиваемых решений. До появления фреймворка Testcontainers приходилось пользоваться различными плагинами, либо самостоятельно писать скрипты для запуска контейнеров перед тестами. Testcontainers позволяет управлять жизненным циклом контейнеров прямо из тестов.

В этой статье я рассмотрю применение Testcontainers при тестировании исходного кода, взаимодействующего с СУБД PostgreSQL с помощью Spring Framework JDBC.

Зависимости проекта

Для проекта потребуются следующие зависимости:

  • org.springframework.boot:spring-boot-starter-web — для демонстрационного REST-сервиса
  • org.springframework.boot:spring-boot-starter-jdbc — для использования API Spring JDBC
  • org.postgresql:postgresql — драйвер PostgreSQL
  • org.testcontainers:junit-jupiter — для поддержки JUnit 5
  • org.testcontainers:postgresql — для API Testcontainers, специфичных для PostgreSQL
  • org.springframework.boot:spring-boot-starter-test — для тестирования

В рамках данной статьи я буду демонстрировать использование Testcontainers совместно с JUnit 4 и JUnit 5, но в реальных условиях, вы, скорее всего, будете использовать какой-то один фрейморк, поэтому помните об исключении лишних зависимостей из проекта. И обратите внимание на то, что Spring Boot, начиная с версий 2.2.x использует в качестве основного тестового фреймворка JUnit 5, в то время как Testcontainers в качестве основного продолжает использовать JUnit 4.

Тестируемые классы

В моём тестовом сервисе есть класс-репозиторий:

Кроме него есть ещё пара классов FrameworkRowMapper и FrameworkSimpleJdbcInsert, которые используются для преобразования ResultSet в Framework и вставки новых записей в таблицу соответственно, а так же простой REST-сервис FrameworkRestController.

В тестовых ресурсах есть sql-файл со схемой БД и тестовыми данными:

Использование Testcontainers

Для тестирования SpringJdbcFrameworkRepository требуется запустить контейнер с PostgreSQL и создать объект типа javax.sql.DataSource, предоставляющий подключение к тестовой базе данных.

Для этого я создам объект класса PostgreSQLContainer, а полученные от него jdbcUrl, username и password буду использовать для создания HikariDataSource.

При помощи аргументов конструкторов контейнеров можно указать образ и версию, которую вы хотите использовать для тестов. Это полезно, когда стандартная для Testcontainers версия образа не соответствует требуемой (например, мой проект использует PostgreSQL 11, тогда как Testcontainers по умолчанию предоставляет версию 9.6.12), либо требуется какой-то модифицированный образ.

Кроме этого все типы контейнеров позволяют указывать дополнительные параметры, как специфичные, так и общие, например при помощи метода withInitScript можно указать путь к sql-файлу, который будет выполнен после запуска JdbcContainer, а при помощи метода withExposedPorts можно задать список портов, которые контейнер должен опубликовать.

Читать еще:  Чувствительность наушников дб какое лучше?

JUnit 5

В JUnit 5 фреймворк Testcontainers использует механизм расширений. Если класс отмечен аннотацией @Testcontainers, то Testcontainers найдёт все члены тестового класса, отмеченные аннотацией @Container и автоматически запустит соответствующие контейнеры.

Если свойство, отмеченное аннотацией @Container имеет модификатор static, то контейнер будет использоваться для всех тестов, в противном случае контейнер будет запускаться для каждого теста индивидуально.

В моём случае перед выполнением каждого теста будет подниматься контейнер из образа postgres:11 и выполняться sql-файл db.sql для заполнения БД тестовыми данными.

JUnit 4

В JUnit 4 Testcontainers использует механизм правил. Для этого каждый контейнер должен быть отмечен аннотацией @Rule и иметь модификатор доступа public.

В остальном отличия только в использовании API JUnit 4, вместо JUnit 5.

Интеграционные тесты со Spring Boot

В случае с интеграционными тестами в контексте приложения достаточно зарегистрировать компонент типа javax.sql.DataSource, который будет использоваться вместо автоматически конфигурируемого.

Я зарегистрировал сам контейнер в контексте приложения в виде отдельного компонента, чтобы иметь возможность автоматически запускать и останавливать его в зависимости от lifecycle-событий приложения. Так же я добавил ожидание доступности порта, по которому будет производиться подключение к PostgreSQL, чтобы тесты начинали выполняться только после того, как сервер PostgreSQL будет готов.

В самих интеграционных тестах никаких изменений при использовании Testcontainers не требуется.

Тестирование с помощью TestNG в Java

Хочу вас познакомить еще с одним инструментом для тестирования, в этой небольшой статье мы рассмотрим основные возможности TestNG .

TestNG – это фреймворк для тестирования, написанный на Java, он взял много чего с JUnit и NUnit, но он не только унаследовался от существующей функциональности Junit, а также внедрил новые инновационные функции, которые делают его мощным, простым в использовании.

TestNG предназначен для:

• интеграционного тестирование и т.д.

Какие возможности в TestNG?

1) Annotations. (Аннотации);

2) Использование XML для гибкого конфигурирования тестов;

3) Поддержка data-driven тестирования (с помощью аннотации @DataProvider);

4) Зависимые методы для тестирования серверных приложений;

5) Поддерживается в Eclipse, IDEA, Ant, Maven, Netbean, Hudson;

6) Тестирование вашего кода проходит многопоточно, что дает безопасность и быстродействие;

7) Легкий переход от JUnit.

Содержание урока

Шаг 1. Начало

Давайте напишем первый наш тест используя TestNG.

Для этого я буду использовать среду разработки Intellij IDEA 12.

Создаем Maven проект и добавляем зависимости:

После этого создадим класс Calc.java и тестовый класс CalcTest.java для него.

Содержимое класса Calc.java:

Содержимое класса CalcTest.java:

7-я строка – это экземпляр класса который мы будем тестировать.

9-я строка – эта аннотация говорти о том что данный метод есть тестовым и может запускаться в отдельном потоке.

Шаг 2. Первый тест

Давайте напишем тест на метод в классе Calc.java:

У нас есть класс, в котором есть метод, который находил сумму двух чисел. Для него мы написали тест, где проверили, что сумма числа 2 и 3 должна быть равна 5-ти.

С помощью класса Assert библиотеки TestNG мы проверяем на правильность работы метода.

Шаг 3. Запуск тестов

Теперь запускаем написанный тест.

После выполнения теста вы увидите следующий результат:

Мы видим, что тест удачно прошел!

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

Запустить тесты можно через консоль используя файл temp-testng-customsuite.xml, для этого войдите в cmd и введите команду:

Внимание!
Тестовый класс, в нашем случае ClacTest.java, должен быть скомпилирован.

Читать еще:  Подключить интернет платежи. Как сделать оплату в интернет-магазине через платежные системы? Баланс между комфортом и безопасностью

Шаг 4. Аннотации TestNG

TestNG является более гибким благодаря своим аннотациям. Что они нам предоставляют?

Значит существует 10 управляемых аннотаций TestNG:

1. @BeforeSuite – указывает, что данный метод будет запускаться перед любым методом тестового класса.

2. @BeforeGroups – аннотирует методы, которые будут выполняться перед первым методом в любой из указанных групп.

3. @BeforeClass – указывает, что метод будет выполнен до всех тестовых методов тестового класса.

4. @BeforeTest – аннотированный метод будет запускаться до всех тестовых методов.

5. @AfterTest – аннотированный метод будет запущен после всех тестовых методов, принадлежащих классам внутри тега .

6. @BeforeMethod – аннотированный метод будет выполняться перед каждым тестовым методом.

7. @AfterMethod – аннотированный метод будет запускаться после каждого тестового метода.

8. @AfterClass – аннотированный метод будет запущен после всех тестовых методов в текущем классе.

9. @AfterGroups – аннотируется методы, которые будут выполняться после всех методом в любом из указанных групп.

10. @AfterSuite – указывает, что данный метод, будет запускаться после всех методов тестового класса.

Вот, что у нас должно получится:

И вот результат:

Шаг 5. Исключения

Отловить ожидаемую ошибку можно с помощью аннотации @Test через параметр expectedExceptions:

где expectedExceptions – ожидаемое исключение(ошибка).

В этом случае тест пройдет успешно, так как мы ожидаем, что данный тест выбросит нам NullPointerException.

Шаг 6. Игнорирование тестовых методов

Иногда требуется проигнорировать тестовый метод, если вам например в очередном тестировании не требуется выполнять данный тестируемый метод.

Для того, чтобы проигнорировать тестовый метод, вам достаточно указать в аннотации @ Test параметре enabled = false.

Шаг 7. Timeout

Если вам потребуется ограничить время проведения теста для определенного тестового метода, то вам на помощь приходит параметр timeOut аннотации @Test.

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

В 6-й строке мы заставляем поток заснуть на 1001 миллисекунду.

Шаг 8. Групповое тестирование

Если вам нужно прогнать тесты группами, например, чтобы тестовые методы выполнялись по очереди группами.

Для этого мы можем использовать атрибут groups в аннотации @Test.

Следующий пример имеет 4 тестовых метода, каждый из них входит в группу интеграции, методы testingMethod1, testingMethod3 testingFMethod4 входят в группу Unit1. testingMethod2 входит в группу unit2.

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

Для этого нажимаем правой кнопкой мыши по названию тестового класса:

После, в появившимся окне делаем следующие действия познанные цифрами:

После этого жмем Run и в результате выполнения вы увидите, что выполнился только 2 тестовых метод, которые относятся к группе unit2:

Шаг 9. Зависимые тесты

Допустим, есть тестовый класс, в котором есть 2 тестовых метода, где один зависит от другого, то есть первый не пройдет успешно, пока не отработает второй. Давайте рассмотрим, как это сделать.

Сделать это можно с помощью параметра dependsOnMethods аннотации @Test:

Как видите, тестовый метод testmethod() зависит от тестового метода initEnvironmentTest() и будет выполнен только после того, как отработает initEnvironmentTest() метод.

Также зависимость можно установить на группу тестов:

Сначала выполнится группа тестов, а потом зависимые от это группы тесты.

Интеграционное тестирование и Spring JDBC

Почти все примеры в статьях и о JDBC и о Spring JDBC были написаны по одному шаблону — подготавливаем структуру базы данных, наполняем её тестовыми данными, исполняем какой-то код на тестовых данных, очищаем базу данных. В статье о признаках хорошего теста я писал, что так устроен практически любой тест: подготовка тестовой среды, выполнение теста, очистка тестовой среды.

Читать еще:  Зачем нужна денормализация баз данных, и когда ее использовать

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

С другой стороны, Spring JDBC предоставляет утилиты, упрощающие разработку интеграционных тестов для кода, работающего с базами данных.

Подготовка

За основу я взял код из примера «Запросы в Spring JDBC» и немного его изменил. В первую очередь, встроенная база H2 заменена на PostgreSQL:

Кроме того, я подготовил пустую базу в PostgreSQL сервере:

SQL скрипты, которые создают структуру базы и наполняют её данными, я разбил на отдельные скрипты создания каждой таблицы и отдельные скрипты для наполнения этих таблиц данными.

Выполнение SQL скриптов.

Класс ResourceDatabasePopulator позволяет выполнять SQL скрипты в указанном порядке. Звучит просто, но реально это очень мощный механизм для работы с данными. Его можно применять как в тестах, так и в каких-либо пакетных операциях над базой и вообще по любому поводу.

Я с его помощью буду инициализировать таблицы для интеграционного теста:

ResourceDatabasePopulator принимает SQL скрипты как экземпляры класса Resource , который скрывает истинный источник данных и позволяет использовать файлы из classpath, файлы из файловой системы, напрямую из интернета, байтовые потоки и т.д. Скрипты выполняются в порядке выполнения. Кроме задания списка скриптов можно задать условия их выполнения: разделитель запросов, символы комментирования, игнорирование ошибок и прочая. Готовый, сконфигурированный DatabasePopulator можно исполнить на базе данных с помощью статического метода execute ( ) класса DatabasePopulatorUtils . Метод execute ( ) ожидает получить объект DataSource , а не JdbcTemplate в качестве ссылки на базу данных.

JdbcTestUtils

Второй вспомогательный класс имеет название, намекающее, что он ориентирован только на тесты. В основном он ориентирован на очищение базы данных после теста.

В данном случае он удаляет таблицу customers, создаваемую скриптами в методе setUp ( ) . Размещение инициализации/деинициализации в методах @Before / @After гарантирует, что каждый тест класса получит одинаковый набор данных, заданный скриптами.

Автоматическое выполнение скриптов

Если внимательно присмотреться к коду setUp ( ) выше, видно, что вообщем-то для разных тестов он будут отличаться только списком скриптов, а в остальном будет тот же самый. Spring JDBC позволяет не повторяться и не копипастить ResourceDatabasePopulator из теста в тест, а указывать набор скриптов в качестве метаданных теста или тестового класса.

Аннотации @SqlGroup и @Sql говорят Spring test framework, что перед запуском каждого теста (поведение по умолчанию) или после завершения каждого теста (если указан параметр executionPhase = Sql . ExecutionPhase . AFTER_TEST_METHOD ) необходимо выполнить скрипт/группу скриптов. В примере выше перед запуском каждого теста выполняются два скрипта, которые создают таблицу и наполняют её данными. А после выполнения тестов запускается sql скрипт, удаляющий таблицу.

И опять JdbcTestUtils

В примере выше в тестовом методе используются два новых метода JdbcTestUtils . Вначале метод JdbcTestUtils . deleteFromTables ( ) очищает таблицы, не удаляя их. Затем, после того как операции над базой выполнены, метод JdbcTestUtils . countRowsInTable ( ) подсчитывает текущее количество строк в таблице. Результат подсчёта сравнивается с эталоном.

Оба метода имеют компаньонов, JdbcTestUtils . deleteFromTableWhere ( ) и JdbcTestUtils . countRowsInTableWhere ( ) , которые позволяют указать условия для выполнения операции. Кроме того, метод JdbcTestUtils . deleteFromTables ( ) принимает несколько аргументов, позволяя очистить несколько таблиц сразу. Таким же поведением обладает и JdbcTestUtils . dropTables ( ) из примера с @Before / @After , который так же способен удалить несколько таблиц.

Всё вместе.

Всё перечисленное выше можно комбинировать. Например таблицы можно создавать скриптом, а удалять в @After методе.

Ссылка на основную публикацию
Статьи c упоминанием слов:

Adblock
detector