Давайте поговорим о том, как использовать Testcontainers для интеграционного тестирования.
Давайте поговорим о том, как использовать Testcontainers для интеграционного тестирования.

Предисловие

1. Что такое тест-контейнеры?

Testcontainers — это библиотека, которая предоставляет простой и легкий API для начальной загрузки локальной разработки и тестирования зависимостей, инкапсулируя реальные сервисы в контейнеры Docker. Используя Testcontainers, вы можете писать тесты, основанные на тех же сервисах, которые вы используете в рабочей среде, без необходимости использования макетов или сервисов в памяти.

Говоря более простыми словами, этоtestcontainers Он позволяет запускать Docker-контейнер через язык программирования и автоматически закрывать контейнер после завершения тестирования программы.

2. Каковы преимущества тестконтейнеров?

  • Каждый тест Группа может писать Интеграционное так же детально, как и писать модульные тесты. Обновление, обеспечивающее высокий охват тестирования для каждого интегрированного модуля.
  • Тестовые группы зависят друг от друга, что означает, что они не используют общие контейнеры Docker.
  • Это обеспечивает согласованность между производственной средой и тестовой средой. При онлайн-развертывании кода вы не столкнетесь с ошибками, вызванными несовместимостью зависимых сервисных интерфейсов.
  • Группы тестов можно запускать параллельно, что сокращает общее время выполнения тестов. Для сравнения, некоторые реализации зависимых служб в памяти не обеспечивают хорошей изоляции ресурсов, таких как порты. Если они выполняются параллельно, возникают конфликты портов.
  • Благодаря Docker все тесты можно проводить в локальной среде и Работа в среде CI/CD, отладка и написание тестового кода аналогичны написанию модульных тестов.
  • Поддерживает основные языки и платформы на рынке, такие как Java, Go, Python и т. д.

3. На что следует обратить внимание при использовании тест-контейнеров?

  • Тестовые контейнеры основаны на Docker, поэтому перед использованием тестовых контейнеров вам необходимо полагаться на среду Docker.
  • Среду, предоставляемую Testcontainers, нельзя использовать в производственных средах, ее можно использовать только в тестовых средах и других сценариях.

4. Стратегия подключения тестконтейнеров к докеру

Язык кода:javascript
копировать
Тестовые контейнеры во время выполнения попытаются подключиться, используя следующие стратегии в следующем порядке: Docker Демон:

Переменные среды: – DOCKER_HOST – DOCKER_TLS_VERIFY – DOCKER_CERT_PATH

Роль каждой переменной:

  • DOCKER_HOST to set the url to the docker server.
  • DOCKER_CERT_PATH to load the tls certificates from.
  • UseDOCKER_TLS_VERIFY to enable or disable TLS verification.

значение по умолчаниюDOCKER_HOST=https://localhost:2376 – DOCKER_TLS_VERIFY=1 – DOCKER_CERT_PATH=~/.docker

Мы можем изменить приведенные выше значения с помощью переменной среды, Пример

Язык кода:javascript
копировать
System.setProperty("DOCKER_HOST","tcp://192.168.0.1:2375")

Примечание: Путем модификации программы мы должны убедиться, что System.setProperty установлен до того, как Testcontainers запустит контейнер, иначе он не вступит в силу.

Вышеуказанный контент можно найти на официальном сайте.https://java.testcontainers.org/supported_docker_environment/Найти более подробное введение

Ниже приведена демонстрация использования тест-контейнеров, интегрирующих Redis и модульное тестирование через Junit5 в качестве примера.

Пример

1. Внедряем junit5 gav в pom проекта.

Язык кода:javascript
копировать
 <properties>
        <junit-platform.version>1.9.2</junit-platform.version>
        <junit-jupiter.version>5.9.2</junit-jupiter.version>
    </properties>

  <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit-jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit-jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <version>${junit-jupiter.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-commons</artifactId>
            <version>${junit-platform.version}</version>
            <scope>test</scope>
        </dependency>

Примечание: Если вы используете более позднюю версию Springboot, вы можете импортировать ее напрямую.

Язык кода:javascript
копировать
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
          <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-commons</artifactId>
            <version>${junit-platform.version}</version>
            <scope>test</scope>
        </dependency>

Вот и все

2. Внедрить тест-контейнеры gav в pom в проекте.

Язык кода:javascript
копировать
<properties>     
<testcontainers.version>1.17.3</testcontainers.version>
    </properties>
  <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <version>${testcontainers.version}</version>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${testcontainers.version}</version>
            <scope>test</scope>
        </dependency>

Конечно, нам также нужно представить клиент redis gav, потому что это должны знать все, поэтому я не буду его вводить.

3. В нашем модульном тесте позвольте testcontainers запустить контейнер Redis.

Пример кода выглядит следующим образом

Язык кода:javascript
копировать
  @Container
    private static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:6.2.6"))
            .withExposedPorts(6379);

Приведенный выше код означает создание зеркала в виде контейнера redis:6.2.6 и предоставление порта 6379.

Также на тестовом занятии,Нужно добавить@Testcontainers(disabledWithoutDocker = true) аннотация

Язык кода:javascript
копировать
@Testcontainers(disabledWithoutDocker = true)
public class RedisTest {
 @Container
    private static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:6.2.6"))
            .withExposedPorts(6379);
}

4. Интегрируйте наши бизнес-программы с контейнерами.

Язык кода:javascript
копировать
private Jedis jedis;

    @BeforeEach
    public void setUp() {

        int port = redis.getMappedPort(6379);
        jedis = new Jedis(redis.getHost(), port);
    }

5. Запустите модульные тесты

Язык кода:javascript
копировать
@Testcontainers(disabledWithoutDocker = true)
public class RedisTest {

    @Container
    private static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:6.2.6"))
            .withExposedPorts(6379);


    private Jedis jedis;

    @BeforeEach
    public void setUp() {

        int port = redis.getMappedPort(6379);
        jedis = new Jedis(redis.getHost(), port);
    }

    @AfterEach
    public void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
    }

    @Test
    public void testRedisConnectionAndSetAndGet() {
        // Тестовое соединение и простой доступ
        String key = "testKey";
        String value = "testValue";

        jedis.set(key, value);
        String result = jedis.get(key);

        assert result.equals(value);
    }

Сначала мы можем наблюдать за Docker-контейнером и обнаружить, что Redis-контейнер успешно запущен.

Давайте еще раз посмотрим на результаты модульного теста. Они такие же, как мы и ожидали.

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

Обнаружено, что контейнер уничтожен

Приведенные выше примеры также содержат подробные руководства на официальном сайте, вы можете проверить следующую ссылку. https://java.testcontainers.org/quickstart/junit_5_quickstart/

В настоящее время наши проекты в основном интегрированы с Springboot. Далее мы кратко продемонстрируем интеграцию тестовых контейнеров, Springboot и Redis.

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

Язык кода:javascript
копировать
@SpringBootTest(classes = TestcontainersApplication.class,webEnvironment = SpringBootTest.WebEnvironment.NONE)
@Testcontainers(disabledWithoutDocker = true)
public class RedisContainerByDynamicPropertySourceTest {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Container
    private static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:6.2.6"))
            .withExposedPorts(6379);

//    @BeforeEach
//    public void setUp() {
//
//        System.setProperty("spring.redis.host", redis.getHost());
//        System.setProperty("spring.redis.port", redis.getMappedPort(6379).toString());
//    }

    /**
     * Spring TEST DynamicPropertySource был представлен в версии 5.2.5.
     * @param registry
     */
    @DynamicPropertySource
    private static void registerRedisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.redis.host", redis::getHost);
        registry.add("spring.redis.port", () -> redis.getMappedPort(6379)
                .toString());
    }

    @Test
    public void testRedisConnectionAndSetAndGet() {
        // Тестовое соединение и простой доступ
        String key = "testKey";
        String value = "testValue";
        redisTemplate.opsForValue().set(key, value);
        String result = redisTemplate.opsForValue().get(key);

        assert Objects.equals(result, value);
    }
}

Основной код

Язык кода:javascript
копировать
  @DynamicPropertySource
    private static void registerRedisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.redis.host", redis::getHost);
        registry.add("spring.redis.port", () -> redis.getMappedPort(6379)
                .toString());
    }

Эта аннотация доступна только после весенней версии 5.2.5.,Когда вы заранее не знаете значение атрибута,проходить@DynamicPropertySourceиDynamicPropertyRegistry Совместное размещение может реализовать динамическую привязку атрибутов. Подробное описание можно найти на официальном сайте Spring. https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/dynamic-property-sources.html

Примечание: Если версия Springboot относительно низкая, вам необходимо ввести следующий gav в pom проекта, чтобы использовать DynamicPropertySource

Язык кода:javascript
копировать
  <spring.version>5.2.15.RELEASE</spring.version>
  <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

Просмотр результатов модульного теста

Подводные камни, возникающие при использовании тестконтейнеров

Примечание: Поскольку мое окно не поддерживает виртуализацию, я не могу установить Docker. рабочий стол. Поэтому мой Пример подключен к удаленному серверу для тестирования.

Поскольку вы хотите подключиться к удаленному докер-серверу, необходимо открыть порт 2375. Шаги для включения следующие:

Язык кода:javascript
копировать
vim /usr/lib/systemd/system/docker.service
изменить значение по умолчанию
ExecStart=/usr/bin/dockerd -H unix://var/run/docker.sock \

Измените следующим образом

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375

Просто добавьте его напрямую,В Интернете много чего написано

Язык кода:javascript
копировать
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock \

Написание таким образом приведет к ошибке. После модификации выполните

Язык кода:javascript
копировать
 systemctl daemon-reload
 service docker restart 

проходить

Язык кода:javascript
копировать
ps -ef | grep docker

Проверьте, открыт ли порт 2375

Друзья, которые были заминированы, знают, что многие хосты считаются майнинговыми машинами, поскольку порт 2375 открыт в общедоступной сети. Таким образом, инструмент applyssh может создать туннель и обеспечить доступ к туннелю. Пример

Однако именно из-за прохождения здесь доступа к туннелю он очень затруднителен в дальнейшем.

Начните объяснять подводные камни

Яма 1: тестовые контейнеры не могут подключиться к удаленному докеру

Сначала я был подвергнут

Язык кода:javascript
копировать
System.setProperty("DOCKER_HOST","tcp://192.168.0.1:2375")

Настроил, потому что точка, которую я установил, позже времени, когда Testcontainers создал контейнер, поэтому Testcontainers подключились к локальному докеру. Поскольку я не устанавливал докер локально, я не смог подключиться.

мы можемпроходитьсуществоватьideaначальствонастраивать

Но есть блогер, который еще лучше. Он напрямую модифицирует код. Измените содержимое кода следующим образом.

Примечание: В проекте необходимо представить следующий GAV

Язык кода:javascript
копировать
<dependency>
            <groupId>com.github.docker-java</groupId>
            <artifactId>docker-java</artifactId>
            <version>3.2.13</version>
        </dependency>
Язык кода:javascript
копировать
/**
 * Стратегия пользовательского подключения Docker для testContainer
 */
@Slf4j
public class MyDockerClientProviderStrategy extends DockerClientProviderStrategy {

    private final DockerClientConfig dockerClientConfig;

    private static final String DOCKER_HOST = "tcp://127.0.0.1:2375";

	/**
	* Настраиваем dockerClientConfig во время инициализации, используем docker-java для подключения к докеру
	*/
    public MyDockerClientProviderStrategy() {
        DefaultDockerClientConfig.Builder configBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder();
        configBuilder.withDockerHost(DOCKER_HOST);
        //проход настраивается следующим образом: закрываем RYUK и решаем Could not connect to Ryuk at localhost
        System.setProperty("TESTCONTAINERS_RYUK_DISABLED","true");
//		// Включить проверку dockerTLS
//        configBuilder.withDockerTlsVerify(true);
//        // Измените папку, в которой находится ключ, на каталог вашего проекта. и все
//        configBuilder.withDockerCertPath("C:\\Users\\Administrator\\Desktop\\docker");

        dockerClientConfig = configBuilder.build();
    }

	/**
	* Определите конфигурацию подключения докера здесь
	*/
    @Override
    public TransportConfig getTransportConfig() {
        return TransportConfig.builder()
                .dockerHost(dockerClientConfig.getDockerHost())
                .sslConfig(dockerClientConfig.getSSLConfig())
                .build();
    }

	/**
	* В соответствии со вторым фильтром выше, он всегда возвращает trueВот. и все。
	*/
    @Override
    protected boolean isApplicable() {
        return true;
    }

    @Override
    public String getDescription() {
        return "my-custom-strategy";
    }
}

Создайте META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy в src/main/resources. Содержимое файла следующее

Язык кода:javascript
копировать
com.github.lybgeek.testcontainers.MyDockerClientProviderStrategy

По сути, это механизм spi. Содержание статьи блогера следующее. Друзья, кому интересно, могут ее прочитать. https://blog.csdn.net/LHFFFFF/article/details/127117917

Причина: Не удалось подключиться к Рюку на локальном хосте.

Я не знаю, что это такое, это официальная информация. https://github.com/testcontainers/testcontainers-java/issues/3609#issuecomment-769615098 настраивать

Язык кода:javascript
копировать
TESTCONTAINERS_RYUK_DISABLED=true

Отключить РЮК

Отключение, похоже, не дает никакого эффекта.

Пример: истекло время ожидания открытия порта контейнера (порты локального хоста: [] должны прослушиваться)

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

Например, порт 32788.,После перезапуска оно станет 32789. Позже я создам случайную группу безопасности порта.,Например, разрешите доступ к сегменту порта 30000-40000. Так что проблема временно решена

Подвести итог

Эта статья — всего лишь введение. На официальном сайте Testcontainers есть более подробные примеры. Если вам интересно, вы можете ознакомиться с ними. https://testcontainers.com/guides/

демо-ссылка

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-testcontainers

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose