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

Тесты, которые лежат в репозитории и не запускаются, ничего не гарантируют. Чтобы тесты реально работали, нужны две вещи:

  1. Покрытие: знать, какие части кода тесты действительно прогоняют, а какие нет.
  2. CI (Continuous Integration): чтобы тесты автоматически запускались на каждый push, а не «когда-нибудь» вручную.

Покрытие через pytest-cov

Покрытие это процент исходного кода, который выполнился во время прогона тестов. Высокое покрытие не гарантирует отсутствия ошибок (можно покрыть строку, но проверить её плохо), но низкое покрытие однозначно показывает «здесь не тестировано».

Для проектов с pytest стандартный инструмент это плагин pytest-cov:

pip install pytest pytest-cov

Запускаем с флагом --cov и именем пакета:

pytest --cov=app tests/

В выводе появится сводка:

---------- coverage: platform ... -- Python ... -----------
Name              Stmts   Miss  Cover
-------------------------------------
app/users.py        25      5    80%
app/orders.py       18      0   100%
-------------------------------------
TOTAL               43      5    88%
  • Stmts: сколько строк кода в файле.
  • Miss: сколько не выполнилось во время тестов.
  • Cover: процент покрытия.

Для детального отчёта (с подсветкой непокрытых строк в браузере) используют HTML:

pytest --cov=app --cov-report=html

Команда создаст папку htmlcov/, откройте htmlcov/index.html — увидите построчное покрытие.

О процентах

Гнаться за 100% обычно не стоит. Реалистичная цель: 80-90% для бизнес-логики. То, что осталось непокрытым, важнее анализировать вручную: «это критичный код или просто getter/setter?». Покрытие это сигнал о пробелах, а не цель сама по себе.

Что такое CI

CI это автоматический запуск тестов (и любых других проверок) при изменениях в репозитории. На GitHub это делает GitHub Actions, в GitLab встроенный GitLab CI, есть ещё CircleCI, Jenkins и другие. Принцип у всех один: на каждый git push запускается заданный pipeline.

Иллюстрация: git push → GitHub Actions запускается автоматически → pytest + coverage → ветка результата passed (зелёный) или failed (красный)

Минимальный CI на GitHub Actions

Положите файл .github/workflows/tests.yml в корне репозитория:

name: tests

on:
    push:
        branches: [main]
    pull_request:
        branches: [main]

jobs:
    test:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4
            - uses: actions/setup-python@v5
              with:
                  python-version: '3.12'
            - run: pip install -r requirements.txt
            - run: pytest --cov=app

Что это делает:

  1. Запускается на push и pull_request в ветку main.
  2. Клонирует репозиторий и ставит Python 3.12.
  3. Ставит зависимости из requirements.txt.
  4. Запускает тесты с покрытием.

Если хоть один тест упал, pipeline становится красным, и PR блокируется (если включить такое требование в настройках репозитория).

Что это даёт

  • Ошибка ловится сразу, а не через неделю в production.
  • Тесты прогоняются всегда, не нужно полагаться на «я запущу локально перед мержем».
  • Pull request показывает, прошли тесты или нет, ревьюер видит сразу.
  • Покрытие отслеживается: если в PR добавлен код без тестов, видно в отчёте.

Проверка понимания

Что верно про покрытие кода и CI?


На этом модуль по тестированию закончен. Вы умеете писать тесты на pytest, использовать фикстуры и параметризацию, подменять зависимости моками, читать unittest-код в legacy-проектах, мерить покрытие и настраивать CI. Этого достаточно, чтобы вкатиться в любой production-проект — остальное добирается по месту.