Что такое Pest и зачем он нужен?
Pest — это современный, элегантный фреймворк для тестирования на языке PHP, созданный Нуно Мадуро (Nuno Maduro) в 2021 году. В отличие от традиционных решений, таких как PHPUnit, Pest предлагает более лаконичный и выразительный синтаксис, вдохновлённый Jest (JavaScript) и RSpec (Ruby). Если PHPUnit — это "швейцарский нож" для тестирования, то Pest — это "скальпель", который позволяет писать тесты быстрее, чище и с меньшим количеством шаблонного кода.
Основная цель Pest — сделать процесс написания тестов максимально приятным и интуитивно понятным. Вместо классов и методов с длинными именами, вы используете замыкания (closures) и цепочки вызовов (fluent interface). Это особенно полезно для проектов, где важна скорость разработки и читаемость кода, например, в стартапах, API-сервисах или при работе в команде, где тесты пишут не только QA-инженеры, но и разработчики.
Ключевые преимущества Pest перед PHPUnit:
- Лаконичность: Меньше кода для написания одного и того же теста.
- Читаемость: Тесты выглядят как естественный язык (например,
expect($result)->toBeTrue()). - Встроенные функции:
beforeEach(),afterEach(),todo()и другие. - Архитектура: Не требует обязательного наследования от
TestCase. - Экосистема: Плагины для Laravel, Livewire, Mockery и других популярных инструментов.
Установка Pest
Pest устанавливается через Composer — стандартный менеджер пакетов для PHP. Убедитесь, что у вас установлен PHP версии 8.0 или выше.
Создайте новый проект или перейдите в корневую директорию существующего проекта:
mkdir my-pest-project
cd my-pest-project
composer init --yes
Установите Pest как зависимость для разработки (dev):
composer require pestphp/pest --dev
После установки инициализируйте Pest в вашем проекте:
./vendor/bin/pest --init
Эта команда создаст базовую структуру:
tests/— директория для тестов.tests/Pest.php— файл с глобальными настройками (например,uses()).phpunit.xml— конфигурационный файл (Pest работает поверх PHPUnit).
Теперь вы можете запустить тесты:
./vendor/bin/pest
Если вы используете Laravel, установите дополнительный пакет для лучшей интеграции:
composer require pestphp/pest-plugin-laravel --dev
Быстрый старт с примером кода
Создайте файл tests/ExampleTest.php:
<?php
test('basic assertion', function () {
$result = 1 + 1;
expect($result)->toBe(2);
});
Запустите тесты:
./vendor/bin/pest
Вы увидите зелёный вывод с сообщением PASS Tests\ExampleTest. Обратите внимание: мы не использовали классы, методы с аннотациями или наследование. Просто функция test() и замыкание. Это и есть "современное тестирование".
Альтернативный синтаксис с использованием ключевого слова it (более читаемый для описания поведения):
it('performs basic addition', function () {
$sum = 2 + 3;
expect($sum)->toEqual(5);
});
Основные методы и функции Pest
Ниже приведены 10 ключевых методов и функций Pest с подробным описанием и примерами.
1. test() — определение теста
Что делает: Создаёт новый тест. Это основная функция для определения тестового сценария.
Параметры:
string $description— описание теста (выводится в консоли).callable $closure— замыкание, содержащее логику теста.
Пример:
test('string length is correct', function () {
$string = 'Hello, Pest!';
expect(strlen($string))->toBe(12);
});
2. it() — альтернатива test()
Что делает: Работает аналогично test(), но добавляет слово "it" перед описанием, что делает тесты более читаемыми (например, "it returns true when condition is met").
Параметры: Те же, что и у test().
Пример:
it('returns true for valid email', function () {
$validator = new EmailValidator();
expect($validator->validate('user@example.com'))->toBeTrue();
});
3. expect() — создание ожидания (assertion)
Что делает: Создаёт объект ожидания, к которому можно применить цепочку методов для проверки значений. Это замена традиционным assertXxx() методам PHPUnit.
Параметры:
mixed $value— значение, которое нужно проверить.
Пример:
test('expect example', function () {
$array = [1, 2, 3];
expect($array)->toHaveCount(3);
expect($array[0])->toBeInt();
});
4. toBe() — строгое сравнение (===)
Что делает: Проверяет, что значение идентично ожидаемому (строгое сравнение с учётом типа).
Параметры:
mixed $expected— ожидаемое значение.
Пример:
test('strict comparison', function () {
$value = 42;
expect($value)->toBe(42); // true
expect($value)->toBe('42'); // false (разные типы)
});
5. toEqual() — нестрогое сравнение (==)
Что делает: Проверяет, что значение равно ожидаемому с приведением типов.
Параметры:
mixed $expected— ожидаемое значение.
Пример:
test('loose comparison', function () {
expect(42)->toEqual('42'); // true, так как 42 == '42'
});
6. toThrow() — проверка исключений
Что делает: Проверяет, что замыкание выбрасывает исключение. Можно указать класс исключения и/или сообщение.
Параметры:
string $exceptionClass(опционально) — класс ожидаемого исключения.string $exceptionMessage(опционально) — текст сообщения исключения.
Пример:
test('exception throwing', function () {
expect(function () {
throw new \InvalidArgumentException('Invalid data');
})->toThrow(\InvalidArgumentException::class, 'Invalid data');
});
7. beforeEach() — подготовка перед каждым тестом
Что делает: Выполняет заданное замыкание перед каждым тестом в файле. Идеально для инициализации общих объектов (например, подключение к БД, создание моков).
Параметры:
callable $closure— замыкание с подготовительной логикой.
Пример:
beforeEach(function () {
$this->user = new User(['name' => 'John']);
});
test('user name is John', function () {
expect($this->user->name)->toBe('John');
});
8. afterEach() — очистка после каждого теста
Что делает: Выполняет замыкание после каждого теста. Полезно для сброса состояния (удаление временных файлов, закрытие соединений).
Параметры:
callable $closure— замыкание с логикой очистки.
Пример:
afterEach(function () {
Mockery::close(); // очистка моков Mockery
});
9. todo() — пометка теста как "запланированного"
Что делает: Создаёт тест, который будет отмечен как "TODO" в отчёте. Не содержит логики и не выполняется. Помогает планировать тесты.
Параметры:
string $description— описание того, что нужно протестировать.
Пример:
todo('test user registration with invalid email');
todo('test password reset flow');
Что делает: Пропускает выполнение теста, если условие истинно. Полезно для тестов, которые зависят от окружения (например, версия PHP, наличие расширения).
Параметры:
bool|callable $condition— условие пропуска.string $message(опционально) — причина пропуска.
Пример:
test('feature requires PHP 8.1', function () {
// тест, использующий фичи PHP 8.1
})->skip(PHP_VERSION_ID < 80100, 'Требуется PHP 8.1 или выше');
Реальный практический пример использования
Предположим, у нас есть простой сервис для управления корзиной покупок (CartService). Мы хотим протестировать добавление товаров, подсчёт суммы и очистку корзины.
Структура проекта:
src/
CartService.php
tests/
CartServiceTest.php
Pest.php
Класс CartService:
<?php
class CartService
{
private array $items = [];
public function addItem(string $name, float $price, int $quantity = 1): void
{
$this->items[] = ['name' => $name, 'price' => $price, 'quantity' => $quantity];
}
public function getTotal(): float
{
return array_sum(array_map(fn($item) => $item['price'] * $item['quantity'], $this->items));
}
public function clear(): void
{
$this->items = [];
}
public function count(): int
{
return count($this->items);
}
}
Тест CartServiceTest.php:
<?php
use function Pest\Faker\fake; // если установлен плагин faker
beforeEach(function () {
$this->cart = new CartService();
});
test('can add items to cart', function () {
$this->cart->addItem('Laptop', 1500.00, 1);
$this->cart->addItem('Mouse', 25.50, 2);
expect($this->cart->count())->toBe(2);
});
test('calculates total correctly', function () {
$this->cart->addItem('Laptop', 1500.00, 1);
$this->cart->addItem('Mouse', 25.50, 2);
$expectedTotal = 1500.00 + (25