От одного контейнера к полному стеку

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

Для QA Docker Compose — это трансформация. Вместо ручной настройки PostgreSQL, Redis и приложения перед запуском интеграционных тестов, вы определяете всё в файле docker-compose.yml и запускаете одной командой.

Основы Docker Compose

Минимальный пример

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://test:test@db:5432/testdb
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine

Запуск и остановка

# Запустить все сервисы
docker compose up -d

# Запустить и пересобрать образы
docker compose up -d --build

# Просмотр логов
docker compose logs -f

# Остановить и удалить контейнеры
docker compose down

# Остановить, удалить контейнеры И тома (чистое состояние)
docker compose down -v

Паттерны тестовых окружений

Паттерн 1: Приложение + Зависимости + Test Runner

Самый распространённый паттерн для интеграционного и E2E-тестирования:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=test
      - DATABASE_URL=postgresql://test:test@db:5432/testdb
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    volumes:
      - ./scripts/init-test-db.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 5s
      timeout: 5s
      retries: 5

  tests:
    build:
      context: .
      dockerfile: Dockerfile.tests
    environment:
      - BASE_URL=http://app:3000
      - DATABASE_URL=postgresql://test:test@db:5432/testdb
    depends_on:
      app:
        condition: service_started
    volumes:
      - ./test-results:/app/test-results

Паттерн 2: Полный интеграционный стек

version: '3.8'

services:
  app:
    build: .
    environment:
      - DATABASE_URL=postgresql://test:test@db:5432/testdb
      - REDIS_URL=redis://cache:6379
      - MAIL_HOST=mailhog
      - MAIL_PORT=1025
      - S3_ENDPOINT=http://localstack:4566
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine

  mailhog:
    image: mailhog/mailhog
    ports:
      - "8025:8025"

  localstack:
    image: localstack/localstack
    environment:
      - SERVICES=s3
      - DEFAULT_REGION=us-east-1

Health Checks

Health checks необходимы для надёжности тестов. Без них тесты могут начаться до того, как БД готова принимать соединения.

db:
  image: postgres:15
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U test"]
    interval: 5s
    timeout: 5s
    retries: 5
    start_period: 10s

Упражнение: Создайте полное тестовое окружение

Спроектируйте docker-compose.yml для e-commerce приложения с:

  • Backend API на Node.js
  • Базой данных PostgreSQL (с начальными данными)
  • Кэшем Redis
  • Почтовым сервисом (для подтверждений заказов)
  • Test runner для API и E2E-тестов
  • Результатами тестов, сохранёнными на хост-машине
Решение
version: '3.8'

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=test
      - DATABASE_URL=postgresql://test:test@db:5432/ecommerce_test
      - REDIS_URL=redis://cache:6379
      - MAIL_HOST=mailhog
      - MAIL_PORT=1025
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 5s
      timeout: 5s
      retries: 10

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: ecommerce_test
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    volumes:
      - ./scripts/seed-test-data.sql:/docker-entrypoint-initdb.d/01-seed.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 5s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine

  mailhog:
    image: mailhog/mailhog
    ports:
      - "8025:8025"

  api-tests:
    build:
      context: .
      dockerfile: Dockerfile.tests
    environment:
      - BASE_URL=http://api:3000
      - MAILHOG_URL=http://mailhog:8025
    command: npm run test:api
    depends_on:
      api:
        condition: service_healthy
    volumes:
      - ./test-results/api:/app/test-results

  e2e-tests:
    image: mcr.microsoft.com/playwright:v1.40.0-focal
    working_dir: /app
    volumes:
      - .:/app
      - ./test-results/e2e:/app/test-results
    environment:
      - BASE_URL=http://api:3000
    command: npx playwright test
    depends_on:
      api:
        condition: service_healthy

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

# Запуск API-тестов
docker compose run --rm api-tests

# Запуск E2E-тестов
docker compose run --rm e2e-tests

# Очистка
docker compose down -v

Лучшие практики

  1. Всегда используйте health checks с depends_on: condition: service_healthy. Одного порядка запуска недостаточно.

  2. Используйте docker compose down -v после тестов. Флаг -v удаляет тома, обеспечивая чистое состояние для каждого запуска.

  3. Монтируйте результаты тестов как тома. Это делает отчёты доступными на хост-машине и в CI-артефактах.

  4. Храните тестовые данные в SQL seed-файлах, монтируемых через docker-entrypoint-initdb.d/. Это обеспечивает консистентные, воспроизводимые тестовые данные.

  5. Используйте docker compose run --rm для выполнения тестов. Флаг --rm удаляет контейнер после завершения.

  6. Фиксируйте версии образов. Используйте postgres:15 вместо postgres:latest для предотвращения неожиданных изменений поведения.