Тесты перед деплоем: как я спроектировала интеграцию PHPUnit и Bruno в CI/CD-пайплайн
Привет! В этой статье я расскажу, как я организовала интеграцию автоматизированных тестов в Jenkins-пайплайн для oninvest-backend.
Задача была простой по концепции, но требовала аккуратной реализации: не деплоить код, пока PHPUnit и Bruno не подтвердят, что всё работает.
Она была сформулирована обобщенно - «внедрить авто-тесты перед деплоем», без готового рецепта. Всю механику я продумала с нуля: предложила схему с временной директорией и отдельными тестовыми контейнерами, чтобы основная директория проекта не трогалась до зелёных тестов. Сначала - на дев-стендах, с прицелом на production.
Коротко: теперь при каждой сборке код сначала попадает во временную директорию, поднимается тестовое окружение, запускаются PHPUnit и Bruno, и только после успешного прохождения тестов код «продвигается» в основную директорию и деплоится. Если тесты не проходят, деплой блокируется, но отчёты всё равно сохраняются в Jenkins.
1. Зачем это понадобилось
До появления тестов пайплайн был простым: сборка и деплой. Быстро, но рискованно. Особенно когда изменения затрагивали API, миграции или конфигурации. Мы хотели fail-fast: узнавать о поломке до выката, а не после него.
Что мы хотели:
- PHPUnit и Bruno запускаются автоматически перед деплоем.
- Основная директория projectfiles/ не изменяется, пока тесты не пройдут.
- Bruno запускается на хосте и обращается к тестовому контейнеру через порт 8081.
- PHPUnit запускается внутри контейнера.
- Отчёты JUnit всегда публикуются в Jenkins, даже если тесты завершаются с ошибкой.
- Есть возможность быстро пропустить тесты через RUN_TESTS, но по умолчанию они включены.
2. Архитектура пайплайна
Пайплайн состоит из 4 Shell-шагов:
Git push / Jenkins build
|
v
[1] Rsync code
|
|-- RUN_TESTS=true -> projectfiles-test/
|-- RUN_TESTS=false -> projectfiles/
v
[2] build-test-runner.sh
docker build + up + DB migrations
|
v
[3] run-tests.sh
PHPUnit + Bruno (fail-fast)
|
|-- success -> promote: down -v + mv projectfiles-test -> projectfiles/
|-- failure -> deployment blocked, promote not executed
v
[4] build-{env}-runner
deploy to dev/stage/prod
Ключевая идея: временная директория `projectfiles-test/. Пока тесты не пройдут, основная projectfiles/ остаётся нетронутой. Только после успешного запуска выполняется promote: тестовые контейнеры останавливаются, а код перемещается в projectfiles/.
3. Что уже было сделано со стороны разработки
Все новые файлы были созданы в репозитории oninvest-deploy-config.
Файл | Назначение |
Dockerfile.test | Тестовый Docker-образ. Наследуется от oninvest-img-php8.2, содержит PHPUnit |
docker-compose-test.yml | Поднимает oninvest-backend-test на порту 8081 и postgres-test на порту 5436 |
conf/.env.test | Тестовое окружение: APP_ENV=test, DB_DATABASE=oninvest_test |
build-test-runner.sh | Собирает и запускает тестовое окружение: проверяет базовый образ, выполняет сборку, удаляет старые контейнеры, запускает окружение, миграции и фикстуры |
scripts/run-tests.sh | Запускает PHPUnit и Bruno. Без сборки и миграций — только выполнение тестов |
Таким образом, я подготовил всё необходимое, чтобы DevOps оставалось только корректно интегрировать это в Jenkins job.
4. Что нужно было сделать на стороне Jenkins
4.1. Плагины Jenkins
В Manage Jenkins → Plugins, убедитесь, что установлен следующий плагин:
Плагин | Минимальная версия | Зачем |
JUnit | 1.60+ | Обрабатывает test-reports/*.xml, строит график динамики |
Без него отчёты не будут отображаться на странице сборки.
4.2. Bruno CLI на целевом сервере
Bruno (bru) запускается на хосте, а не в контейнере. Ему нужно отправлять HTTP-запросы на http://localhost:8081, где работает тестовый backend.
Установка:
```bash
npm install -g @usebruno/cli
bru --version
```4.3. Четыре Shell-шагa в Jenkins
В разделе Build должно быть 4 шага. Логика следующая:
Шаг | Что делает | Условие |
1 | Rsync кода | Если `RUN_TESTS=true — в projectfiles-test/, иначе напрямую в projectfiles/ |
2 | Сборка тестового окружения | build-test-runner.sh: build + up + миграции |
3 | Тесты + promote | run-tests.sh: PHPUnit + Bruno, затем down -v и mv projectfiles-test → projectfiles |
4 | Деплой | Как обычно, через build-{env}-runne |
Если RUN_TESTS=false, шаги 2 и 3 пропускаются, код напрямую синхронизируется в projectfiles/ и деплоится.
4.4. Параметр RUN_TESTS
В General → This project is parameterized, добавьте Boolean Parameter:
- Имя: RUN_TESTS
- Значение по умолчанию: ✅ включено
- Описание: запускать PHPUnit + Bruno перед деплоем
По умолчанию тесты включены. Если срочно нужно выпустить hotfix без тестов, этот параметр можно отключить. Но это осознанное исключение, а не стандартный режим.
4.5. Публикация отчётов
В Post-build Actions, добавьте:
- Publish JUnit test result report
- Test report XMLs: test-reports/*.xml
Отчёты сохраняются всегда, даже если тесты завершаются с ошибкой. Jenkins хранит последние 30 сборок и строит Test Result Trend график.
5. Чек-лист DevOps
- [ ] Установить Bruno CLI: npm install -g @usebruno/cli, проверить bru --version
- [ ] Проверить плагин JUnit для Jenkins версии 1.60+
- [ ] Обновить job: 4 Shell-шага
- [ ] Настроить параметр RUN_TESTS (Boolean Parameter, включён по умолчанию)
- [ ] Добавить Post-build Action: Publish JUnit test result report с путём test-reports/*.xml
- [ ] Запустить первую сборку и убедиться, что все шаги проходят
- [ ] Проверить, что график Test Result Trend появился на странице сборки
6. Результат
Главный результат: деплой стал безопаснее. Теперь невозможно случайно отправить код с неуспешными тестами: пайплайн останавливается, promote не выполняется, а основная директория остаётся нетронутой.
Что ещё важно:
- Изоляция. Тестовое окружение работает отдельно: свои контейнеры, своя БД, свои порты.
- Fail-fast. PHPUnit или Bruno завершается с ошибкой — деплой блокируется.
- Наблюдаемость (Observability). JUnit-отчёты и график динамики в Jenkins.
- Гибкость. RUN_TESTS позволяет пропустить тесты, но по умолчанию они включены.
- Разделение ответственности. Разработчик подготовил тестовый контур, DevOps интегрировал его в Jenkins.
В dev-окружениях схема уже работает. Следующий шаг — аккуратно включить её в production, добавить больше тестов и контролировать время выполнения, чтобы обратная связь оставалась быстрой.