Тесты перед деплоем: как я спроектировала интеграцию PHPUnit и Bruno в CI/CD-пайплайн

5 мин чтения 4 просм. 4 просм. сентябрь 26, 2026
Тесты перед деплоем: как я спроектировала интеграцию 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, добавить больше тестов и контролировать время выполнения, чтобы обратная связь оставалась быстрой.

Поделиться: