Автоматизация тестирования 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 сценариев.