Стратегия маркеров: @pytest.mark.slow, @pytest.mark.integration. GitHub Actions: Docker Compose, кеширование образов. pytest-xdist параллельный запуск. Matrix-стратегия: разные версии PostgreSQL. Coverage thresholds и quality gates
Production CI/CD должен быть быстрым (параллельный запуск), надёжным (quality gates) и гибким (тесты на разных версиях БД).
Цель: Настроить production-ready CI/CD pipeline с pytest-xdist, Docker и quality gates.
# Все тесты вместе — медленно!
pytest tests/
# Результат:
# 500 unit tests (fast) + 50 integration tests (slow) = 10 minutes# tests/test_users_unit.py
import pytest
@pytest.mark.unit
def test_user_validation():
"""Быстрый unit тест"""
# ...
pass
# tests/test_users_integration.py
@pytest.mark.integration
@pytest.mark.slow
def test_user_creation_db(db_connection):
"""Медленный integration тест"""
# ...
pass# pytest.ini
[pytest]
markers =
unit: Unit tests (fast, no external dependencies)
integration: Integration tests (slow, require DB/API)
slow: Slow tests (>1s per test)
smoke: Smoke tests (critical functionality)# Только unit (быстрые)
pytest -m unit
# Только integration
pytest -m integration
# Исключить slow
pytest -m "not slow"
# unit ИЛИ smoke
pytest -m "unit or smoke"
# integration И медленные
pytest -m "integration and slow"name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run unit tests
run: |
pytest -m unit --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest
- name: Run integration tests
env:
DB_HOST: localhost
DB_PORT: 5432
DB_USER: testuser
DB_PASSWORD: testpass
DB_NAME: testdb
run: |
pytest -m integration -vpip install pytest-xdist# Автоматическое определение количества ядер
pytest -n auto
# Конкретное количество workers
pytest -n 4
# С маркерами
pytest -m integration -n auto# .github/workflows/tests.yml
- name: Run integration tests (parallel)
run: |
pytest -m integration -n auto -v# ❌ Плохо: глобальное состояние
cache = {}
def test_cache_write():
cache["key"] = "value"
def test_cache_read():
assert cache["key"] == "value" # ❌ Упадёт при -n auto!Решение: изоляция через фикстуры
# ✅ Хорошо: каждый тест изолирован
@pytest.fixture
def cache():
return {}
def test_cache_write(cache):
cache["key"] = "value"
def test_cache_read(cache):
# Новый cache для каждого теста
cache["key"] = "value"
assert cache["key"] == "value"# pytest.ini
[pytest]
addopts =
--cov=src
--cov-report=term-missing
--cov-report=xml
--cov-fail-under=80--cov-fail-under=80 — тесты упадут если coverage < 80%
- name: Run tests with coverage
run: |
pytest -m unit --cov=src --cov-fail-under=80
- name: Check coverage report
if: failure()
run: |
echo "❌ Coverage is below 80%"
exit 1# .github/workflows/tests.yml
jobs:
integration-tests:
runs-on: ubuntu-latest
strategy:
matrix:
postgres-version: [12, 13, 14, 15, 16]
services:
postgres:
image: postgres:${{ matrix.postgres-version }}
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
steps:
- name: Run tests on PostgreSQL ${{ matrix.postgres-version }}
run: |
pytest -m integration -v✅ Тесты запустятся на всех 5 версиях PostgreSQL!
# ❌ Медленно: 5 минут на сборку каждый раз
- name: Build Docker image
run: docker build -t myapp .
- name: Run tests
run: docker-compose up --abort-on-container-exit- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ hashFiles('**/Dockerfile') }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and cache
uses: docker/build-push-action@v5
with:
context: .
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
tags: myapp:latest
load: true
- name: Run tests
run: docker-compose up --abort-on-container-exit# Flaky test: иногда проходит, иногда падает
def test_api_response_time():
response = requests.get("http://api/endpoint")
assert response.elapsed.total_seconds() < 1.0 # ❌ Иногда 1.1s!pip install pytest-rerunfailures# Перезапустить упавшие тесты 3 раза
pytest --reruns 3- name: Run tests with reruns
run: |
pytest -m integration --reruns 3 --reruns-delay 2 -v# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install ruff mypy
- run: ruff check .
- run: mypy src/
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -r requirements.txt pytest pytest-cov pytest-xdist
- run: pytest -m unit -n auto --cov=src --cov-fail-under=80 -v
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
strategy:
matrix:
postgres-version: [15, 16]
services:
postgres:
image: postgres:${{ matrix.postgres-version }}
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -r requirements.txt pytest pytest-xdist pytest-rerunfailures
- run: pytest -m integration -n auto --reruns 2 -v
quality-gate:
runs-on: ubuntu-latest
needs: [lint, unit-tests, integration-tests]
if: always()
steps:
- name: Check all jobs passed
run: |
if [[ "${{ needs.lint.result }}" == "failure" ]] || \
[[ "${{ needs.unit-tests.result }}" == "failure" ]] || \
[[ "${{ needs.integration-tests.result }}" == "failure" ]]; then
echo "❌ Quality gate FAILED"
exit 1
fi
echo "✅ Quality gate PASSED"Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.