ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

Testcontainers и JUnit 5: проверяем PostgreSQL из Java

Автор: · Обновлено

Интеграционный тест SQL должен проверять реальную семантику выбранной базы. Testcontainers запускает временный PostgreSQL для теста, отдаёт фактический JDBC URL и удаляет…

Интеграционный тест SQL должен проверять реальную семантику выбранной базы. Testcontainers запускает временный PostgreSQL для теста, отдаёт фактический JDBC URL и удаляет окружение после завершения. Это позволяет не зависеть от базы на localhost:5432 и не рисковать данными разработчика.

Полный Maven-проект

Нужны JDK 17 или новее, Maven 3.9 и доступный Docker Engine. Проверяйте Docker из того же окружения, где запускается Maven: docker version. Создайте новую папку и файл pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>postgres-container-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.13.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>testcontainers-junit-jupiter</artifactId>
      <version>2.0.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>testcontainers-postgresql</artifactId>
      <version>2.0.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.7.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.5</version>
      </plugin>
    </plugins>
  </build>
</project>

Версии закреплены для воспроизводимого примера, проверенного в сентябре 2026 года. У Testcontainers 2.x новые имена модулей и PostgreSQLContainer находится в пакете org.testcontainers.postgresql. Старые примеры с другими artifactId нельзя смешивать с этим POM. JDBC driver добавлен явно: модуль контейнера не обязан предоставлять его приложению. Зависимости PostgreSQL-модуля.

Тест с данными

Сохраните src/test/java/example/PostgresContainerTest.java:

package example;

import java.sql.DriverManager;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@Testcontainers
class PostgresContainerTest {
    @Container
    static final PostgreSQLContainer db =
        new PostgreSQLContainer("postgres:17-alpine")
            .withDatabaseName("lesson")
            .withUsername("lesson")
            .withPassword("training-only");

    @Test
    void savesAndReadsNote() throws Exception {
        try (var connection = DriverManager.getConnection(
                db.getJdbcUrl(), db.getUsername(), db.getPassword());
             var statement = connection.createStatement()) {
            statement.execute("CREATE TABLE note (id integer PRIMARY KEY, body text NOT NULL)");
            try (var insert = connection.prepareStatement(
                    "INSERT INTO note (id, body) VALUES (?, ?)")) {
                insert.setInt(1, 7);
                insert.setString(2, "container-ready");
                assertEquals(1, insert.executeUpdate());
            }
            try (var rows = statement.executeQuery("SELECT body FROM note WHERE id = 7")) {
                assertTrue(rows.next());
                assertEquals("container-ready", rows.getString("body"));
            }
        }
    }
}

Запуск и критерий успеха

В корне выполните mvn test. Первый запуск скачивает зависимости и служебные образы. Ожидайте один пройденный тест и BUILD SUCCESS. Логи и отчёт находятся в target/surefire-reports. Тест не подключается к произвольной существующей базе: параметры получены у созданного контейнера.

Testcontainers выбирает доступный внешний порт. Не подставляйте 5432 вручную: db.getJdbcUrl() включает getHost() и mapped port. Состояние running ещё не означает готовность базы; PostgreSQL-модуль использует свою стратегию ожидания. Не заменяйте её фиксированным Thread.sleep(5000). Жизненный цикл и подключение.

Что разделяют тесты

Статическое поле @Container используется на уровне класса; база может сохранять состояние между методами этого класса. Обычное нестатическое поле создаёт контейнер для каждого тестового метода. Для нескольких тестов выберите явную изоляцию: отдельные схемы, контролируемую очистку или откат транзакции, если архитектура действительно позволяет его. Порядок методов нельзя использовать как скрытое условие корректности.

Диагностика и упражнение

Could not find a valid Docker environment возникает до SQL: сравните окружение IDE и shell, endpoint Docker, доступ к сокету и версии клиента. Подробная развилка — в [уроке о среде Testcontainers](/lessons/without-university/docker-containerization/docker-developer-33). Не открывайте Docker TCP-порт без защиты и не отключайте уборку ресурсов, чтобы спрятать симптом.

Добавьте проверку нарушения PRIMARY KEY отдельным тестом и обеспечьте независимое начальное состояние. Затем выполните весь класс дважды: оба запуска должны проходить без ручной очистки локального PostgreSQL. Это проверка воспроизводимости и жизненного цикла, а не только успешного SELECT.

Источники