Коли тести виконуються лише на ноутбуці розробника, вони перестають виконувати свою справжню роботу: ловити регресії до того, як код потрапить у продакшен. Безперервна інтеграція (CI) -- це практика автоматичного запуску повного тест-сьюту при кожному push або pull request на чистій машині, що нічого не знає про ваше локальне налаштування. Два інструменти, з якими ви будете стикатися найчастіше: **GitHub Actions** (вбудований у GitHub, безкоштовний для публічних репозиторіїв) і **tox** (Python-специфічний інструмент автоматизації тестів).
## Чому CI -- це не просто "pytest на сервері"
Запуск pytest у CI вводить обмеження, яких не існує локально:
- **Немає попередньо встановлених пакетів.** Раннер CI стартує з базового образу ОС. Ваш робочий процес має встановити кожну залежність.
- **Немає `.env`-файлів.** Секрети та змінні середовища мають надаватися через сховище секретів системи CI.
- **Ненульовий код виходу має значення.** pytest виходить з кодом `1`, коли будь-який тест провалюється. CI-платформи трактують ненульовий вихід як збій пайплайну -- pull request не може бути злитий.
- **Відтворюваність.** Тест, що проходить локально, але провалюється в CI, майже завжди означає, що тест залежить від чогось у вашому середовищі (глобально встановленого пакету, локального файлу, запущеного сервісу).
## Основи GitHub Actions
GitHub Actions запускає робочі процеси, визначені як YAML-файли у `.github/workflows/`. Робочий процес спрацьовує за подіями (push, pull request, розклад) і виконує послідовність кроків всередині раннера -- тимчасової віртуальної машини.
Мінімальна структура робочого процесу для pytest:
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytest
```
Кожен рядок `uses:` підключає попередньо зібрану дію. `actions/checkout@v4` клонує ваш репозиторій у раннер. `actions/setup-python@v5` встановлює потрібну версію Python і додає її до `PATH`.
## Кешування завантажень pip
Встановлення пакетів з нуля при кожному запуску -- це повільно. GitHub Actions дозволяє кешувати кеш завантажень pip між запусками:
```yaml
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
```
`key` включає хеш `requirements.txt`, тому кеш інвалідується при зміні залежностей. `restore-keys` надає резервний варіант, що відповідає ОС навіть при промаху точного ключа -- він відновлює частковий кеш, який все одно швидше, ніж починати з нічого.
## Провал пайплайну при падінні покриття
Покриття вимірює, який відсоток вашого коду виконується тестами. Додавання `--cov` до pytest (за наявності `pytest-cov`) виводить звіт про покриття. Додавання `--cov-fail-under=80` змушує pytest виходити з кодом `2`, якщо покриття падає нижче 80%, що провалює CI-пайплайн:
```yaml
- name: Run tests with coverage
run: pytest --cov=src --cov-fail-under=80 --cov-report=term-missing --cov-report=xml
```
`--cov-report=xml` записує `coverage.xml` у форматі Cobertura -- багато CI-інтеграцій та інструментів перегляду PR можуть парсити цей файл для відображення diff покриття в рядку. Якщо XML-файл не потрібен, цей прапор можна опустити.
`--cov=src` вказує pytest-cov, який каталог вимірювати (вихідний код, а не самі тести). `--cov-report=term-missing` виводить таблицю, що показує непокриті рядки.
Поріг -- це рішення щодо політики. 80% є поширеною відправною точкою; 100% часто є нереалістичним і контрпродуктивним (ви в кінцевому підсумку пишете тести, які нічого не тестують змістовно, лише щоб досягти числа).
## Тестування на різних версіях Python з tox
`tox` -- це інструмент, що створює ізольовані віртуальні середовища і запускає ваш тест-сьют всередині кожного з них. Він найкорисніший для авторів бібліотек, яким потрібно перевірити сумісність з кількома версіями Python, але також поширений у проектах застосунків, яким потрібно підтримувати різні середовища.
Мінімальний `tox.ini`:
```ini
[tox]
envlist = py311, py312
[testenv]
deps = -r requirements.txt
commands = pytest {posargs}
```
Запуск `tox` локально створює `.tox/py311/` і `.tox/py312/` віртуальні середовища, встановлює залежності в кожному і запускає `pytest` всередині кожного. `{posargs}` передає будь-які аргументи, додані після `tox --`, безпосередньо до pytest (`tox -- -k test_login`).
У GitHub Actions можна використовувати **матрицю** для запуску tox по версіях:
```yaml
strategy:
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox -e py${{ matrix.python-version | replace('.', '') }}
```
Це запускає два паралельних завдання -- по одному на кожну версію -- і позначає робочий процес як невдалий, якщо будь-яке з них провалюється.
## Налаштування pytest для CI
Ви можете зберігати CI-специфічні дефолтні значення pytest у `pytest.ini` (або `pyproject.toml`), щоб кожен запуск -- локальний і в CI -- використовував однакові налаштування:
```ini
[pytest]
addopts = -v --tb=short
testpaths = tests
```
`--tb=short` дає достатньо трасування для діагностики збоїв без заповнення логу шумом. `-v` (детальний режим) виводить кожне ім'я тесту під час запуску, що допомагає при читанні логів CI визначити, який тест провалився.
## Звіти про результати тестів
Деякі системи CI та інтеграції GitHub можуть рендерити результати тестів як структурований звіт, а не як вихід необробленого терміналу. pytest може виводити JUnit XML-файл:
```
pytest --junitxml=reports/test-results.xml
```
GitHub Actions потім може завантажити це як артефакт:
```yaml
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: test-results
path: reports/test-results.xml
```
Це не змінює, пройде чи провалиться пайплайн -- це просто робить результати доступними для завантаження після запуску, що корисно при налагодженні нестабільних збоїв.
Повний CI-робочий процес: GitHub Actions + tox + покриття
Створіть файл `.github/workflows/tests.yml` для проекту з такою структурою:
```
my_project/
├── src/
│ └── app.py
├── tests/
│ └── test_app.py
└── requirements-dev.txt # містить: pytest
```
Вимоги до робочого процесу:
1. Спрацьовувати при `push` і `pull_request` до гілки `main`
2. Запускатись на `ubuntu-latest` з Python 3.12
3. Клонувати код
4. Встановлювати залежності з `requirements-dev.txt`
5. Запускати `pytest`
Напишіть повний YAML-вміст файлу робочого процесу.
# Напишіть вміст .github/workflows/tests.yml нижче.
# Це YAML-файл, а не Python -- заповніть кожну секцію.
# name: ...
# on:
# ...
# jobs:
# test:
# runs-on: ...
# steps:
# - ...
Напишіть `tox.ini` для проекту, де:
- Тести мають запускатись на Python 3.11 і 3.12
- Залежності знаходяться у `requirements.txt` і `requirements-dev.txt`
- Команда тестування -- `pytest` з будь-якими аргументами, які користувач передає через tox
Також напишіть команду для:
1. Запуску tox для всіх середовищ
2. Запуску tox лише для Python 3.12
3. Передачі `-v` до pytest через tox
# tox.ini
[tox]
envlist = ...
[testenv]
deps =
...
commands =
...
# Команди (напишіть як коментарі):
# 1. Запустити всі середовища:
# tox ...
#
# 2. Запустити лише Python 3.12:
# tox ...
#
# 3. Передати -v до pytest:
# tox ...
У вас є цей крок робочого процесу, що запускає тести з покриттям:
```yaml
- name: Run tests with coverage
run: pytest --cov=src --cov-report=term-missing --cov-fail-under=80 --cov-report=xml
```
Додайте крок після нього, що:
1. Завантажує `coverage.xml` як артефакт GitHub Actions з ім'ям `coverage-report`
2. Запускається навіть якщо попередній крок провалився (наприклад, поріг покриття не досягнуто)
Напишіть лише новий крок у YAML.
# Напишіть два кроки для додавання після кроку 'Run tests with coverage':
# - name: Upload coverage report
# uses: ...
# with:
# ...
# if: ...
Команда має порожній `pytest.ini` і CI-робочий процес, де крок тестування є:
```yaml
- name: Run tests
run: pytest -v --tb=short --strict-markers tests/
```
Проблема: розробники, що запускають `pytest` локально, отримують інший вивід, ніж CI (без `-v`, повні трасування), і шлях `tests/` доводиться повторювати і у файлі робочого процесу, і в будь-яких локальних скриптах.
Виправте це, написавши `pytest.ini`, що:
1. Встановлює `addopts` так, що `-v --tb=short --strict-markers` завжди застосовуються
2. Встановлює `testpaths` так, що pytest завжди шукає в `tests/` за замовчуванням
3. Реєструє три кастомні мітки, щоб уникнути `PytestUnknownMarkWarning`: `unit`, `integration`, `slow`
Після цієї зміни крок CI має бути просто `pytest` без зайвих аргументів.
Напишіть повний `pytest.ini` і спрощений крок CI.
# pytest.ini
[pytest]
addopts = ...
testpaths = ...
markers =
...
# Спрощений крок CI (напишіть як коментар):
# - name: Run tests
# run: ...
Рішення
# pytest.ini
[pytest]
addopts = -v --tb=short --strict-markers
testpaths = tests
markers =
unit: fast unit tests with no I/O
integration: tests that hit the database or network
slow: tests that take more than 1 second
# Спрощений крок CI:
# - name: Run tests
# run: pytest
No split tab
Налаштування cookies
Ми використовуємо необхідні cookies для роботи сайту. З вашого дозволу ми також можемо зберігати налаштування сайту та використовувати аналітичні й рекламні cookies, щоб розуміти використання сайту й підтримувати розвиток проєкту.
* Ви завжди можете змінити свій вибір у налаштуваннях сайту.
Оберіть категорії cookies
Налаштування аналітики
Можна вимкнути аналітику використання платформи. Також можна надіслати в Google Analytics запит на видалення даних про використання цього сайту, пов'язаних із цим браузером.
Навчальний workspace
Навчайтеся, читаючи, запускаючи код і розв'язуючи задачі.
Практикуйте програмування з поясненнями тем, вправами, інструментами browser IDE, перевіркою regex і тренуванням друку коду в одному workspace.
Відкривайте інструменти у вкладках.Вправи, IDE-інструменти й тренажери залишаються доступними як вкладки сайту.
Перемикайтеся без втрати контексту.Переходьте між поясненнями, кодом та утилітами, зберігаючи своє місце.
Використовуйте sidebar як карту.Ліві панелі містять навігацію, налаштування, файли, libraries та керування інструментами.
PythonJavaScriptSQLite
Одна IDE, три практичні режими
Python у браузері.Запускайте невеликі скрипти, пробуйте бібліотеки й тренуйте API-запити без встановлення.
JavaScript для швидких експериментів.Перевіряйте код для браузера й порівнюйте ідеї поруч із навчальними матеріалами.
SQLite для практики з даними.Відкривайте оглядач бази даних, переглядайте таблиці, пишіть запити й вивчайте SQL локально.
ТемаIDE
Працюйте поруч у split tabs
Тримайте інструкції перед очима.Відкрийте вправу або довідкову сторінку поруч з IDE, замість постійних перемикань.
Порівнюйте інструменти під час навчання.Розміщуйте перевірки regex, пояснення та експерименти з кодом поруч, коли це потрібно для задачі.
Закрийте split, коли завершите.Workspace повернеться до однієї сфокусованої вкладки, а відкриті вкладки сайту залишаться доступними.
Тренажер друку коду
Або звичайного тексту
Тренажер розрахований на фізичну клавіатуру.Відкрийте цей розділ на ноутбуці або комп'ютері з широким екраном. На телефоні тренування друку не працюватиме коректно.
Швидкість: 0 зн/хв
0 слів/хв
Найкраща швидкість (60с): 0 зн/хв
0 слів/хв
Помилки: 0
Загальний час: 0.0 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.