Качество и эксплуатация: тестирование, профилирование, CI/CD и деплой
В предыдущих статьях курса мы построили основу: современный PHP (типы, ООП, исключения), рабочие инструменты (Composer, PSR, окружение), корректную работу с данными (SQL, транзакции, миграции, кеширование) и веб-слой (HTTP, middleware, безопасность). Эта статья переводит всё это в режим надёжной эксплуатации: как проверять код автоматически, как находить узкие места производительности, как собирать CI/CD и как деплоить без сюрпризов.
К концу статьи вы должны уверенно понимать:
что именно тестировать и на каких уровнях
как использовать PHPUnit, чтобы тесты были предсказуемыми и полезными
что такое профилирование и чем оно отличается от «оптимизации на глаз»
как устроен CI (Continuous Integration) и CD (Continuous Delivery/Deployment)
какие практики деплоя и проверок после деплоя считаются базовыми на уровне middleКачество как система, а не как «настроение команды»
Качество в разработке — это не только «код красивый». Это способность системы:
правильно работать (корректность)
безопасно меняться (поддерживаемость)
выдерживать нагрузку (производительность)
быть наблюдаемой в продакшене (эксплуатация)На уровне middle важно мыслить качеством как набором автоматических проверок и процессов, а не как разовыми усилиями.
Типичная цель процесса качества:
локально вы быстро ловите ошибки типов и контрактов (то, что мы закрепляли в статьях про типы и PSR)
в CI вы гарантируете, что PR не ломает проект
в продакшене вы быстро диагностируете проблемы (логирование, метрики, ошибки)Тестирование: что это такое и какие уровни нужны
Тест — это код, который проверяет, что другая часть кода ведёт себя ожидаемо.
Важно не пытаться «покрыть тестами всё подряд», а выбрать уровни тестов, которые дают максимальную пользу.
Уровни тестирования
| Уровень | Что проверяем | Скорость | Что чаще всего ломается | Где особенно полезно в нашем курсе |
|---|---|---|---|---|
| Unit-тесты (модульные) | один класс/функция | быстро | бизнес-правила, ветвления | доменная логика, сервисы без I/O |
| Integration-тесты (интеграционные) | взаимодействие с БД/очередью/файлами | медленнее | SQL, транзакции, конфигурация | репозитории на PDO/ORM, миграции |
| API/HTTP-тесты | поведение API как системы | средне | маршруты, middleware, статусы | REST-эндпоинты, авторизация |
Практика middle-уровня:
unit-тестами покрывают правила и преобразования данных
интеграционными тестами закрывают самые рискованные места I/O (БД, транзакции)
API-тесты фиксируют контракт HTTP (коды, формат ошибок, доступ)!Пирамида показывает, почему быстрых unit-тестов обычно больше, чем медленных end-to-end
Тестовый дубль: простыми словами
В тестах часто заменяют реальную зависимость на упрощённую.
Mock — объект-заглушка, который ещё и проверяет, как его вызывали
Stub — объект, который возвращает заранее заданные данныеЗачем это нужно:
чтобы не ходить в реальную базу/сеть
чтобы тест был быстрым и детерминированным (то есть всегда давал одинаковый результат)PHPUnit: базовый инструмент тестирования в PHP
Самый распространённый тестовый фреймворк для PHP — PHPUnit.
PHPUnitМинимальная структура теста
Пример unit-теста для доменной логики (без базы и сети):
Практическое правило:
ожидайте конкретное исключение, а не общий ExceptionИнтеграционные тесты с базой
Интеграционный тест — это проверка, что ваш код реально работает с инфраструктурой (например, с PostgreSQL).
Ключевая идея:
unit-тесты не должны требовать БД
интеграционные тесты должны поднимать предсказуемую БД (часто через Docker) и прогонять миграцииСвязь с предыдущими статьями:
из статьи про данные вы уже знаете транзакции и миграции
из статьи про окружение вы знаете, как делать воспроизводимостьМинимальные правила для интеграционных тестов:
каждый тест должен начинаться с чистого состояния (часто через транзакцию с откатом или пересоздание схемы)
тесты не должны зависеть от порядка запускаПокрытие кода: что измеряет и чего не обещает
Покрытие кода показывает, какие строки/ветки выполнялись во время тестов.
Это полезно, но не является доказательством качества.
Практические выводы:
100% coverage не гарантирует отсутствие багов
низкое coverage часто означает, что в проекте легко сломать критичные пути незаметноИнструменты покрытия в PHP обычно используют Xdebug:
XdebugСтатический анализ и стиль: ловим проблемы до запуска
В предыдущих статьях мы опирались на типы и PSR. Статический анализ делает это практикой: проверяет код без выполнения.
Популярные инструменты:
PHPStan
PsalmЧто они ловят чаще всего:
несоответствие типов аргументов и возвращаемых значений
возможные null там, где вы не ожидаете null
недостижимый код, неверные PHPDoc-обобщения для массивовДля стиля кода часто используют:
PHP-CS-Fixer
PHP_CodeSnifferЕдиная точка запуска через Composer scripts
Чтобы команда и CI запускали проверки одинаково, фиксируйте команды в composer.json.
Польза:
разработчик запускает composer ci локально
CI запускает ровно то же самоеПрофилирование: как ускорять код без самообмана
Профилирование — это измерение того, где программа тратит время и память.
Важно отличать:
логирование (что происходило)
метрики (сколько раз и сколько заняло)
профилирование (какие функции и вызовы съели время)Когда профилирование действительно нужно
Профилирование включают, когда есть симптом:
медленный эндпоинт
рост времени ответа при увеличении нагрузки
подозрение на N+1 запросы (связь со статьёй про ORM)Практический подход:
Сначала зафиксируйте проблему числом: время ответа, количество запросов, потребление памяти.
Потом профилируйте и находите узкие места.
Оптимизируйте и снова измеряйте.Инструменты профилирования в PHP
Xdebug умеет профилирование и трассировку: Xdebug
Blackfire — коммерческий профайлер и мониторинг производительности: Blackfire
Tideways — коммерческий APM и профилирование: TidewaysЕсли вы используете Symfony-компоненты, удобно измерять небольшие участки кода через Stopwatch:
Symfony StopwatchЧастые причины медленной работы, которые видит middle
| Причина | Как проявляется | Как диагностировать | Типичный фикс |
|---|---|---|---|
| N+1 запросы | много запросов на один экран | лог запросов + профайлер | eager loading, переписать выборку |
| Нет индекса | медленные WHERE/JOIN | EXPLAIN в БД | добавить индекс |
| Слишком много сериализации JSON | CPU на кодирование/декодирование | профайлер | уменьшить объём ответа, кешировать |
| Внешний HTTP вызов внутри критичного пути | скачки по времени | тайминги + логирование | таймауты, ретраи, очередь |
| Неправильный кеш | промахи и «шторм» | метрики cache hit ratio | TTL, инвалидация, защита от stampede |
!Схема показывает, где обычно измеряют производительность и где чаще всего возникают узкие места
CI: Continuous Integration на практике
CI (Continuous Integration) — это процесс, при котором каждый коммит/PR автоматически проходит проверки.
Зачем это нужно:
код не попадает в main «вслепую»
качество проверяется одинаково у всех
проблемы ловятся раньше и дешевлеПопулярные системы:
GitHub Actions
GitLab CI/CDТиповой пайплайн CI для PHP-проекта
Обычно этапы такие:
установка зависимостей через composer install
линт и формат (например, PHP-CS-Fixer)
статический анализ (PHPStan/Psalm)
тесты (PHPUnit)
сборка артефакта (если вы деплоите собранный пакет)!Диаграмма помогает увидеть, какие проверки выполняются до мержа и релиза
Пример GitHub Actions workflow
Что важно понимать:
версия PHP фиксируется (связь со статьёй про окружение)
команды запускаются через Composer scripts (единый источник правды)Практика: разделяйте быстрые и медленные проверки
Чтобы CI не стал «вечно красным и медленным», часто разделяют:
быстрые проверки на каждый PR
более тяжёлые проверки по расписанию или перед релизом (например, полный набор интеграционных тестов)CD и деплой: как выпускать изменения безопасно
Термины простыми словами:
Continuous Delivery — каждый коммит потенциально готов к релизу, деплой делается кнопкой
Continuous Deployment — деплой происходит автоматически после прохождения пайплайнаВ обоих случаях цель одна: маленькие предсказуемые релизы вместо редких опасных.
Что такое «артефакт» и зачем он нужен
Артефакт — это собранный результат (например, архив приложения или Docker-образ), который вы деплоите.
Зачем:
вы деплоите ровно то, что прошло проверки
вы уменьшаете шанс «в CI одно, на сервере другое»Конфигурация и секреты
Базовая практика эксплуатации:
конфигурация (DSN, ключи, режимы) задаётся переменными окружения
секреты не хранятся в репозитории
отличия окружений (dev/stage/prod) не требуют изменения кодаЭто напрямую продолжает идеи из статьи про окружение.
Миграции на деплое: самая частая зона риска
Миграции из статьи про данные становятся частью деплоя. Ошибки здесь приводят к простоям.
Практические правила:
миграции должны быть обратимыми там, где это реально
миграции должны быть совместимы со старым и новым кодом на время перехода
тяжёлые миграции (долгие) лучше делать в несколько шаговПример безопасной стратегии изменения схемы:
Добавить новую колонку, не удаляя старую.
Обновить код так, чтобы он писал в обе колонки или умел читать из обеих.
Перенести данные фоном.
Удалить старую колонку отдельной миграцией позже.Стратегии деплоя, которые стоит знать middle
| Стратегия | Идея | Плюсы | Минусы |
|---|---|---|---|
| Rolling update | обновляем серверы по очереди | меньше простоя | нужно поддерживать совместимость версий |
| Blue/Green | два окружения, переключение трафика | быстрый откат | дороже по ресурсам |
| Canary | малой доле пользователей новая версия | снижает риск | сложнее маршрутизация и наблюдаемость |
Проверки после деплоя
Чтобы «деплой прошёл» означало «система работает», нужны проверки.
Минимальный набор:
healthcheck-эндпоинт (например, GET /health), который проверяет базовые зависимости
проверка логов на всплеск ошибок
метрики времени ответа и ошибокИнструменты наблюдаемости зависят от стека, но источники для понимания:
ошибки и алерты часто заводят через Sentry
метрики часто собирают через PrometheusОткат (rollback): что это и почему нужно готовить заранее
Rollback — возврат на предыдущую версию, если новая ведёт себя плохо.
Middle-практика:
откат должен быть быстрым и технически простым
если миграция ломает обратную совместимость, откат усложняется, это нужно учитывать заранееПрактический чеклист после статьи
вы можете объяснить разницу между unit- и интеграционными тестами и выбрать уровень проверки под задачу
вы умеете запускать тесты и статический анализ одинаково локально и в CI через Composer scripts
вы понимаете, что профилирование начинается с измерений, а не с «перепишем на более быстрый код»
вы можете набросать минимальный CI пайплайн для PHP-проекта
вы понимаете риски миграций на деплое и базовые стратегии безопасного релизаДальше в курсе эту тему удобно закреплять мини-проектом: собрать небольшой сервис с API, репозиториями, миграциями и кешем, подключить CI, добавить тесты и провести пробный деплой на staging с измерениями и логами.