Тестирование в PHP: unit, integration, e2e от А до Я

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

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

Начать курс

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

Блоги

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