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

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

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

Начать курс

Интеграционное тестирование PHP с Docker: от А до Я

Современная PHP-разработка немыслима без качественного тестирования. Если модульные тесты проверяют изолированные куски кода, то интеграционное тестирование убеждается, что все компоненты системы работают вместе: база данных, Redis, внешние API и файловая система. Docker делает этот процесс воспроизводимым, быстрым и независимым от окружения разработчика. В этой статье мы разберем, как настроить интеграционное тестирование PHP-приложения с помощью Docker с нуля.

Почему Docker — идеальный инструмент для интеграционных тестов?

Традиционные проблемы интеграционного тестирования:

  • Зависимость от окружения — у каждого разработчика своя версия MySQL, PHP, Redis.
  • Загрязнение данных — тесты оставляют мусор в реальной БД.
  • Сложность параллельного запуска — тесты конфликтуют друг с другом.

Docker решает эти проблемы:

  • Каждый тест запускается в чистом контейнере с известными версиями зависимостей.
  • Контейнеры изолированы и удаляются после прогона.
  • Docker Compose позволяет поднять всю инфраструктуру одной командой.

Шаг 1: Структура проекта и Docker Compose

Начнем с типичного PHP-приложения на Symfony. Создадим файл docker-compose.test.yml для тестового окружения:

version: '3.8'
services:
  php:
    build:
      context: .
      dockerfile: Dockerfile.test
    environment:
      DATABASE_URL: "mysql://test:test@mysql:3306/test_db?serverVersion=8.0"
      REDIS_URL: "redis://redis:6379"
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_db
      MYSQL_USER: test
      MYSQL_PASSWORD: test
    tmpfs: /var/lib/mysql  # Данные в памяти — быстрее

  redis:
    image: redis:7-alpine
    tmpfs: /data

Файл Dockerfile.test:

FROM php:8.2-cli

RUN docker-php-ext-install pdo_mysql
RUN pecl install redis && docker-php-ext-enable redis

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

WORKDIR /app
COPY . .

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

CMD ["vendor/bin/phpunit", "--configuration", "phpunit.xml.dist"]

Обратите внимание на tmpfs — это монтирование временной файловой системы в память. База данных и Redis будут работать в разы быстрее, чем на диске, что критично для быстрых тестов.

Шаг 2: Настройка PHPUnit для интеграционных тестов

Создадим отдельную конфигурацию phpunit.integration.xml.dist:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="tests/bootstrap.php"
         colors="true">
    <testsuites>
        <testsuite name="Integration">
            <directory>tests/Integration</directory>
        </testsuite>
    </testsuites>
    <php>
        <env name="APP_ENV" value="test"/>
        <env name="KERNEL_CLASS" value="App\Kernel"/>
    </php>
</phpunit>

Файл tests/bootstrap.php для инициализации тестового окружения:

<?php

require dirname(__DIR__) . '/vendor/autoload.php';

// Ждем, пока MySQL и Redis будут готовы
$maxAttempts = 30;
$connected = false;

for ($i = 0; $i < $maxAttempts; $i++) {
    try {
        $pdo = new PDO(
            $_ENV['DATABASE_URL'],
            null, null,
            [PDO::ATTR_TIMEOUT => 1]
        );
        $connected = true;
        break;
    } catch (PDOException $e) {
        sleep(1);
    }
}

if (!$connected) {
    throw new RuntimeException('Не удалось подключиться к MySQL');
}

// Создаем схему БД
$pdo->exec(file_get_contents(dirname(__DIR__) . '/migrations/schema.sql'));

Шаг 3: Пишем интеграционные тесты

Рассмотрим три типичных сценария: работа с БД, Redis и внешним API.

Тестирование базы данных

<?php

namespace App\Tests\Integration;

use PHPUnit\Framework\TestCase;
use App\Repository\UserRepository;

class UserRepositoryTest extends TestCase
{
    private static \PDO $pdo;

    public static function setUpBeforeClass(): void
    {
        self::$pdo = new PDO($_ENV['DATABASE_URL']);
        // Очищаем таблицу перед тестом
        self::$pdo->exec('TRUNCATE TABLE users');
    }

    public function testCreateUser(): void
    {
        $repo = new UserRepository(self::$pdo);

        $userId = $repo->create('test@example.com', 'password_hash');
        $this->assertIsInt($userId);
        $this->assertGreaterThan(0, $userId);

        // Проверяем, что пользователь действительно сохранен
        $user = $repo->findById($userId);
        $this->assertNotNull($user);
        $this->assertEquals('test@example.com', $user['email']);
    }
}

Тестирование работы с Redis

<?php

namespace App\Tests\Integration;

use PHPUnit\Framework\TestCase;
use App\Service\SessionManager;

class SessionManagerTest extends TestCase
{
    private static \Redis $redis;

    public static function setUpBeforeClass(): void
    {
        self::$redis = new \Redis();
        self::$redis->connect('redis', 6379);
        self::$redis->flushAll(); // Очищаем перед тестом
    }

    public function testSetAndGetSession(): void
    {
        $manager = new SessionManager(self::$redis);

        $sessionId = 'test_session_123';
        $data = ['user_id' => 1, 'role' => 'admin'];

        $manager->set($sessionId, $data, 3600);
        $result = $manager->get($sessionId);

        $this->assertEquals($data, $result);
    }

    public                

Блоги

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