Представим ситуацию, когда команда разработчиков работает над проектом, который использует реляционную базу данных, например PostgreSQL. В процессе разработки и тестирования у них может возникнуть несколько проблем:
- Несоответствие окружений. Разработчики могут использовать разные версии базы данных на своих локальных машинах, что приводит к проблемам с совместимостью и ошибкам, которые сложно воспроизвести. У одного разработчика может работать, а у другого - нет.
- Сложность настройки. Установка базы данных локально на свой компьютер и ее настройка ее может быть сложной и занимать много времени. Это может привести к тому, что тесты будут запускаться не регулярно или вообще игнорироваться.
- Изоляция тестов. Тесты могут влиять друг на друга, если они используют одну и ту же базу данных, что может привести к ложным срабатываниям и затруднить отладку.
Для решения этих проблем существует библиотека TestContainers. Она предоставляет простой и удобный способ работы с Docker контейнерами в тестах
TestContainers — это библиотека для Java, которая позволяет разработчикам создавать и управлять Docker контейнерами прямо в коде тестов. Она особенно полезна для интеграционного тестирования, когда необходимо взаимодействовать с внешними сервисами, такими как базы данных или очереди сообщений.
TestContainers позволяет автоматически загружать, настраивать и запускать контейнеры в тестах. При помощи аннотаций можно создать контейнер с PostgreSQL, который будет автоматически запущен перед выполнением тестов и остановлен после их завершения. Это значительно экономит время и ресурсы
Каждый тест в такой ситуации может запускать свой собственный контейнер с нужной версией базы данных. Это гарантирует, что все тесты у всех разработчиков выполняются в одинаковых условиях, что устраняет проблемы с несовместимостью.
Чтобы начать пользоваться библиотекой, нужно добавить зависимости в файл build.gradle.kts
// Эта зависимость необходима для интеграции TestContainer с JUnit 5
testImplementation("org.testcontainers:junit-jupiter:1.20.6")
// Основная зависимость библиотеки
testImplementation("org.testcontainers:testcontainers:1.20.6")
// Эта зависимость предоставляет поддержку для создания контейнеров PostgreSQL и настройки базы
testImplementation("org.testcontainers:postgresql:1.20.6")
Напишем простой тест для приложения, которое использует базу данных PostgreSQL, и на этом примере разберемся подобнее, как работать с TestContainers
@SpringBootTest
@AutoConfigureMockMvc
// Аннотация позволяет автоматически запускать и останавливать в тестах все контейнеры
@Testcontainers
public class AppTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private PersonRepository personRepository;
// Аннотация отмечает контейнер, который будет автоматически запущен
@Container
// Создаём контейнер с СУБД PostgreSQL
// В конструктор передаём имя образа, который будет скачан с Dockerhub
// Если не указать версию, будет скачана последняя версия образа
private static PostgreSQLContainer<?> database = new PostgreSQLContainer<>("postgres")
// Создаём базу данных с указанным именем
.withDatabaseName("dbname")
// Указываем имя пользователя и пароль
.withUsername("sa")
.withPassword("sa")
// Скрипт, который будет выполнен при запуске контейнера, если вдруг нужно создать схему или наполнить базу
.withInitScript("script.sql");
// Так как мы не можем знать заранее, какой URL будет у базы данных в контейнере
// Нам потребуется установить это свойство динамически
@DynamicPropertySource
public static void properties(DynamicPropertyRegistry registry) {
// Устанавливаем URL базы данных
registry.add("spring.datasource.url", database::getJdbcUrl);
// Имя пользователя и пароль для подключения
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
// Эти значения приложение будет использовать при подключении к базе данных
}
@BeforeEach
public void setUp() {
// Очищаем базу перед каждым тестом
}
// Тестируем приложение
@Test
void testCreatePerson() throws Exception {
// Добавляем нового пользователя
MockHttpServletResponse responsePost = mockMvc
.perform(
post("/people")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"\", \"email\": \"jackson@mail.com\"}")
)
.andReturn()
.getResponse();
assertThat(responsePost.getStatus()).isEqualTo(201);
// И проверяем, что пользователь добавился в базу
var person = personRepository.findByEmail("jackson@mail.com").orElse(null);
assertThat(person).isNotNull();
assertThat(person.getName()).isEqualTo("Jackson");
}
// Остальные тесты
}
Самостоятельная работа
В прошлых заданиях при тестировании мы использовали встроенную базу данных H2 в памяти. Это значительно упрощало тестирование приложения. Но в некоторых случаях мы можем захотеть проверить работу приложения с реальной базой данных, с той, которая будет использоваться при работе. В этом домашнем задании вам предстоит написать тесты для проверки работы приложения с базой данных PostgreSQL. Для этого мы будем использовать библиотеку Testcontainer. Она позволит нам в тестах запустить Docker контейнер с базой данных PostgreSQL. Для выполнения этого задания вам понадобится установленный Docker.
Приложение
Для этого задания воспользуемся готовым приложением Java Spring Blog. Форкните его и изучите, как он устроен
Зависимости
Подключите в проект зависимости для тестов с Test Container.
Так как наше приложение использует базу данных PostgreSQL, будем тестировать его работу именно с этой базой.
Тесты
Добавьте новые тесты с необходимыми аннотациями, чтобы обеспечить автоматический старт и остановку контейнеров.
Запустите тесты и убедитесь, что они отработали корректно
Подсказки
Пример теста
@SpringBootTest
@AutoConfigureMockMvc
// Аннотация позволяет автоматически запускать и останавливать в тестах все контейнеры
@Testcontainers
// Все тесты выполняем в транзакции
@Transactional
public class AppTest {
@Autowired
private MockMvc mockMvc;
// Аннотация отмечает контейнер, который будет автоматически запущен
@Container
// Создаём контейнер с СУБД PostgreSQL
// В конструктор передаём имя образа, который будет скачан с Dockerhub
// Если не указать версию, будет скачана последняя версия образа
private static PostgreSQLContainer<?> database = new PostgreSQLContainer<>("postgres")
// Создаём базу данных с указанным именем
.withDatabaseName("dbname")
// Указываем имя пользователя и пароль
.withUsername("sa")
.withPassword("sa")
// Скрипт, который будет выполнен при запуске контейнера и наполнит базу тестовыми данными
.withInitScript("script.sql");
// Так как мы не можем знать заранее, какой URL будет у базы данных в контейнере
// Нам потребуется установить это свойство динамически
@DynamicPropertySource
public static void properties(DynamicPropertyRegistry registry) {
// Устанавливаем URL базы данных
registry.add("spring.datasource.url", database::getJdbcUrl);
// Имя пользователя и пароль для подключения
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
// Эти значения приложение будет использовать при подключении к базе данных
}
// Тестируем приложение
@Test
void testCreatePerson() throws Exception {
// Добавляем нового пользователя
MockHttpServletResponse responsePost = mockMvc
.perform(
post("/people")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"firstName\": \"Jackson\", \"lastName\": \"Bind\"}")
)
.andReturn()
.getResponse();
assertThat(responsePost.getStatus()).isEqualTo(200);
// И проверяем, что пользователь добавился в базу
MockHttpServletResponse response = mockMvc
.perform(get("/people"))
.andReturn()
.getResponse();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON.toString());
assertThat(response.getContentAsString()).contains("Jackson", "Bind");
}
// Остальные тесты
}
Дополнительные материалы
- Интеграция JUnit и Testcontainer
- Аннотация `@Testcontainers` – Активизирует автоматический старт и остановку контейнеров в отмеченном тестовом классе
- Аннотация `@Container` – Отмечает контейнер, который будет запущен в тестах
- Аннотация `@DynamicPropertySource` – позволяет динамически установить свойства приложения при интеграционных тестах
- Класс PostgreSQLContainer – служит для создания контейнера с базой данных PostgreSQL
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.