📘 Что это
Это workflow для Test-Driven Development (TDD) в Laravel, ориентированный на достижение покрытия кода выше 80% (юнит + функциональные тесты). Навык предписывает использовать PHPUnit или Pest (с приоритетом Pest для новых тестов), фабрики, трейты управления БД и моки, чтобы писать надёжные, изолированные тесты для контроллеров, моделей, политик, очередей, уведомлений и Inertia-страниц. Он подходит как для разработки новых фич, так и для рефакторинга / исправления багов.
⚙️ Как работает
🔴🟢🔄 Red-Green-Refactor
Весь процесс строится по классическому циклу TDD:
- Red — написать тест, который падает (требуемая функциональность ещё не реализована).
- Green — написать минимальный код, чтобы тест прошёл.
- Refactor — улучшить код, не ломая тесты.
📐 Уровни тестов
- Unit — проверка чистых PHP-классов, Value Objects, сервисов без БД.
- Functional — проверка HTTP-эндпоинтов, аутентификации, валидации, политик доступа.
- Integration — проверка связки БД + очередь + внешние сервисы.
Выбор уровня зависит от того, что именно вы тестируете: бизнес-логику — Unit, ответы API — Functional, сложное взаимодействие — Integration.
🗄 Стратегии работы с БД
Три основных трейта для управления миграциями и транзакциями:
RefreshDatabase (рекомендуется) — запускает миграцию один раз за сессию, а каждый тест оборачивает в транзакцию (если БД поддерживает). Для :memory: SQLite — перезапускает миграцию перед каждым тестом.
DatabaseTransactions — если схема уже накатана, нужен только откат изменений.
DatabaseMigrations — полная миграция перед каждым тестом (дорого, только при крайней необходимости).
🧪 Выбор фреймворка
- Pest — предпочтительный выбор для новых тестов (синтаксис лаконичнее, больше хелперов).
- PHPUnit — только если проект уже стандартизирован под него или нужны специфические фичи PHPUnit.
📝 Примеры кода
Pest (новый тест):
test('owner can create project', function () {
$user = User::factory()->create();
$response = actingAs($user)->postJson('/api/projects', [
'name' => 'New Project',
]);
$response->assertCreated();
assertDatabaseHas('projects', ['name' => 'New Project']);
});
PHPUnit (для проектов с PHPUnit):
final class ProjectControllerTest extends TestCase
{
use RefreshDatabase;
public function test_owner_can_create_project(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->postJson('/api/projects', [
'name' => 'New Project',
]);
$response->assertCreated();
$this->assertDatabaseHas('projects', ['name' => 'New Project']);
}
}
🧑🔬 Фабрики и состояния
- Используйте фабрики (
User::factory()) для генерации данных.
- Определяйте состояния для граничных случаев:
['role' => 'admin'], trial, archived.
🔗 Моки и фасады
Для изоляции побочных эффектов используйте встроенные фасады Laravel:
Bus::fake() — для Job.
Queue::fake() — для очередей.
Mail::fake() / Notification::fake() — для писем и уведомлений.
Event::fake() — для событий.
Http::fake() — для внешних API.
Queue::fake();
dispatch(new SendOrderConfirmation($order->id));
Queue::assertPushed(SendOrderConfirmation::class);
🔐 Аутентификация (Sanctum)
Sanctum::actingAs($user);
$response = $this->getJson('/api/projects');
$response->assertOk();
🧪 Проверка авторизации (Gates)
$this->assertTrue(Gate::forUser($user)->allows('update', $project));
$this->assertFalse(Gate::forUser($otherUser)->allows('update', $project));
🖥 Inertia-тесты
Если проект использует Inertia.js, применяйте assertInertia для проверки компонента и его пропсов:
$response->assertInertia(fn (AssertableInertia $page) =>
$page->component('Dashboard')
->where('user.id', $user->id)
->has('projects')
);
✅ assertDatabaseHas vs ручные запросы
Всегда предпочитайте assertDatabaseHas прямому выполнению запросов к БД в теле теста — это делает тесты чище и нагляднее.
📊 Покрытие кода
- Цель: ≥80% (юнит + функциональные тесты).
- В CI используйте pcov или XDEBUG_MODE=coverage.
- Запуск:
php artisan test / vendor/bin/phpunit / vendor/bin/pest.
⚙ Конфигурация
- В
phpunit.xml установите DB_CONNECTION=sqlite и DB_DATABASE=:memory: для быстрых тестов.
- Используйте отдельное окружение для тестов (не трогайте dev/prod БД).
🎯 Когда использовать
- Разработка нового эндпоинта или API — незамедлительно пишите функциональный тест.
- Добавление бизнес-логики (сервис, Value Object) — Unit-тест.
- Исправление бага — сначала напишите тест, воспроизводящий баг, затем чините.
- Рефакторинг — перед изменениями убедитесь, что покрытие достаточное.
- Тестирование Eloquent-моделей, Policies, Job, Notifications, Events.
- Inertia-приложения — проверяйте не только HTTP-ответ, но и компоненты с пропсами.
⚠ Важно знать
- Не дублируйте тесты — если логика меняется, достаточно одного теста на сценарий.
- Изолируйте внешние сервисы —
Http::fake() и фасады обязательны, иначе тесты станут медленными и недетерминированными.
- RefreshDatabase — стандарт — используйте его для всех тестов, которые работают с БД, если нет веских причин на
DatabaseTransactions.
- Предпочитайте Pest — он читаемее, поддерживает более выразительные
->assert... цепочки, но в legacy-проектах с PHPUnit оставайтесь на нём.
- Не гоняйтесь за 100% — 80% достаточно, чтобы отлавливать большинство регрессий без излишней работы.
Комментарии
Комментариев пока нет. Будьте первым.