Тестирование в PHP: Unit, Integration, E2E — полный гайд

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

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

Начать курс

Введение: зачем нужно тестирование в PHP?

Разработка на PHP — это не просто написание кода, который «работает». В современной веб-разработке стабильность и предсказуемость приложения выходят на первый план. Без тестирования любое изменение в коде рискует сломать уже работающий функционал, а поиск ошибок вручную превращается в бесконечную «охоту на баги». Именно здесь на помощь приходит тестирование.

В этой статье мы разберём три ключевых уровня тестирования PHP-приложений: Unit (модульное), Integration (интеграционное) и E2E (сквозное). Вы узнаете, чем они отличаются, какие инструменты использовать и как построить эффективную стратегию тестирования для вашего проекта.

Unit-тесты: основа пирамиды

Unit-тесты (модульные) — это самый быстрый и изолированный уровень тестирования. Их цель — проверить работу отдельного «юнита» (класса, метода или функции) в отрыве от внешних зависимостей: базы данных, файловой системы, API.

Инструменты для PHP

Самый популярный фреймворк — PHPUnit. Более современная альтернатива — Pest, который предлагает более лаконичный синтаксис.

Пример Unit-теста на PHPUnit

Допустим, у нас есть простой класс-калькулятор:

<?php
// src/Calculator.php
class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }

    public function divide(int $a, int $b): float
    {
        if ($b === 0) {
            throw new \InvalidArgumentException('Division by zero');
        }
        return $a / $b;
    }
}

Тест для него будет выглядеть так:

<?php
// tests/Unit/CalculatorTest.php
use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase
{
    private Calculator $calculator;

    protected function setUp(): void
    {
        $this->calculator = new Calculator();
    }

    public function testAddReturnsCorrectSum(): void
    {
        $result = $this->calculator->add(2, 3);
        $this->assertEquals(5, $result);
    }

    public function testDivideThrowsExceptionOnZero(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->calculator->divide(10, 0);
    }
}

Ключевые принципы Unit-тестов:

  • Изоляция: все внешние зависимости (БД, API) заменяются на mock (заглушки) или stub.
  • Скорость: выполняются за миллисекунды.
  • Проверяют один конкретный сценарий (один assert на тест — хорошая практика).

Запуск: ./vendor/bin/phpunit tests/Unit

Integration-тесты: проверяем взаимодействие

Интеграционные тесты идут на уровень выше. Они проверяют, как разные части системы работают вместе: класс с базой данных, контроллер с моделью, сервис с внешним API (реальным или симулированным).

Когда нужны Integration-тесты?

  • Проверка корректности SQL-запросов.
  • Валидация связи между слоями приложения (например, Repository + Entity).
  • Тестирование middleware или событий (events).

Пример: тестирование репозитория с реальной БД

Для интеграционных тестов часто используют тестовую базу данных (SQLite в памяти или транзакции в PostgreSQL). Рассмотрим пример с использованием PHPUnit и трейта для сброса БД:

<?php
// tests/Integration/UserRepositoryTest.php
use PHPUnit\Framework\TestCase;

class UserRepositoryTest extends TestCase
{
    private PDO $pdo;
    private UserRepository $repository;

    protected function setUp(): void
    {
        // Создаём in-memory SQLite для изоляции
        $this->pdo = new PDO('sqlite::memory:');
        $this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
        $this->repository = new UserRepository($this->pdo);
    }

    public function testSaveAndFindUser(): void
    {
        $user = new User('John', 'john@example.com');
        $this->repository->save($user);

        $foundUser = $this->repository->findByEmail('john@example.com');
        $this->assertNotNull($foundUser);
        $this->assertEquals('John', $foundUser->getName());
    }

    public function testFindReturnsNullForNonExistentEmail(): void
    {
        $result = $this->repository->findByEmail('ghost@example.com');
        $this->assertNull($result);
    }
}

Важно: интеграционные тесты медленнее unit-тестов (секунды вместо миллисекунд), но они дают гораздо больше уверенности в том, что система действительно работает. Для фреймворков (Laravel, Symfony) есть встроенные инструменты, упрощающие этот процесс (например, RefreshDatabase в Laravel).

E2E-тесты: проверка с точки зрения пользователя

Сквозное тестирование (End-to-End) — это самый «тяжёлый» уровень. E2E-тесты эмулируют действия реального пользователя: открытие браузера, заполнение форм, клики, переходы по страницам. Они проверяют всё приложение целиком: фронтенд, бэкенд, базу данных, внешние сервисы.

Инструменты для E2E в PHP

  • Codeception — мощный фреймворк, поддерживающий модульное, интеграционное и E2E тестирование (через WebDriver).
  • Laravel Dusk — встроенное решение для Laravel.
  • Symfony Panther — для Symfony-проектов.

Пример E2E-теста с Codeception

Предположим, у нас есть страница логина. Тест должен открыть её, ввести данные и проверить, что пользователь перенаправлен на дашборд.

<?php
// tests/acceptance/LoginCept.php
$I = new AcceptanceTester($scenario);
$I->wantTo('Проверить успешный вход в систему');

// Открываем страницу логина
$I->amOnPage('/login');
$I->see('Войти', 'button');

// Заполняем форму
$I->                

Блоги

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