Тестирование в PHP: unit, integration, e2e — полное руководство

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

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

Начать курс

Введение: зачем тестировать PHP-приложения?

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

В этой статье мы разберём три ключевых уровня тестирования: unit (модульное), integration (интеграционное) и e2e (сквозное). Вы узнаете, чем они отличаются, когда применять каждый из них, и увидите реальные примеры кода на PHP. Независимо от того, используете ли вы Laravel, Symfony или пишете на чистом PHP, эти знания помогут вам создавать надёжные приложения.

Unit-тестирование: проверка изолированных компонентов

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

Инструменты для unit-тестирования в PHP

  • PHPUnit — де-факто стандарт. Мощный, гибкий, поддерживается большинством фреймворков.
  • Pest — современная альтернатива с более лаконичным синтаксисом. Идеально подходит для новичков и команд, ценящих читаемость.

Пример на PHPUnit

Предположим, у нас есть простой класс для расчёта скидки:

<?php
class DiscountCalculator
{
    public function calculate(float $price, float $percent): float
    {
        if ($percent < 0 || $percent > 100) {
            throw new \InvalidArgumentException('Процент скидки должен быть от 0 до 100');
        }
        return $price - ($price * $percent / 100);
    }
}
?>

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

<?php
use PHPUnit\Framework\TestCase;

class DiscountCalculatorTest extends TestCase
{
    public function testCalculateWithValidDiscount(): void
    {
        $calculator = new DiscountCalculator();
        $result = $calculator->calculate(1000, 20);
        $this->assertEquals(800.0, $result);
    }

    public function testCalculateWithZeroPercent(): void
    {
        $calculator = new DiscountCalculator();
        $result = $calculator->calculate(500, 0);
        $this->assertEquals(500.0, $result);
    }

    public function testCalculateThrowsExceptionForInvalidPercent(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $calculator = new DiscountCalculator();
        $calculator->calculate(100, 150);
    }
}
?>

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

  • Один тест — одна проверка (один assert).
  • Тесты не должны зависеть друг от друга.
  • Используйте моки (mocks) для изоляции внешних сервисов. Например, с помощью createMock() в PHPUnit.
  • Покрывайте граничные случаи: null, пустые строки, отрицательные значения.

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

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

Когда писать integration-тесты?

  • Проверка взаимодействия с базой данных (ORM, запросы).
  • Работа с файловой системой (загрузка/чтение файлов).
  • Взаимодействие с внешними API (через тестовые заглушки).
  • Проверка корректности работы репозиториев и сервисного слоя.

Пример на PHP с использованием PDO (без фреймворка)

<?php
use PHPUnit\Framework\TestCase;

class UserRepositoryIntegrationTest extends TestCase
{
    private static \PDO $pdo;
    private UserRepository $repository;

    public static function setUpBeforeClass(): void
    {
        // Используем SQLite в памяти для изолированного теста
        self::$pdo = new \PDO('sqlite::memory:');
        self::$pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
    }

    protected function setUp(): void
    {
        $this->repository = new UserRepository(self::$pdo);
        self::$pdo->exec('DELETE FROM users'); // Очищаем перед каждым тестом
    }

    public function testSaveAndFindUser(): void
    {
        $user = new User('Иван Иванов', 'ivan@example.com');
        $savedId = $this->repository->save($user);

        $foundUser = $this->repository->findById($savedId);
        $this->assertNotNull($foundUser);
        $this->assertEquals('Иван Иванов', $foundUser->getName());
        $this->assertEquals('ivan@example.com', $foundUser->getEmail());
    }

    public function testFindByEmailReturnsNullForNonExistentUser(): void
    {
        $user = $this->repository->findByEmail('nonexistent@example.com');
        $this->assertNull($user);
    }
}
?>

Советы для integration-тестов:

  • Используйте тестовую базу данных (SQLite в памяти или отдельную БД).
  • Очищайте данные между тестами (через транзакции или метод setUp()).
  • Не злоупотребляйте — integration тесты медленнее unit-тестов.
  • Для Laravel используйте RefreshDatabase trait.

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

End-to-end (e2e) тесты имитируют действия реального пользователя: клики, заполнение форм, навигацию. Они проверяют, что вся система работает как единое целое — от фронтенда до базы данных.

Инструменты для e2e в PHP-проектах

  • Codeception — популярный PHP-фреймворк для тестирования. Поддерживает модули для Symfony, Laravel, Yii.
  • Symfony Panther — использует реальный браузер (Chrome/Firefox

Блоги

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