CI и автозапуск тестов
Как сделать так, чтобы тесты запускались сами на каждое изменение и не пускали в проект сломанный код.
Непрерывная интеграция (CI) — практика, при которой каждое изменение автоматически собирается и прогоняется через тесты на сервере, прежде чем попасть в основную ветку.
Зачем CI
«У меня локально всё работает» — частая причина сломанных сборок: у коллеги другая версия, забытый файл, иное окружение. CI прогоняет тесты в чистом, одинаковом для всех окружении на сервере. Если тесты красные — изменение не вливается. Так основная ветка всегда остаётся рабочей.
Типичный пайплайн
- Разработчик делает
git pushили открывает pull request. - CI-сервер поднимает чистое окружение и ставит зависимости.
- Запускает линтер и тесты.
- Зелено — разрешает слияние; красно — блокирует и сообщает автору.
Такой автозапуск умеют GitHub Actions, GitLab CI, Jenkins и другие. На GitHub пайплайн описывают YAML-файлом в .github/workflows/. Это конфиг, не запускаемый код:
name: tests
on: [push, pull_request] # запускать на каждый push и PR
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 # упадёт -> сборка красная -> merge блокируется
Команда запуска тестов в проекте — обычно одна строка:
# локально и в CI запускается одинаково
pytest -q
Что именно проверяет CI
Сами тесты — обычный код с проверками, который мы писали весь курс. CI лишь запускает их автоматически. Смоделируем, что делает CI-шаг «прогнать тесты»: собирает результаты и решает, «зелено» или «красно».
def add(a, b):
return a + b
# Набор тестов проекта
def test_add_positive(): assert add(2, 3) == 5
def test_add_zero(): assert add(5, 0) == 5
def test_add_negative(): assert add(-1, -1) == -2
# Имитация CI: прогнать все тесты и вынести вердикт
suite = [test_add_positive, test_add_zero, test_add_negative]
failed = 0
for t in suite:
try:
t()
except AssertionError:
failed += 1
if failed == 0:
print(f"CI: PASS — {len(suite)} тестов зелёные, слияние разрешено")
else:
print(f"CI: FAIL — {failed} тест(ов) упало, merge заблокирован")
Вывод:
CI: PASS — 3 тестов зелёные, слияние разрешено
Итог
- CI автоматически прогоняет тесты в чистом окружении на каждое изменение.
- Красные тесты блокируют слияние — основная ветка всегда рабочая.
- На GitHub пайплайн описывают YAML в
.github/workflows/; тесты запускает одна команда.