Интеграционное тестирование PHP с Docker: полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему интеграционное тестирование PHP в Docker — это must-have

Разработка современных PHP-приложений редко ограничивается одним сервером. Базы данных, кэши (Redis, Memcached), очереди (RabbitMQ), внешние API — всё это требует проверки взаимодействия компонентов. Модульные тесты (unit tests) изолируют код, но не гарантируют, что класс A правильно работает с классом B через реальную базу данных. Здесь на помощь приходит интеграционное тестирование.

Проблема классического подхода — настройка окружения. Разработчик тратит часы на поднятие MySQL, настройку прав, установку расширений PHP. Docker решает эту проблему раз и навсегда. В этой статье мы разберём, как организовать интеграционное тестирование PHP-приложения с использованием Docker и Docker Compose. Вы получите готовые конфиги, примеры кода и рекомендации по оптимизации.

1. Настройка Docker-окружения для PHP-тестов

1.1. Базовая структура проекта

Предположим, у нас есть простое PHP-приложение на Symfony или Laravel (подойдёт любой фреймворк). Создадим файл docker-compose.yml в корне проекта:

version: '3.8'

services:
  php:
    build: .
    volumes:
      - .:/var/www/html
    environment:
      - DATABASE_URL=mysql://test_user:test_pass@db:3306/test_db
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root_pass
      MYSQL_DATABASE: test_db
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_pass
    ports:
      - "3307:3306"  # Проброс на нестандартный порт, чтобы не конфликтовать с локальной БД

  test_runner:
    image: php:8.2-cli
    volumes:
      - .:/var/www/html
    working_dir: /var/www/html
    entrypoint: ["vendor/bin/phpunit"]
    depends_on:
      - db
      - php

Ключевые моменты:

  • Сервис test_runner использует отдельный контейнер с PHP CLI — это изолирует выполнение тестов от основного приложения.
  • База данных поднимается с чистой схемой. Для каждого прогона тестов мы можем пересоздавать её.
  • Порт MySQL проброшен на 3307, чтобы не мешать локальной БД разработчика.

1.2. Dockerfile для PHP-приложения

Создадим Dockerfile для основного сервиса php:

FROM php:8.2-fpm

RUN docker-php-ext-install pdo_mysql mysqli

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

COPY . .

RUN composer install --no-interaction --prefer-dist

Этот образ будет использоваться как для разработки, так и для тестов. Важно установить расширение pdo_mysql — без него тесты, работающие с БД, упадут.

2. Написание интеграционных тестов на PHPUnit

2.1. Базовая настройка PHPUnit

Убедитесь, что у вас установлен PHPUnit (через Composer). Файл phpunit.xml должен указывать на тестовую базу данных:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
    <php>
        <env name="DATABASE_URL" value="mysql://test_user:test_pass@db:3306/test_db" />
        <env name="APP_ENV" value="test" />
    </php>
    <testsuites>
        <testsuite name="Integration">
            <directory>tests/Integration</directory>
        </testsuite>
    </testsuites>
</phpunit>

2.2. Пример интеграционного теста: работа с БД

Допустим, у нас есть репозиторий пользователей. Напишем тест, который проверяет сохранение и поиск:

<?php

namespace App\Tests\Integration;

use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class UserRepositoryTest extends KernelTestCase
{
    private ?EntityManagerInterface $entityManager;
    private ?UserRepository $userRepository;

    protected function setUp(): void
    {
        $kernel = self::bootKernel();
        $this->entityManager = $kernel->getContainer()
            ->get('doctrine')
            ->getManager();
        $this->userRepository = $this->entityManager->getRepository(User::class);

        // Очищаем таблицу перед каждым тестом
        $this->entityManager->createQuery('DELETE FROM App\Entity\User')->execute();
    }

    public function testSaveAndFindUser(): void
    {
        $user = new User();
        $user->setEmail('test@example.com');
        $user->setName('Test User');

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        $foundUser = $this->userRepository->findOneBy(['email' => 'test@example.com']);

        $this->assertNotNull($foundUser);
        $this->assertEquals('Test User', $foundUser->getName());
    }

    protected function tearDown(): void
    {
        parent::tearDown();
        $this->entityManager->close();
        $this->entityManager = null;
    }
}

Важно: В методе setUp() мы очищаем БД. Это гарантирует, что каждый тест начинается с чистого состояния. Для больших проектов используйте транзакции с откатом — это быстрее, чем полная очистка таблиц.

3. Запуск тестов и управление окружением

3.1. Команды для запуска

Соберём и запустим контейнеры:

docker-compose up -d --build
docker-compose run --rm test_runner

Первая команда поднимает сервисы. Вторая запускает PHPUnit внутри контейнера test_runner. Флаг --rm удаляет контейнер после завершения тестов — это экономит место.

Чтобы пересоздать базу данных перед тестами, добавьте скрипт bin/reset-test-db.sh:

                

Блоги

Рекомендаций по книгам