Раздел 10 — Отчёты, покрытие, CI

Тесты, которые запускаются сами на каждый коммит и оставляют понятный отчёт

Прогресс курса Раздел 10 из 12

Что вы освоите в этом разделе

6 академических часов: 2 теории, 4 практики. Что такое конвейер, раннер и артефакт, подробно разобрано в курсе «CI/CD»; здесь — только то, что нужно для автотестов.
01

Отчёт JUnit XML

Вывод pytest в терминале удобен человеку. Программам — CI, системе управления тестированием, панели качества — нужен файл. Стандарт де-факто — формат JUnit XML: его понимают GitLab, Jenkins, TeamCity и большинство систем управления тестированием, и его умеют писать фреймворки всех распространённых языков.

$ pytest -q --junitxml=report.xml
.....                                                                    [100%]
5 passed in 1.63s
$ sed "s/></>\n</g" report.xml; echo
<?xml version="1.0" encoding="utf-8"?>
<testsuites name="pytest tests">
<testsuite name="pytest" errors="0" failures="0" skipped="0" tests="5" time="1.645" timestamp="2026-10-31T03:15:35.908371+03:00" hostname="mypc">
<testcase classname="tests.test_smoke" name="test_login_page_opens" time="0.216" />
<testcase classname="tests.test_smoke" name="test_api_login_gives_token" time="0.004" />
<testcase classname="tests.test_smoke" name="test_ivan_sees_own_tasks" time="0.006" />
<testcase classname="tests.test_smoke" name="test_create_and_delete" time="0.011" />
<testcase classname="tests.test_smoke" name="test_ui_login[chromium]" time="1.363" />
</testsuite>
</testsuites>

В файле на каждый тест — элемент testcase с именем и временем; у упавшего внутри будет failure с сообщением, у пропущенного — skipped. Время теста в браузере — больше секунды, остальных — миллисекунды: та же пирамида из раздела 01, только в цифрах.

02

HTML-отчёт

Плагин pytest-html делает отчёт для людей: таблица тестов с результатом и временем, фильтры, вывод упавших тестов. Ключ --self-contained-html собирает всё в один файл, который можно отправить или открыть из артефактов CI.

$ pytest -q --html=report.html --self-contained-html
.....                                                                    [100%]
--- Generated html report: file://~/qa/starter/report.html ----
5 passed in 1.61s
$ ls -l report.html | cut -d" " -f5-
34640 окт 31 03:15 report.html

Отчёт открывается в любом браузере. Его прикладывают к тест-отчёту выпуска и к баг-репорту, если дефект найден автотестом. JUnit XML и HTML можно получить одним прогоном — ключи совместимы.

03

Покрытие кода

Покрытие (coverage) — доля строк кода, которые выполнились хотя бы раз во время тестов. Инструмент coverage запускает pytest под наблюдением и отмечает выполненные строки. Пример — тесты раздела 03 на shop.py (ошибка в скидке уже исправлена):

$ coverage run --include=shop.py -m pytest -q
.........                                                                [100%]
9 passed in 0.26s
$ coverage report -m
Name      Stmts   Miss  Cover   Missing
---------------------------------------
shop.py      33     11    67%   11, 38-39, 44-46, 51-53, 58-59
---------------------------------------
TOTAL        33     11    67%

Столбец Missing — номера невыполненных строк. Это функции today, order_total, wednesday_bonus, save_receipt и send_sms: их проверяют тесты раздела 05, а здесь запущены только тесты раздела 03. Отчёт по строкам с подсветкой — coverage html, файл htmlcov/index.html:

$ coverage html && ls htmlcov | head -5
Wrote HTML report to htmlcov/index.html
class_index.html
coverage_html_cb_54cfdca1.js
favicon_32_cb_c827f16f.png
function_index.html
index.html

Чем покрытие полезно и чем опасно:

Покрытие говоритПокрытие не говорит
Какой код не выполнялся ни одним тестом — там дефекты точно не найденыЧто выполненный код проверен: тест без assert тоже даёт покрытие
Какие ветки условий забыты (с ключом --branch)Что проверены границы: total > 5000 выполнялось и на ошибочной версии
Как меняется доля непроверенного кода от выпуска к выпускуЧто требования выполнены — покрытие меряет код, а не требования

Строку с ошибкой if total > 5000 выполняли все тесты скидки — покрытие 100%, а дефект нашёл только тест на границе. Поэтому покрытие используют, чтобы найти «белые пятна», но не как цель. Требование «покрытие не ниже 80%» без требования к качеству проверок приводит к тестам, которые вызывают код и ничего не проверяют.

Покрытие считают для модульных тестов: они выполняют код в том же процессе. API- и UI-тесты гоняют приложение в отдельном процессе, и coverage его не видит без особой настройки. Для них мерой служит покрытие требований и операций API: каждая операция OpenAPI и каждое требование Т1–Т9 — хотя бы одним тестом.

04

Конвейер CI

Непрерывная интеграция (CI) запускает тесты автоматически на каждый коммит и на каждый запрос на слияние (merge request). Тесты, которые запускают «когда вспомнят», быстро устаревают; тесты в CI — часть правил команды: красный конвейер — изменение не принимают.

Конвейер описывают файлом в корне репозитория. Для GitLab CI это .gitlab-ci.yml:

# Автотесты «Задачника»: быстрые на каждый коммит, браузерные — следом.
stages:
  - test
  - ui

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

fast:
  stage: test
  image: python:3.13-slim
  cache:
    paths: [.cache/pip]
  script:
    - pip install -r requirements.txt
    - pytest -m "not slow" --junitxml=report-fast.xml
  artifacts:
    when: always
    reports:
      junit: report-fast.xml

ui:
  stage: ui
  image: mcr.microsoft.com/playwright/python:v1.64.0-noble
  script:
    - pip install -r requirements.txt
    - pytest -m slow --junitxml=report-ui.xml --html=report-ui.html --self-contained-html
      --screenshot only-on-failure --tracing retain-on-failure
  artifacts:
    when: always
    expire_in: 1 week
    paths:
      - report-ui.html
      - test-results/
    reports:
      junit: report-ui.xml
КлючЧто задаёт
stagesЭтапы по порядку. Браузерные тесты — вторым этапом: если упали быстрые, ждать медленные незачем
imageDocker-образ, в котором идёт задание. Для быстрых тестов хватает python:3.13-slim; для браузерных нужен образ, где уже стоят браузеры Playwright и системные библиотеки к ним, — официальный образ Playwright той же версии, что в requirements.txt
scriptКоманды задания: поставить пакеты и запустить pytest. Ненулевой код возврата pytest (есть упавшие) — задание красное
artifacts: reports: junitGitLab разбирает JUnit XML и показывает упавшие тесты прямо в merge request
artifacts: paths, when: alwaysФайлы, которые надо сохранить даже при падении: HTML-отчёт, снимки и трассы
cacheКэш скачанных пакетов между запусками — конвейер быстрее

Задание fast мы прогнали так же, как его выполнит раннер, — в чистом контейнере python:3.13-slim с копией проекта:

$ docker run --rm -v "$PWD":/src:ro python:3.13-slim sh -c "cp -r /src /w && cd /w && pip install -q --root-user-action=ignore --disable-pip-version-check -r requirements.txt && pytest -m \"not slow\" --junitxml=report-fast.xml"
============================= test session starts ==============================
platform linux -- Python 3.13.17, pytest-9.2.0, pluggy-1.7.0
rootdir: /w
configfile: pytest.ini
testpaths: tests
plugins: metadata-3.1.1, playwright-0.10.0, html-4.2.0
collected 5 items / 1 deselected / 4 selected

tests/test_smoke.py ....                                                 [100%]

-------------------- generated xml file: /w/report-fast.xml --------------------
======================= 4 passed, 1 deselected in 0.29s ========================

Файл проверен по схеме .gitlab-ci.yml версии GitLab 19.4. В GitFlic CI и GitHub Actions файл называется иначе и ключи пишутся по-другому, но части те же: образ, команды, отчёты, артефакты.

В CI нет экрана, поэтому браузер работает без окна — как у нас по умолчанию. А вот codegen и --headed там не работают. Тесты не должны зависеть от размера экрана: размер окна задают в самих тестах (set_viewport_size), если это важно.
05

Зелёный конвейер и известные дефекты

Автотесты нашли дефект, он зарегистрирован, но исправят его через месяц. Если оставить тест падать, конвейер будет красным весь месяц, и новые падения никто не заметит. Если удалить тест — дефект забудут. Правильно — отметить тест xfail со ссылкой на баг-репорт:

@pytest.mark.xfail(reason="BUG-31: скидка на границе 5000 не начисляется", strict=True)
def test_discount_at_5000():
    assert shop.discount(5000) == 500

Метки xfail ставят только на дефекты, у которых есть баг-репорт. «Тест падает, а почему — не знаю» — это не xfail, это задача на разбор (раздел 09).

06

Запуск одной командой

Конвейер и человек должны запускать набор одинаково. Всё, что можно, выносят в pytest.ini, а в CI и в README остаётся одна команда:

[pytest]
testpaths = tests
pythonpath = .
addopts = -ra --strict-markers
    --junitxml=report.xml
    --screenshot only-on-failure --tracing retain-on-failure
markers =
    smoke: быстрые проверки главного — запускать первыми
    slow: долгие проверки (браузер)

В README проекта пишут: что нужно поставить (Python, pip install -r requirements.txt, playwright install chromium), какой командой запустить и где смотреть отчёты. Проверка README простая: новый человек по нему запускает тесты на чистом компьютере без вопросов.

Ключевые выводы раздела

Запомните главное

  • JUnit XML — для CI и инструментов, HTML — для людей; оба одним прогоном
  • Покрытие показывает непроверенный код, но не качество проверок; цель — белые пятна, а не процент
  • Конвейер: быстрые тесты первым этапом, браузерные — вторым, в образе с браузерами той же версии Playwright
  • Отчёты и трассы — артефакты с when: always, иначе при падении их не будет
  • Известный дефект — xfail(strict=True) со ссылкой на баг-репорт; конвейер зелёный, дефект не забыт
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Раздел 09: UI — страница-объект и стабильность Все разделы Раздел 11: Качество в команде