Тема
Testing
КРИТИЧНО: Тестовая БД
НИКОГДА не запускать тесты с RefreshDatabase на рабочей БД.
phpunit.xmlОБЯЗАН содержать<env name="DB_DATABASE" value="{project}_test"/>- Тестовая БД создаётся вручную:
CREATE DATABASE catalog_test;+CREATE EXTENSION IF NOT EXISTS postgis; - Перед первым запуском тестов — убедиться что тестовая БД существует и extensions установлены
RefreshDatabaseоткатывает ВСЕ миграции и пересоздаёт схему — на рабочей БД это = потеря всех данных
Стратегия
| Тип | Что тестирует | Приоритет |
|---|---|---|
| Feature tests | HTTP responses, роуты, формы, API | Высший — делать всегда |
| Unit tests | Сервисы со сложной логикой (расчёты, формулы) | Средний — для критичной логики |
| Visual (Playwright) | Рендеринг ключевых страниц | Низкий — для финальной проверки |
Структура
tests/
├── Feature/
│ ├── PageTest.php — статические страницы → 200
│ ├── {Section}Test.php — каждая группа роутов
│ ├── RedirectTest.php — 301 редиректы
│ ├── Api{Entity}Test.php — API endpoints
│ └── SitemapTest.php — sitemap содержит все URL
├── Unit/
│ └── {Service}Test.php — сервисы с логикой
└── TestCase.phpFeature Tests
Что тестировать обязательно
- Каждый публичный URL → 200
- 301 редиректы → правильный destination
- POST формы → валидация (422 на невалидные данные) + сохранение (201/302)
- API rate limiting → 429 после лимита
- 404 на несуществующие URL
- Наличие ключевых элементов на странице (h1, breadcrumbs, title)
Правила
RefreshDatabasetrait — чистая БД на каждый тест$this->seed()в setUp — если тесты зависят от данных из seeders- Один assert на одно поведение — не мешать проверки в одном тесте
- Именование:
test_{что_тестируем}_{ожидаемый_результат}— напримерtest_contact_form_validates_phone
Checklist для нового роута
- GET → 200
- GET несуществующий slug → 404
- Наличие title, h1 в response
- Если форма — POST с валидными данными → success
- Если форма — POST с невалидными данными → 422/redirect с errors
- Если 301 — проверить destination
Unit Tests
Когда писать
- Сервис с расчётами (калькулятор, формулы, скидки)
- Сервис с ветвлением (разные стратегии по условию)
- Utility/helper с edge cases
Когда НЕ писать
- Простой CRUD без логики — покрывается feature тестом
- Eloquent relations — это ответственность фреймворка
- Прямые вызовы внешних API — мокать, не тестировать реальный API
Правила
- Тестировать поведение, не реализацию — если рефакторинг меняет внутренности, тест не должен ломаться
- Мокать внешние зависимости (API, email, telegram) —
Mail::fake(),Queue::fake() - НЕ мокать БД — использовать
RefreshDatabaseс реальными запросами
Playwright (Visual)
Когда использовать
- Финальная проверка ключевых страниц после крупных изменений
- Проверка JS-функциональности (формы, калькуляторы, модальные окна)
- Сравнение с ожидаемым результатом после миграции
Процесс
browser_navigate→ URLbrowser_snapshot→ проверить ключевые элементыbrowser_console_messages→ убедиться что нет JS ошибокbrowser_close— всегда закрывать после проверки
Ключевые страницы для проверки
- Главная
- Категория услуг (типовая)
- Детальная страница (типовая)
- Контакты / форма
- Калькулятор (если есть JS-логика)
Sitemap Validation
- Получить sitemap → проверить что каждая сущность из БД присутствует
- Все URL из sitemap → 200 (не 404, не 500)
- Сравнение с предыдущей версией sitemap — нет потерянных URL
Запуск тестов
Всегда через Docker:
- Все тесты:
docker exec {container} php artisan test - По классу:
docker exec {container} php artisan test --filter=PageTest - Параллельно:
docker exec {container} php artisan test --parallel
Anti-patterns
| Паттерн | Проблема | Решение |
|---|---|---|
| Нет тестов на публичные URL | Сломанная страница уезжает в prod | Feature test на каждый роут |
| Mock БД вместо реальных запросов | Тесты проходят, но миграция сломана | RefreshDatabase |
| Тест зависит от порядка | Flaky tests | Каждый тест — изолирован |
| Один тест на 10 assertions | Непонятно что сломалось | Один assert = одно поведение |
| Тест проверяет реализацию | Ломается при рефакторинге | Тестировать поведение |
| Нет теста на 301 redirects | Redirect потерян → SEO пострадал | RedirectTest |
dd() / dump() в тесте | Остаётся в коде | $this->assert*() |