«1. Введение

В этом руководстве мы рассмотрим библиотеку Java TestContainers. Это позволяет нам использовать контейнеры Docker в наших тестах. В результате мы можем писать автономные интеграционные тесты, которые зависят от внешних ресурсов.

Мы можем использовать в наших тестах любой ресурс, у которого есть образ докера. Например, есть образы для баз данных, веб-браузеров, веб-серверов и очередей сообщений. Поэтому мы можем запускать их как контейнеры в наших тестах.

2. Требования

Библиотека TestContainers может использоваться с Java 8 и выше. Кроме того, он совместим с JUnit Rules API.

Во-первых, давайте определим зависимость maven для основной функциональности:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.11.4</version>
</dependency>

Существуют также модули для специализированных контейнеров. В этом руководстве мы будем использовать PostgreSQL и Selenium.

Давайте добавим соответствующие зависимости:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql </artifactId>
    <version>1.11.4</version>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>selenium </artifactId>
    <version>1.11.4</version>
</dependency>

Последние версии можно найти на Maven Central.

Также нам нужен Docker для запуска контейнеров. Инструкции по установке см. в документации Docker.

Убедитесь, что вы можете запускать контейнеры Docker в своей тестовой среде.

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

Давайте настроим общее правило контейнера:

@ClassRule
public static GenericContainer simpleWebServer
 = new GenericContainer("alpine:3.2")
   .withExposedPorts(80)
   .withCommand("/bin/sh", "-c", "while true; do echo "
     + "\"HTTP/1.1 200 OK\n\nHello World!\" | nc -l -p 80; done");

Мы создадим тестовое правило GenericContainer, указав имя образа докера. Затем мы настраиваем его с помощью методов компоновщика:

    Мы используем withExposedPorts для предоставления порта из контейнера withCommand определяет команду контейнера. Он будет выполнен при запуске контейнера.

Правило снабжено аннотацией @ClassRule. В результате он запустит контейнер Docker до запуска любого теста в этом классе. Контейнер будет уничтожен после выполнения всех методов.

Если вы примените аннотацию @Rule, правило GenericContainer запустит новый контейнер для каждого метода тестирования. И он остановит контейнер, когда этот метод тестирования завершится.

Мы можем использовать IP-адрес и порт для связи с процессом, работающим в контейнере:

@Test
public void givenSimpleWebServerContainer_whenGetReuqest_thenReturnsResponse()
  throws Exception {
    String address = "http://" 
      + simpleWebServer.getContainerIpAddress() 
      + ":" + simpleWebServer.getMappedPort(80);
    String response = simpleGetRequest(address);
    
    assertEquals(response, "Hello World!");
}

4. Режимы использования

Существует несколько режимов использования тестовых контейнеров. Мы видели пример запуска GenericContainer.

Библиотека TestContainers также содержит определения правил со специальной функциональностью. Они предназначены для контейнеров общих баз данных, таких как MySQL, PostgreSQL; и другие, такие как веб-клиенты.

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

4.1. Базы данных

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

Например, мы запускаем контейнер PostgreSQL с правилом PostgreSQLContainer. Затем мы можем использовать вспомогательные методы. Это getJdbcUrl, getUsername, getPassword для подключения к базе данных:

@Rule
public PostgreSQLContainer postgresContainer = new PostgreSQLContainer();

@Test
public void whenSelectQueryExecuted_thenResulstsReturned()
  throws Exception {
    String jdbcUrl = postgresContainer.getJdbcUrl();
    String username = postgresContainer.getUsername();
    String password = postgresContainer.getPassword();
    Connection conn = DriverManager
      .getConnection(jdbcUrl, username, password);
    ResultSet resultSet = 
      conn.createStatement().executeQuery("SELECT 1");
    resultSet.next();
    int result = resultSet.getInt(1);
    
    assertEquals(1, result);
}

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

4.2. Веб-драйверы

Другой полезный сценарий — запуск контейнеров с помощью веб-браузеров. Правило BrowserWebDriverContainer позволяет запускать Chrome и Firefox в контейнерах docker-selenium. Затем мы управляем ими с помощью RemoteWebDriver.

Это очень полезно для автоматизации UI/приемочных тестов для веб-приложений:

@Rule
public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer()
  .withCapabilities(new ChromeOptions());
@Test
public void whenNavigatedToPage_thenHeadingIsInThePage() {
    RemoteWebDriver driver = chrome.getWebDriver();
    driver.get("http://example.com");
    String heading = driver.findElement(By.xpath("/html/body/div/h1"))
      .getText();
 
    assertEquals("Example Domain", heading);
}

4.3. Docker Compose

Если для тестов требуются более сложные сервисы, мы можем указать их в файле docker-compose:

simpleWebServer:
  image: alpine:3.2
  command: ["/bin/sh", "-c", "while true; do echo 'HTTP/1.1 200 OK\n\nHello World!' | nc -l -p 80; done"]

Затем мы используем правило DockerComposeContainer. Это правило запускает и запускает службы, как определено в файле компоновки.

Мы используем методы getServiceHost и getServicePost для построения адреса подключения к сервису:

@ClassRule
public static DockerComposeContainer compose = 
  new DockerComposeContainer(
    new File("src/test/resources/test-compose.yml"))
      .withExposedService("simpleWebServer_1", 80);

@Test
public void givenSimpleWebServerContainer_whenGetReuqest_thenReturnsResponse()
  throws Exception {
 
    String address = "http://" + compose.getServiceHost("simpleWebServer_1", 80) + ":" + compose.getServicePort("simpleWebServer_1", 80);
    String response = simpleGetRequest(address);
    
    assertEquals(response, "Hello World");
}

5. Заключение

Мы увидели, как можно использовать библиотеку TestContainers. Это упрощает разработку и запуск интеграционных тестов.

Мы использовали правило GenericContainer для контейнеров заданных образов докеров. Затем мы рассмотрели правила PostgreSQLContainer, BrowserWebDriverContainer и DockerComposeContainer. Они дают больше функциональности для конкретных случаев использования.

Наконец, примеры кода здесь можно найти на GitHub.