Тесты красные, job зелёная: JUnit-отчёт и код завершения тестовой команды
В merge request видны два упавших теста, однако pipeline зелёный и разрешает релиз. Это не обязательно ошибка разбора XML. JUnit-отчёт описывает результаты для интерфейса, а статус job определяется выполнением script и политикой allow_failure. Корректный отчёт может соседствовать с ошибочно возвращённым нулевым кодом.
Чаще всего код тестовой команды теряется в shell: после неё выполняется успешный echo, ошибка подавляется через || true или вывод передаётся в tee без обработки статуса первого процесса. Исправление начинается с реальной команды запуска, а не с переименования junit.xml.
Самостоятельный пример с одним падающим тестом
Создайте tests/test_math.py и .gitlab-ci.yml. Pytest записывает XML и возвращает ненулевой код при проваленном assert. В YAML намеренно нет tee и маскирующих условий.
def test_sum():
assert 2 + 2 == 4
def test_wrong_expectation():
assert 2 + 2 == 5
tests:
image: python:3.12-alpine
script:
- python -m pip install pytest==8.3.5
- python -m pytest tests --junitxml=report.xml
artifacts:
when: always
reports:
junit: report.xml
paths:
- report.xml
Ожидается одна успешная и одна неуспешная проверка, failed у job и доступный отчёт. После замены 5 на 4 оба теста должны пройти, а статус стать успешным. Версия pytest зафиксирована для повторяемости примера; рабочий проект должен устанавливать согласованный набор из своего lock-файла.
JUnit-отчёт сам по себе не меняет статус job. Reports даёт GitLab структуру результатов, paths оставляет файл доступным для скачивания, when: always помогает сохранить диагностический файл после ошибки script. Но отсутствующий XML из-за сбоя до старта pytest не появится автоматически.
Найдём место потери статуса
Откройте итоговый script после include и extends. Найдите || true, set +e, блоки с несколькими командами и последнюю команду shell-скрипта. Если wrapper выполняет pytest, затем успешно печатает сообщение и завершается его статусом, CI получает успех. При необходимости сохраните результат сразу после тестов и завершите wrapper именно этим кодом.
set +e
python -m pytest tests --junitxml=report.xml
test_status=$?
set -e
printf 'pytest exit code: %s\n' "$test_status"
exit "$test_status"
Этот вариант нужен, когда после тестов выполняется дополнительная диагностика. Без такой необходимости проще оставить прямой вызов pytest из первого примера. Переменную надо присваивать немедленно: любая промежуточная команда изменит значение $?.
Почему tee требует отдельного решения
В POSIX shell статус pipeline обычно равен статусу последней команды. У pytest | tee tests.log это статус tee, который может успешно записать вывод упавших тестов. Bash с pipefail меняет правило, но для него Runner должен действительно запускать Bash. В Alpine по умолчанию нельзя предполагать Bash только потому, что пример найден для него.
Если нужна переносимая запись журнала, можно сначала направить вывод тестов в файл, сохранить код, затем вывести файл и завершиться сохранённым кодом. При этом исчезнет живой поток вывода, зато причина статуса станет явной. Выбирайте способ под фактический shell и требования к наблюдению.
Проверьте политику ошибки поверх команды
Даже правильный ненулевой код может не блокировать pipeline при allow_failure: true или подходящем allow_failure:exit_codes. Посмотрите эти поля в самой job и в совпавшем rule. Для обязательных тестов задайте поведение, при котором ошибка действительно запрещает дальнейшие этапы.
Не путайте «в отчёте ноль тестов» с успешной проверкой. Неверный путь, фильтр выбора или ошибка загрузки могут означать, что нужные тесты вообще не выполнялись. Pytest имеет разные коды для проваленных тестов, ошибок запуска и отсутствия собранных тестов; сохраняйте исходный код, пока процесс явно не определил допустимые исключения.
Проверяйте исправление намеренно падающим тестом в отдельной ветке. Должны совпасть результат теста, код процесса, статус job и поведение следующей стадии. Только такой отрицательный сценарий подтверждает, что зелёный pipeline действительно зависит от тестов, а не от последней успешной служебной команды.
Ещё по теме
- Cache и artifacts в GitLab CI: как передать результат сборки следующему job
- GitLab CI: job завис в pending — как проверить Runner и теги
Продолжить изучение
Хотите собрать проверяемый путь от коммита до релиза? Jobs, runners, артефакты и деплой — темы программы «GitLab CI/CD: автоматизация сборки и деплоя».
Продолжить на практике
- GitLab CI/CD: автоматизация сборки и деплоя ↗
От первого .gitlab-ci.yml до pipeline, который тестирует код, собирает образ и выкладывает приложение. Runners, artifacts, cache, registry, окружения и безопасный откат — на одном практическом проекте.
