Автоматизация тестирования PHP-приложений с использованием Pest: от юнит-тестов до E2E

Современная разработка требует надежных механизмов верификации кода. Pest — это элегантный фреймворк для тестирования PHP, который сочетает простоту синтаксиса с мощными возможностями. В отличие от PHPUnit, Pest предлагает более выразительный DSL (Domain Specific Language), что ускоряет написание и поддержку тестов.

Установка и базовая конфигурация Pest

Начнем с установки фреймворка через Composer. Pest может работать поверх PHPUnit, сохраняя совместимость с существующими тестами.

composer require pestphp/pest --dev --with-all-dependencies
./vendor/bin/pest --init

После инициализации создается файл phpunit.xml (или pest.xml) с базовой конфигурацией. Для кастомизации можно создать файл Pest.php в корне проекта:

<?php
use PestTestSuite;
TestSuite::configure() ->in('tests') ->skip('integration') ->group('fast');

Структура тестов и базовые примеры

Pest организует тесты в директории tests. Рассмотрим пример класса Calculator и его тестирование.

// 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; }
}

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

// tests/Unit/CalculatorTest.php
use AppCalculator;
test('addition works', function () { $calc = new Calculator(); expect($calc->add(2, 3))->toBe(5);
});
it('throws exception on division by zero', function () { $calc = new Calculator(); $calc->divide(5, 0);
})->throws(InvalidArgumentException::class);

Использование хуков и дата-провайдеров

Pest предоставляет хуки beforeEach, afterEach для подготовки окружения. Дата-провайдеры упрощают тестирование с разными наборами данных.

describe('Calculator operations', function () { beforeEach(function () { $this->calculator = new Calculator(); }); test('addition with various inputs', function ($a, $b, $expected) { expect($this->calculator->add($a, $b))->toBe($expected); })->with([ [1, 1, 2], [-1, 1, 0], [100, 200, 300] ]);
});

Интеграционное тестирование с базой данных

Для тестирования взаимодействия с БД используем транзакции или тестовую базу. Пример с Laravel, но подход применим к любому фреймворку.

// tests/Integration/UserRepositoryTest.php
use AppRepositoriesUserRepository;
use AppModelsUser;
use IlluminateFoundationTestingRefreshDatabase;
uses(RefreshDatabase::class);
it('creates user correctly', function () { $repository = new UserRepository(); $user = $repository->create([ 'name' => 'John Doe', 'email' => 'john@example.com' ]); expect($user)->toBeInstanceOf(User::class) ->and($user->email)->toBe('john@example.com') ->and(User::count())->toBe(1);
});

End-to-End тестирование API

Pest может тестировать API endpoints через HTTP-клиенты. Рассмотрим пример с использованием библиотеки symfony/http-client.

// tests/Feature/ApiTest.php
use SymfonyComponentHttpClientHttpClient;
test('API returns correct response', function () { $client = HttpClient::create(); $response = $client->request('GET', 'https://api.example.com/users/1'); expect($response->getStatusCode())->toBe(200) ->and($response->getHeaders()['content-type'][0]) ->toContain('application/json') ->and($response->toArray()) ->toHaveKeys(['id', 'name', 'email']);
});

Мокирование внешних сервисов

При тестировании API часто нужно мокировать внешние вызовы. Pest интегрируется с Mockery и другими библиотеками.

// tests/Unit/WeatherServiceTest.php
use AppServicesWeatherService;
use AppContractsHttpClientInterface;
it('fetches weather data', function () { $mockClient = Mockery::mock(HttpClientInterface::class); $mockClient->shouldReceive('get') ->once() ->with('https://api.weather.com/data') ->andReturn(['temperature' => 22, 'conditions' => 'sunny']); $service = new WeatherService($mockClient); $result = $service->getCurrentWeather(); expect($result['temperature'])->toBe(22);
});

Параллельное выполнение и CI/CD интеграция

Pest поддерживает параллельное выполнение тестов через расширение pest-plugin-parallel. Это критично для больших проектов.

composer require pestphp/pest-plugin-parallel --dev
./vendor/bin/pest --parallel

Для интеграции в CI/CD пайплайн создаем конфигурационный файл .github/workflows/tests.yml для GitHub Actions:

name: Tests
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: '8.2' - run: composer install --no-interaction - run: ./vendor/bin/pest --parallel --coverage-clover=coverage.xml - uses: codecov/codecov-action@v3 with: file: coverage.xml

Кастомизация и расширение возможностей

Pest позволяет создавать кастомные матчеры (matchers) для улучшения читаемости тестов. Пример матчера для проверки JSON структуры:

// tests/Pest.php
use PestMatchersMatcher;
Matcher::extend('toHaveJsonStructure', function ($value, $structure) { foreach ($structure as $key => $type) { if (!array_key_exists($key, $value)) { return false; } if (gettype($value[$key]) !== $type) { return false; } } return true;
});
// Использование в тесте
test('response has correct structure', function () { $data = ['id' => 1, 'name' => 'Test']; expect($data)->toHaveJsonStructure(['id' => 'integer', 'name' => 'string']);
});

Внедрение Pest в процесс разработки повышает надежность кодовой базы, ускоряет рефакторинг и улучшает архитектуру приложения через тестируемый дизайн. Начинайте с юнит-тестов критических компонентов, постепенно расширяя покрытие до интеграционных и E2E сценариев.

Автор: Разработчик