Статьи

Сервисное тестирование с Docker-контейнерами

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

мотивация

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

  • Модульные тесты могут выполняться намного быстрее
  • легче определить, почему тест не удался

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

Если все ваши удаленные вызовы являются поддельными, вы можете никогда не заметить, что вы неправильно настроили свой http-клиент. Если вы никогда не запускали свои тесты на реальной базе данных, вы можете никогда не заметить, что ваша транзакция никогда не фиксируется или что sql, который вы используете для переноса таблицы, работает только с вашей базой данных в памяти.

Одним из способов решения этой проблемы является создание сквозных тестов, которые ведут приложение через внешний интерфейс и выполняют реальное поведение пользователя в системе. Сначала это может звучать как хорошая идея, но часто это приводит к очень хрупкой системе. Тесты могут пройти в один день и провалиться в другой.

  • Некоторые сегменты сети могут быть недоступны
  • Некоторые другие команды приложений могли удалить свою базу данных в промежуточной среде
  • может случиться миллион других вещей

Но все, что вы можете видеть, это то, что ваши тесты не прошли, и вам нужно выяснить, почему.

У вас могут быть гораздо более надежные тесты, если вы тестируете только некоторые компоненты изолированно. Запустите службу со всеми необходимыми зависимостями, выполните запрос к ней и убедитесь, что результат соответствует ожидаемому, будь то ответ, запись в базе данных, сообщение в очереди или что-то еще.

Если в системе используются какие-либо удаленные сервисы, вы можете интегрировать очень простые фиктивные сервисы. В конце концов, целью этих тестов является не тестирование всего, что может быть протестировано, а только некоторые репрезентативные области.

докер

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

Контейнеры запускаются из изображений, указанных с помощью файла Dockerfile. Он может расширять существующий образ (например, контейнер, который обеспечивает среду выполнения Java) и добавлять специфичные для приложения задачи (например, флаги среды выполнения). Пример: следующий dockerfile — это то, что генерируется JHipster для службы.

01
02
03
04
05
06
07
08
09
10
11
12
13
FROM openjdk:8-jre-alpine
 
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
    JHIPSTER_SLEEP=0 \
    JAVA_OPTS=""
 
# add directly the war
ADD *.war /app.war
 
EXPOSE 8081
CMD echo "The application will start in ${JHIPSTER_SLEEP}s..." && \
    sleep ${JHIPSTER_SLEEP} && \
    java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /app.war

Он расширяет образ jre, который предоставляет среду выполнения Java, добавляет файл war с кодом приложения и имеет команду для его запуска. Этот Dockerfile может быть использован для создания образа с помощью docker build Docker, и его можно запустить с помощью docker run .

Если у вас есть несколько контейнеров, которые должны запускаться вместе, как в случае с тестами, которые мы здесь выполняем, вы можете использовать docker compose. Вы указываете сервисы в файле yml, можете передавать переменные окружения и многие другие параметры. Простой пример для сервиса, который использует PostgreSQL.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
version: '2'
services:
    my-service:
        image: my-service
        environment:
            - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/my-service
            - SPRING_DATASOURCE_USERNAME=user
            - SPRING_DATASOURCE_PASSWORD=
    postgresql:
        image: postgres:9.6.2
        environment:
            - POSTGRES_USER=user
            - POSTGRES_PASSWORD=
        ports:
            - 5432:5432

Это запускает два контейнера: один для службы my-service и один для базы данных. Во время выполнения будет выделенная сеть, которая позволяет хостам для контейнеров разрешаться по имени их службы, поэтому в этом примере мы можем использовать url jdbc:postgresql://postgresql:5432/my-service .

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

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

Письменные тесты

Какие тесты вы пишете, конечно, сильно зависит от того, какое приложение вы просматриваете. Для многих приложений это может означать отправку http-запроса в службу и впоследствии проверить, есть ли новая запись в базе данных. Конечно, также может быть проверено много других результатов: создаваемый файл, сообщение, отправляемое в очередь, другой запрос, отправляемый другой службе.

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

Библиотека Java, которая может быть весьма полезна для написания такого рода тестов, называется Awaitility . Он реализует механизмы для проверки асинхронных взаимодействий, в основном посредством опроса. Код для ожидания условия может выглядеть примерно так:

1
await().atMost(5, TimeUnit.SECONDS).until(hasNewEntry());

with hasNewEntry() :

1
2
3
private Callable<Boolean> hasNewEntry() {
        return () -> jdbcTemplate.queryForObject("select count(*) from my_database_entry", Integer.class) > 0;
    }

Самый простой способ запустить тесты — это также запустить для них контейнер, который делает другие сервисы доступными по их имени в качестве имени хоста в той же сети. Для тестов на основе Java вы можете извлечь свое изображение из образа maven и добавить в него файлы своего проекта. Добавляя флаг --abort-on-container-exit для запуска docker-compose, вы можете убедиться, что все контейнеры закрываются, когда заканчивается один контейнер, который, скорее всего, будет тестовым контейнером.

Вы можете добавить интеграционные тесты в файл docker compose:

1
2
3
4
5
6
integrationtest:
        build: integrationtest
        command: ./wait-for-it.sh -t 150 my-service:8081 -- mvn test
        volumes:
          - $PWD/target/surefire-reports:/app/target/surefire-reports
          - ~/.m2/repository:/root/.m2/repository

Я использую функцию wait-for-it, которую можно использовать только для выполнения команды, когда определенная служба доступна. В этом случае мы ждем, пока что-то будет доступно в my-service:8081 с таймаутом в 150 секунд.

Тестовый выходной каталог и локальный репозиторий maven добавляются как тома, что делает их доступными в файловой системе контейнера. Хранилище предотвращает загрузку артефактов снова и снова, выходной каталог теста — это место, где пишутся отчеты.

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

Когда задействованы другие службы, вам, конечно же, нужно убедиться, что они хотя бы доступны. Наличие очень простых пустышек, которые возвращают предопределенные результаты, может уже проделать долгий путь. Опять же, вы можете использовать любую технологию для этого. Для базовых сервисов http такие инструменты, как Express или Spring MVC могут быть хорошим выбором.

Вывод

Мне потребовалось некоторое время, чтобы привыкнуть к различным концепциям в Docker и к тому, как объединить все, но это может быть действительно мощным инструментом для тестирования сервисов в изоляции.
Этот подход намного лучше, чем то, что я видел во многих компаниях: использование реальных сервисов в среде разработчика или промежуточной среде в тестировании, что означает, что, когда они заканчиваются или имеют данные, отличные от ожидаемых, ваши тесты не пройдут. Для более современного подхода к тестированию на производстве (с которым у меня нет большого опыта) взгляните на этот пост Синди Шридхарен .

Смотрите оригинальную статью здесь: Сервисное тестирование с помощью Docker Containers

Мнения, высказанные участниками Java Code Geeks, являются их собственными.