Введение: зачем тестировать 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 используйте
RefreshDatabasetrait.
E2E-тестирование: сквозная проверка пользовательского сценария
End-to-end (e2e) тесты имитируют действия реального пользователя: клики, заполнение форм, навигацию. Они проверяют, что вся система работает как единое целое — от фронтенда до базы данных.
Инструменты для e2e в PHP-проектах
- Codeception — популярный PHP-фреймворк для тестирования. Поддерживает модули для Symfony, Laravel, Yii.
- Symfony Panther — использует реальный браузер (Chrome/Firefox