TestContainers просто потрясающий! Он предоставляет очень удобный способ запуска и CLEANLY разрушения док-контейнеров в тестах JUnit. Эта функция очень полезна для тестирования интеграции приложений с реальными базами данных и любыми другими ресурсами, для которых доступен образ докера.
Моя цель — продемонстрировать пример теста для Spring Boot Application на основе JPA с использованием TestContainers. Образец основан на примере в репозитории github TestContainer .
Образец приложения
Приложение на основе Spring Boot является простым — это приложение на основе Spring Data JPA с веб-слоем, написанным с использованием Spring Web Flux . Весь образец доступен в моем репозитории github, и может быть проще просто следовать коду прямо там.
Сохраняемая сущность City выглядит следующим образом (используя Kotlin ):
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
import javax.persistence.Entityimport javax.persistence.GeneratedValueimport javax.persistence.Id@Entitydata class City( @Id @GeneratedValue var id: Long? = null, val name: String, val country: String, val pop: Long) { constructor() : this(id = null, name = "", country = "", pop = 0L)} |
Все, что требуется для обеспечения хранилища для управления этим объектом, — это следующий интерфейс, благодаря отличному проекту Spring Data JPA :
|
1
2
3
4
|
import org.springframework.data.jpa.repository.JpaRepositoryimport samples.geo.domain.Cityinterface CityRepo: JpaRepository<City, Long> |
Я не буду освещать здесь веб-слой, поскольку он не имеет отношения к обсуждению.
Тестирование репозитория
Spring Boot предоставляет функцию, называемую « Тесты срезов», которая представляет собой удобный способ тестирования различных горизонтальных срезов приложения. Тест для репозитория CityRepo выглядит следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;import org.springframework.test.context.junit4.SpringRunner;import samples.geo.domain.City;import samples.geo.repo.CityRepo;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringRunner.class)@DataJpaTestpublic class CitiesWithEmbeddedDbTest { @Autowired private CityRepo cityRepo; @Test public void testWithDb() { City city1 = cityRepo.save(new City(null, "city1", "USA", 20000L)); City city2 = cityRepo.save(new City(null, "city2", "USA", 40000L)); assertThat(city1) .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L); assertThat(city2) .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L); assertThat(cityRepo.findAll()).containsExactly(city1, city2); }} |
Аннотация «@DataJpaTest» запускает встроенные базы данных h2, настраивает JPA и загружает любые репозитории Spring Data JPA (в данном случае CityRepo).
Этот вид теста работает хорошо, учитывая, что JPA обеспечивает абстракцию базы данных, и если JPA используется правильно, код должен переноситься между любыми поддерживаемыми базами данных. Однако, если предположить, что это приложение будет работать на PostgreSQL в рабочей среде, в идеале должен быть какой-то уровень интеграционного тестирования с базой данных, в который входит TestContainer. Он обеспечивает способ загрузки PostgreSQL в качестве докера контейнер.
TestContainers
Тот же тест хранилища с использованием TestContainers выглядит следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
import org.junit.ClassRule;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;import org.springframework.boot.test.util.TestPropertyValues;import org.springframework.context.ApplicationContextInitializer;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringRunner;import org.testcontainers.containers.PostgreSQLContainer;import samples.geo.domain.City;import samples.geo.repo.CityRepo;import java.time.Duration;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringRunner.class)@DataJpaTest@ContextConfiguration(initializers = {CitiesWithPostgresContainerTest.Initializer.class})public class CitiesWithPostgresContainerTest { @ClassRule public static PostgreSQLContainer postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer("postgres:10.4") .withDatabaseName("sampledb") .withUsername("sampleuser") .withPassword("samplepwd") .withStartupTimeout(Duration.ofSeconds(600)); @Autowired private CityRepo cityRepo; @Test public void testWithDb() { City city1 = cityRepo.save(new City(null, "city1", "USA", 20000L)); City city2 = cityRepo.save(new City(null, "city2", "USA", 40000L)); assertThat(city1) .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L); assertThat(city2) .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L); assertThat(cityRepo.findAll()).containsExactly(city1, city2); } static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.username=" + postgreSQLContainer.getUsername(), "spring.datasource.password=" + postgreSQLContainer.getPassword() ).applyTo(configurableApplicationContext.getEnvironment()); } }} |
Ядро кода выглядит так же, как и в предыдущем тесте, но репозиторий здесь тестируется на реальной базе данных PostgreSQL. Чтобы вдаваться в подробности —
Контейнер PostgreSQL запускается с использованием правила класса JUnit, которое запускается до запуска любого из тестов. Эта зависимость извлекается с использованием зависимости gradle следующего типа:
|
1
|
testCompile("org.testcontainers:postgresql:1.7.3") |
Правило класса запускает Docker-контейнер PostgreSQL (postgres: 10.4) и настраивает базу данных и учетные данные для базы данных. Теперь, с точки зрения Spring Boot, эти детали необходимо передать приложению, поскольку свойства ДО того, как Spring начинает создавать контекст теста для запуска теста, и это делается для теста с использованием ApplicationContextInitializer , который вызывается Spring очень рано жизненный цикл Spring Context.
Пользовательский ApplicationContextInitializer, который устанавливает имя базы данных, URL и учетные данные пользователя, подключается к тесту с помощью этого кода:
|
01
02
03
04
05
06
07
08
09
10
|
...import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringRunner;...@RunWith(SpringRunner.class)@DataJpaTest@ContextConfiguration(initializers = {CitiesWithPostgresContainerTest.Initializer.class})public class CitiesWithPostgresContainerTest {... |
При установленной плите котла TestContainer и тестирование среза пружинной загрузки возьмет на себя выполнение теста. Что еще более важно TestContainers также заботится о демонтаже, правило класса JUnit гарантирует, что после завершения теста контейнеры останавливаются и удаляются.
Вывод
Это был вихревой тур по TestContainers, TestContainers — это гораздо больше, чем то, что я здесь рассмотрел, но я надеюсь, что это даст представление о том, что возможно с помощью этой превосходной библиотеки и как ее настроить с помощью Spring Boot. Этот образец доступен в моем репозитории github
| Смотрите оригинальную статью здесь: TestContainers и Spring Boot
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |