Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 09.07 · 15 мин
Средний
pytestModule summarycoverage.pyCI integrationGitHub ActionsPhase 69 forward-linkPhase 70 forward-linkRun-on-Your-Machineproduction hygiene

M08 summary + bridge к coverage.py и CI — production hygiene

Этот финальный урок Module 08 — bridge между in-browser pytest learning и production CI/CD pipeline на вашей local machine + remote runner. Module 08 дал vocabulary для writing tests; этот урок — что дальшеcoverage measurement + CI integration для production hygiene.

В этом уроке:

  1. M08 recap — 6 уроков, ключевые концепции.
  2. Synthesis recap — M06 урок 05 (@contextmanager) → M08 урок 02 (yield fixtures) architectural reuse.
  3. Why coverage.py — measurement of test thoroughness.
  4. Run-on-Your-Machinepip install coverage + coverage run -m pytest + coverage report + coverage html.
  5. Forward-link Phase 69 — pytest в pre-commit hook + GitHub Actions workflow.
  6. Forward-link Phase 70 — final exam + assessment parity launch.

M08 recap — 6 уроков, 6 концепций

#УрокКлючевая концепцияCite
1test-discovery-aaatest_.py / _test.py / Test class / test_ function patterns; AAA pattern (Arrange/Act/Assert); plain assert + AST rewriting_pytest/main.py + _pytest/assertion/rewrite.py
2fixtures-and-scope@pytest.fixture decorator; yield-style + try/finally; scope hierarchy function/class/module/package/session (Pitfall 16); fixture dependencies_pytest/fixtures.py + Lib/contextlib.py reuse
3parametrize-and-ids@pytest.mark.parametrize tables; ids= readable IDs; pytest.param(marks=) per-row; testCases parallel_pytest/python.py:Metafunc
4pytest-raises-and-typed-exceptionspytest.raises(Exc, match=r’regex’); re.search semantics (Pitfall 18); cross-link M07 урок 06 (PYTH-09)_pytest/python_api.py + Lib/builtins.py
5mocks-and-monkeypatchunittest.mock.MagicMock + patch + monkeypatch (Pitfall 17 ScopeMismatch); pytest.MonkeyPatch() session alternativeLib/unittest/mock.py + pytest 8 monkeypatch
6conftest-and-discoveryconftest.py auto-discovery; src-layout vs flat-layout; pyproject.toml [tool.pytest.ini_options]_pytest/config/init.py + PEP 518

Cross-module bridges уже накоплены:

  • M06 урок 05 → M08 урок 02: @contextmanager pattern directly reused в _pytest/fixtures.py для yield-fixtures.
  • M07 урок 06 → M08 урок 04: PYTH-09 typed exceptions ↔ pytest.raises(SpecificType, match=...).
  • M07 урок 04 → M08 урок 02: runtime introspection (inspect.signature.parameters) используется pytest для fixture injection.

Module 08 — practical application уроков Phase 65/66/67 в production-grade testing tool.


Synthesis recap — climax M06 → M08

M06 урок 05 дал нам @contextlib.contextmanager decorator — synthesis closure (M03 урок 04) + generator (M05 урок 02) + context-manager protocol (M06 урок 04):

# M06 урок 05 climax
from contextlib import contextmanager

@contextmanager
def temp_attr(obj, attr, val):
    original = getattr(obj, attr)
    setattr(obj, attr, val)
    try:
        yield obj                  # ← boundary: setup → teardown
    finally:
        setattr(obj, attr, original)

M08 урок 02 показал — тот же pattern в pytest’s _pytest/fixtures.py:

# M08 урок 02 — pytest yield-fixture
import pytest

@pytest.fixture
def db():
    conn = open_conn()
    try:
        yield conn                 # ← same boundary
    finally:
        conn.close()

Architectural reuse — pytest’s fixture machinery literally reuses _GeneratorContextManager-like handler из contextlib.contextmanager. Это не parallel — это shared implementation. Closure + generator + protocol → @contextmanager@pytest.fixture(yield-style). Same Python primitives, three abstractions.

Это — “deep” payoff для учащегося: production-grade testing tool не магия — built из stdlib primitives, которые мы выучили.


Why coverage.py — measurement of test thoroughness

Tests могут пройти, но не покрывать important paths. Coverage measures which lines / branches of source code были executed во время test run. Highlights untested code.

# mypkg/auth.py
def login(email: str, password: str) -> bool:
    if not email or not password:
        return False                 # ← may be uncovered if test only happy path
    user = User.find_by_email(email)
    if user is None:
        return False                 # ← may be uncovered
    return user.verify_password(password)

Without coverage — вы не знаете, что edge cases (empty email, non-existent user) — untested. Coverage — rachet для confidence: increase coverage → catch regressions earlier.

coverage.py — third-party tool (NOT в stdlib), default Python coverage measurement. Integrates с pytest via pytest-cov plugin или standalone coverage run -m pytest.

Cite: coverage.py docs; pytest-cov — pytest plugin variant.


Run-on-Your-Machine — coverage.py workflow

TIP

Run-on-Your-Machine: coverage.py + pytest workflow

Установите coverage:

pip install coverage
# OR — pytest-cov plugin variant:
# pip install pytest-cov

Создайте mypkg/calc.py:

# mypkg/calc.py
def add(a: int, b: int) -> int:
    return a + b


def divide(a: int, b: int) -> float:
    if b == 0:
        raise ZeroDivisionError('cannot divide by zero')
    return a / b

Создайте tests/test_calc.py:

# tests/test_calc.py
import pytest
from mypkg.calc import add, divide


def test_add():
    assert add(2, 3) == 5


def test_divide():
    assert divide(10, 2) == 5.0
    # NOT testing zero-division path!

Запустите с coverage:

coverage run --source=mypkg -m pytest tests/

Получите отчёт:

coverage report

Expected output:

Name                Stmts   Miss  Cover   Missing
-------------------------------------------------
mypkg/__init__.py       0      0   100%
mypkg/calc.py           5      2    60%   5-6
-------------------------------------------------
TOTAL                   5      2    60%

Что мы видим:

  • mypkg/calc.py — 5 statements, 2 missed → 60% coverage.
  • Missing lines 5-6 — это if b == 0: raise ZeroDivisionError(...)untested edge case.

Сгенерируйте HTML report для interactive review:

coverage html

Откройте htmlcov/index.html в браузере — interactive view, какие lines не purchased.

Bonus — добавьте test для edge case:

# Add to test_calc.py:
def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError, match='cannot divide by zero'):
        divide(10, 0)

Re-run coverage — теперь 100% (lines 5-6 covered).

Production CI gate: добавьте к pyproject.toml:

[tool.coverage.report]
fail_under = 80                      # CI fails if coverage < 80%
show_missing = true

Запуск coverage report exits non-zero если < 80% — pre-commit hook / CI workflow blocks PR.


Production CI integration будет cover’ed в Phase 69 (Production Skills). Preview:

pre-commit hook

Run pytest before every commit — block bad commits локально before push:

# .pre-commit-config.yaml (Phase 69 preview)
repos:
  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: pytest
        language: system
        pass_filenames: false
        always_run: true
pip install pre-commit
pre-commit install      # installs git hook
git commit -m "..."     # runs pytest before commit

GitHub Actions workflow

# .github/workflows/test.yml (Phase 69 preview)
name: Test

on: [push, pull_request]

jobs:
  pytest:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.11', '3.12', '3.13']

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -e .[dev]
      - run: coverage run --source=mypkg -m pytest
      - run: coverage report --fail-under=80
      - uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml

Что мы получили (preview):

  1. Matrix testing — pytest runs против 3 Python versions параллельно.
  2. Coverage gate — PR blocked если coverage drops < 80%.
  3. External tool integration — Codecov uploads coverage report для PR review.

Это — production hygiene. Tests meaningful только если CI enforces them. Phase 69 даст полное treatment.


Phase 70 (Launch Polish) — финальная phase milestone v2.4 Python для Data Engineer. Preview:

  1. Final exam — across all 9 modules (M00-M08) — comprehensive assessment.
  2. Assessment parity с track-001 patterns — uniform exam shape, passThreshold standardization.
  3. Course-wide self-assessment checklists — 7-item per module style (modeling Phase 67 Plan 67-02 lesson 07 + this lesson).
  4. Glossary final review — ~50+ terms across 6+ categories.

После Phase 70 — milestone v2.4 launch ready.


M08 self-assessment checklist

Перед finalize’ом Module 08 — убедитесь, что можете ответить без подсказки:

  1. Назовите все 4 file/class/function name patterns, по которым pytest автоматически discover’ит tests. (Answer: test_*.py, *_test.py, Test* classes без __init__, test_* functions.)
  2. Объясните, какие 5 scope levels поддерживает @pytest.fixture(scope=...) — order from narrow to broad. (Answer: function / class / module / package / session.)
  3. Запишите yield-style fixture с defensive teardown (try/finally guarantee). Объясните cross-link к M06 урок 05 @contextmanager.
  4. Какая разница между @pytest.mark.parametrize('a,b', [(1,2)]) (один decorator) и stacked @parametrize('a',[1]) + @parametrize('b',[2]) (два decorators)?
  5. Pitfall 18: какой regex method использует pytest.raises(Exc, match='...')re.match или re.search? Какая практическая implication?
  6. Реализуйте test для validate_age('abc') raising InvalidFormatError(ValidationError) — show pytest.raises(InvalidFormatError, match=...) shape (cross-link M07 урок 06 PYTH-09).
  7. Pitfall 17: почему monkeypatch fixture нельзя использовать в @pytest.fixture(scope='session') fixture? Какое workaround?
  8. Назовите 4 main config keys в pyproject.toml [tool.pytest.ini_options]testpaths, markers, addopts, python_files. Что включает --strict-markers?

Если уверенно отвечаете на все 8 — Module 08 пройден. Сомневаетесь — re-read соответствующий урок.


Cross-course context

Cross-course → DataFusion: 09/02 ecosystem-databases — DataFusion-based databases (Ballista, InfluxDB IOx, GreptimeDB) shipping CI/CD pipeline по тем же principles, что pytest + coverage + GitHub Actions: PR triggers test matrix, coverage threshold блокирует merge, pre-commit hooks валидируют code style. Параллель архитектурная: production data engine эволюционирует через тот же gating-flow, что и любая Python library.

Cross-course → ClickHouse: 14/06 schema-migrations — schema migration testing — параллельный pattern к pytest для DDL: каждая миграция должна быть тестируема (apply + rollback + idempotency), что покрывается набором integration-tests аналогичных функциональным pytest setup’ам. Coverage threshold для migrations: 100% (каждая ALTER TABLE должна иметь reverse-migration test).


Ключевые выводы

  1. Module 08 закрыт — 6 уроков покрывают full pytest vocabulary: discovery, fixtures, parametrize, exceptions, mocks, conftest. Production-grade testing toolkit.
  2. Climax cross-link M06 → M08@contextmanager (M06 урок 05) directly reused в _pytest/fixtures.py для yield-fixtures (M08 урок 02). Architectural reuse — closure + generator + protocol → @contextmanager → @pytest.fixture.
  3. coverage.py — measurement of test thoroughness. coverage run --source=X -m pytest + coverage report + coverage html. Production CI gate: fail_under = 80.
  4. pre-commit hook — pytest runs before every commit locally. GitHub Actions workflow — pytest + coverage matrix testing across Python versions. Phase 69 даст полный treatment.
  5. Phase 70 — final exam (cross-module) + assessment parity launch milestone v2.4.
  6. M00 урок 03 promise fulfilled — Run-on-Your-Machine convention shipped 4+ callouts в M07/M08 (mypy install + mypy.ini + fixtures + parametrize + mocks + coverage). Browser challenges + local toolchain — both first-class learning surfaces.

Поздравляем — вы прошли production testing toolkit. Module 08 — bridge от concept to confidence. Дальше — Phase 69 production skills + Phase 70 launch polish + финальный экзамен курса.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7