Введение: зачем нужно тестирование в 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->