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.
В этом уроке:
- M08 recap — 6 уроков, ключевые концепции.
- Synthesis recap — M06 урок 05 (@contextmanager) → M08 урок 02 (yield fixtures) architectural reuse.
- Why coverage.py — measurement of test thoroughness.
- Run-on-Your-Machine —
pip install coverage+coverage run -m pytest+coverage report+coverage html. - Forward-link Phase 69 — pytest в pre-commit hook + GitHub Actions workflow.
- Forward-link Phase 70 — final exam + assessment parity launch.
M08 recap — 6 уроков, 6 концепций
| # | Урок | Ключевая концепция | Cite |
|---|---|---|---|
| 1 | test-discovery-aaa | test_.py / _test.py / Test class / test_ function patterns; AAA pattern (Arrange/Act/Assert); plain assert + AST rewriting | _pytest/main.py + _pytest/assertion/rewrite.py |
| 2 | fixtures-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 |
| 3 | parametrize-and-ids | @pytest.mark.parametrize tables; ids= readable IDs; pytest.param(marks=) per-row; testCases parallel | _pytest/python.py:Metafunc |
| 4 | pytest-raises-and-typed-exceptions | pytest.raises(Exc, match=r’regex’); re.search semantics (Pitfall 18); cross-link M07 урок 06 (PYTH-09) | _pytest/python_api.py + Lib/builtins.py |
| 5 | mocks-and-monkeypatch | unittest.mock.MagicMock + patch + monkeypatch (Pitfall 17 ScopeMismatch); pytest.MonkeyPatch() session alternative | Lib/unittest/mock.py + pytest 8 monkeypatch |
| 6 | conftest-and-discovery | conftest.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:
@contextmanagerpattern 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
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 reportExpected 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.
Forward-link Phase 69 — pre-commit hook + GitHub Actions
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):
- Matrix testing — pytest runs против 3 Python versions параллельно.
- Coverage gate — PR blocked если coverage drops < 80%.
- External tool integration — Codecov uploads coverage report для PR review.
Это — production hygiene. Tests meaningful только если CI enforces them. Phase 69 даст полное treatment.
Forward-link Phase 70 — final exam + assessment parity
Phase 70 (Launch Polish) — финальная phase milestone v2.4 Python для Data Engineer. Preview:
- Final exam — across all 9 modules (M00-M08) — comprehensive assessment.
- Assessment parity с track-001 patterns — uniform exam shape, passThreshold standardization.
- Course-wide self-assessment checklists — 7-item per module style (modeling Phase 67 Plan 67-02 lesson 07 + this lesson).
- Glossary final review — ~50+ terms across 6+ categories.
После Phase 70 — milestone v2.4 launch ready.
M08 self-assessment checklist
Перед finalize’ом Module 08 — убедитесь, что можете ответить без подсказки:
- ☐ Назовите все 4 file/class/function name patterns, по которым pytest автоматически discover’ит tests. (Answer:
test_*.py,*_test.py,Test*classes без__init__,test_*functions.) - ☐ Объясните, какие 5 scope levels поддерживает
@pytest.fixture(scope=...)— order from narrow to broad. (Answer: function / class / module / package / session.) - ☐ Запишите yield-style fixture с defensive teardown (try/finally guarantee). Объясните cross-link к M06 урок 05
@contextmanager. - ☐ Какая разница между
@pytest.mark.parametrize('a,b', [(1,2)])(один decorator) и stacked@parametrize('a',[1])+@parametrize('b',[2])(два decorators)? - ☐ Pitfall 18: какой regex method использует
pytest.raises(Exc, match='...')—re.matchилиre.search? Какая практическая implication? - ☐ Реализуйте test для
validate_age('abc')raisingInvalidFormatError(ValidationError)— showpytest.raises(InvalidFormatError, match=...)shape (cross-link M07 урок 06 PYTH-09). - ☐ Pitfall 17: почему
monkeypatchfixture нельзя использовать в@pytest.fixture(scope='session')fixture? Какое workaround? - ☐ Назовите 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).
Ключевые выводы
- Module 08 закрыт — 6 уроков покрывают full pytest vocabulary: discovery, fixtures, parametrize, exceptions, mocks, conftest. Production-grade testing toolkit.
- 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. coverage.py— measurement of test thoroughness.coverage run --source=X -m pytest+coverage report+coverage html. Production CI gate:fail_under = 80.pre-commithook — pytest runs before every commit locally. GitHub Actions workflow — pytest + coverage matrix testing across Python versions. Phase 69 даст полный treatment.- Phase 70 — final exam (cross-module) + assessment parity launch milestone v2.4.
- 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 + финальный экзамен курса.