Введение: Почему тестирование в PHP — это не опция, а стандарт
В современной PHP-разработке тестирование давно перестало быть «хорошим тоном» и превратилось в обязательный этап production-проектов. Без покрытия кода тестами вы рискуете выпустить в продакшн ошибку, которая может стоить бизнесу денег и репутации. В этой статье мы разберем три уровня тестирования: unit (модульное), integration (интеграционное) и e2e (сквозное). Вы узнаете, чем они отличаются, какие инструменты использовать (PHPUnit, Pest, Laravel Dusk, Codeception) и как выстроить правильную стратегию тестирования для вашего PHP-проекта.
1. Unit-тесты: тестируем изолированные единицы кода
Unit-тесты проверяют работу отдельных классов, функций или методов в изоляции от внешних зависимостей (БД, API, файловая система). Это самый быстрый и дешевый вид тестов. Основной инструмент в PHP — PHPUnit (фактический стандарт) и его «модная» альтернатива Pest.
1.1. Установка и первый тест с PHPUnit
Установите PHPUnit через Composer:
composer require --dev phpunit/phpunit
Создайте тест для простого класса-калькулятора:
<?php
// src/Calculator.php
class Calculator {
public function add(int $a, int $b): int {
return $a + $b;
}
}
// tests/CalculatorTest.php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase {
public function testAddition(): void {
$calc = new Calculator();
$result = $calc->add(2, 3);
$this->assertEquals(5, $result);
}
}
Запустите тест:
./vendor/bin/phpunit tests
1.2. Использование Mock-объектов
Чтобы изолировать тестируемый класс, мы «подменяем» его зависимости с помощью моков. Пример с репозиторием пользователей:
<?php
class UserService {
private UserRepository $repo;
public function __construct(UserRepository $repo) {
$this->repo = $repo;
}
public function getUserName(int $id): string {
$user = $this->repo->findById($id);
return $user ? $user->getName() : 'Guest';
}
}
// В тесте
$mockRepo = $this->createMock(UserRepository::class);
$mockRepo->method('findById')
->with(1)
->willReturn(new User('Alice'));
$service = new UserService($mockRepo);
$this->assertEquals('Alice', $service->getUserName(1));
Best practice: Пишите unit-тесты на всю бизнес-логику. Они должны выполняться за секунды и не зависеть от окружения.
2. Integration-тесты: проверяем взаимодействие с БД, API и внешними сервисами
Интеграционные тесты проверяют, как ваши классы работают вместе с реальными компонентами: базой данных, кэшем, внешними HTTP-сервисами. Они медленнее unit-тестов, но дают больше уверенности в том, что система работает как единое целое.
2.1. Тестирование работы с базой данных (Laravel + RefreshDatabase)
Если вы используете Laravel, интеграционные тесты становятся очень простыми благодаря трейту RefreshDatabase:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserRegistrationTest extends TestCase {
use RefreshDatabase;
public function test_user_can_register(): void {
$response = $this->post('/register', [
'name' => 'John',
'email' => 'john@example.com',
'password' => 'secret123',
]);
$response->assertStatus(302); // redirect after success
$this->assertDatabaseHas('users', [
'email' => 'john@example.com'
]);
}
}
Трейт RefreshDatabase автоматически мигрирует БД перед тестами и откатывает транзакции после каждого теста, сохраняя изоляцию.
2.2. Тестирование внешних API с помощью Http Fake (Laravel)
Для тестирования интеграции с внешними сервисами используйте фасад Http::fake():
<?php
use Illuminate\Support\Facades\Http;
public function test_weather_api_integration(): void {
Http::fake([
'api.weather.com/*' => Http::response([
'temp' => 25,
'city' => 'Moscow'
], 200),
]);
$service = new WeatherService();
$result = $service->getTemperature('Moscow');
$this->assertEquals(25, $result);
}
2.3. Инструменты для интеграционного тестирования
- PHPUnit — универсальный фреймворк, используется и для unit, и для integration.
- Codeception — модульный инструмент, поддерживает тесты для БД, REST API, очередей.
- Doctrine Fixtures / Faker — для генерации тестовых данных.
3. E2E-тесты: сквозное тестирование через браузер
E2E (End-to-End) тесты симулируют действия реального пользователя: клики, заполнение форм, навигацию. Они проверяют работу фронтенда и бэкенда вместе. Это самые медленные и хрупкие тесты, но они незаменимы для критических сценариев (регистрация, оформление заказа).
3.1. Laravel Dusk — встроенный инструмент для E2E
Laravel Dusk использует ChromeDriver (или Selenium) для управления браузером. Установка:
composer require --dev laravel/dusk
php artisan dusk:install
Пример теста для формы логина:
<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class LoginTest extends DuskTestCase {
public function test_user_can_login(): void {
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->type('email', 'admin@example.com')
->type('password', 'password')
->press('Login')
->assertPath