Pytest: Борьба с Race Conditions в Async-коде
9 / 9100%
Автоматизация: pytest plugin для flaky tests
60 минут
Проектный якорь
- После уроков про интеграции у нас есть список проблемных тестов. Плагин автоматизирует обнаружение медленных и flaky и сохраняет артефакты в CI.
Плагин скорости и стабильности
tests/plugins/stability.py:
import json
import pytest
def pytest_addoption(parser):
parser.addoption("--slow-threshold", action="store", type=float, default=0.5)
parser.addoption("--flaky-cache", action="store", default=None, help="путь к json со списком flaky")
def pytest_configure(config):
config.addinivalue_line("markers", "contract: контрактные тесты API")
config.addinivalue_line("markers", "flaky: нестабильный тест, требует исправления")
config._slow_tests = []
config._suspected_flaky = set()
if cache := config.getoption("--flaky-cache"):
try:
with open(cache) as f:
config._suspected_flaky = set(json.load(f))
except FileNotFoundError:
config._suspected_flaky = set()
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
if item.nodeid in getattr(item.config, "_suspected_flaky", set()):
item.add_marker(pytest.mark.xfail(reason="known flaky"))
def pytest_runtest_logreport(report):
if report.when != "call":
return
cfg = report.config
threshold = cfg.getoption("--slow-threshold")
if report.duration > threshold:
cfg._slow_tests.append((report.nodeid, report.duration))
if report.failed and "flaky" in report.keywords:
cfg._suspected_flaky.add(report.nodeid)
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
slow = sorted(session.config._slow_tests, key=lambda t: t[1], reverse=True)
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
if slow:
reporter.write_line("\n[slow-tests] топ медленных:", red=False, bold=True)
for nodeid, duration in slow[:10]:
reporter.write_line(f"{nodeid} - {duration:.3f}s")
cache_path = session.config.getoption("--flaky-cache")
if cache_path:
with open(cache_path, "w") as f:
json.dump(sorted(session.config._suspected_flaky), f, indent=2)Запуск:
pytest -p tests.plugins.stability --slow-threshold=0.3 --flaky-cache=artifacts/flaky.json- Медленные тесты выводятся в терминал и сохраняются в артефакты CI.
- Flaky помечаются
xfailдо исправления, чтобы не ронять сборку, но не забывать долг.
request.node и метрики
@pytest.fixture(autouse=True)
def mark_contract_tests(request, metrics_client):
if "contract" in request.keywords:
metrics_client.increment("tests.contract")- Маркеры позволяют собирать метрики по типам тестов и навешивать политику запуска (
-m "not contract"для быстрых прогонов).
Когда писать свой плагин
- Нужно единое качество: обязательные таймауты, запрет глобального state, автодобавление маркеров.
- Хотите переиспользовать инфраструктурные фикстуры/правила между несколькими сервисами.
- Требуется расширить CLI опциями команды без копипаста в
conftest.py.
🎉 Congratulations on completing the course!
Share your experience and get a promo code for free access to any premium material of your choice
✨
Free access to any premium material of your choice
🎓
Your experience will help other students
📧
Promo code will arrive via email within 24 hours