宣布 pytest-test-categories v1.1.0:将 Google 测试哲学引入 Python
Source: Dev.to
大多数测试套件的问题
说实话——以下哪些情况出现在你的代码库中?
- ⏱️ CI 流水线慢,因为测试没有时间预算
- 🎲 由于网络超时、竞争条件或共享状态导致的 flaky 测试
- 🔺 测试金字塔倒置,慢速集成测试过多
- 🚫 单元、集成和系统测试之间没有强制的边界
如果你对其中任何一点点头,那你并不孤单。这些是 Python 项目中最常见的测试反模式。
介绍 pytest-test-categories v1.1.0
一个 pytest 插件,将 Google 在《Software Engineering at Google》中经过实战检验的测试哲学带到 Python。
pip install pytest-test-categories
1. 按规模对测试进行分类,并设定明确的资源约束
import pytest
import requests
@pytest.mark.small
def test_pure_function():
"""Must complete in 0"""
@pytest.mark.large
def test_external_api():
"""Full network access, up to 15 minutes"""
response = requests.get("https://api.example.com")
assert response.ok
@pytest.mark.xlarge
def test_extended_e2e():
"""Full access, up to 15 minutes, for extensive E2E tests"""
# Long‑running end‑to‑end workflow
pass
2. 强制 hermetic(隔离)性
======================================================================
[TC001] Network Violation
======================================================================
Category: SMALL
What happened:
SMALL test attempted network connection to api.example.com:443
To fix this (choose one):
• Mock the network call using responses, httpretty, or respx
• Use dependency injection to provide a fake HTTP client
• Change test category to @pytest.mark.medium
======================================================================
3. 验证你的测试金字塔
======================== Test Size Distribution ========================
Small: 120 tests (80.0%) - Target: 80% ✓
Medium: 22 tests (14.7%) - Target: 15% ✓
Large: 8 tests ( 5.3%) - Target: 5% ✓
========================================================================
没有 “allow network” 逃生口
插件特意没有 @pytest.mark.allow_network 标记。添加此类标记会违背初衷:
# This defeats the entire purpose
@pytest.mark.small
@pytest.mark.allow_network # ❌ This marker doesn't exist!
def test_api():
requests.get("https://api.example.com") # Still flaky!
相反,使用合适的分类:
@pytest.mark.medium # ✓ Honest about what the test does
def test_api():
requests.get("https://api.example.com")
按测试规模划分的资源矩阵
| 资源 | Small | Medium | Large | XLarge |
|---|---|---|---|---|
| 时间 | 1 s | 5 min | 15 min | 15 min |
| 网络 | ❌ 阻止 | 本地主机 ✓ | ✓ 允许 | ✓ 允许 |
| 文件系统 | ❌ 阻止 | ✓ 允许 | ✓ 允许 | ✓ 允许 |
| 数据库 | ❌ 阻止 | ✓ 允许 | ✓ 允许 | ✓ 允许 |
| 子进程 | ❌ 阻止 | ✓ 允许 | ✓ 允许 | ✓ 允许 |
| Sleep | ❌ 阻止 | ✓ 允许 | ✓ 允许 | ✓ 允许 |
小测试中的 Mock
小测试可以使用 mocking 库(如 responses、respx、pytest-mock、pyfakefs、VCR.py),不会触发违规,因为 mock 在库层拦截,阻止了对真实资源的访问:
import pytest
import responses
import requests
@pytest.mark.small
@responses.activate
def test_api_with_mock():
"""This is hermetic – no real network call is made."""
responses.add(
responses.GET,
"https://api.example.com/users",
json={"users": []},
status=200,
)
response = requests.get("https://api.example.com/users")
assert response.json() == {"users": []}
对于小测试中的文件系统操作,使用 pyfakefs 或内存对象(如 io.StringIO / io.BytesIO)。
渐进式强制执行 rollout
不必在第一天就全部严格:
# pyproject.toml
# 第 1 周:发现 – 查看哪些会失败
[tool.pytest.ini_options]
test_categories_enforcement = "off"
# 第 2‑4 周:迁移 – 逐步修复违规
test_categories_enforcement = "warn"
# 第 5 周起:强制 – 违规导致构建失败
test_categories_enforcement = "strict"
并行执行支持
使用 pytest-xdist 完全支持并行运行已分类的测试:
pytest -n auto
分布统计会在各工作进程间正确聚合,计时隔离防止竞争条件。
导出机器可读报告
生成 JSON 报告以供仪表盘和 CI 集成使用:
pytest --test-size-report=json --test-size-report-file=report.json
{
"summary": {
"total_tests": 150,
"distribution": {
"small": { "count": 120, "percentage": 80.0 },
"medium": { "count": 22, "percentage": 14.67 },
"large": { "count": 8, "percentage": 5.33 }
},
"violations": {
"timing": 0,
"hermeticity": {
"network": 0,
"filesystem": 0,
"subprocess": 0,
"database": 0,
"sleep": 0,
"total": 0
}
}
}
}
安装与快速入门
pip install pytest-test-categories
在 pyproject.toml 中启用强制执行:
[tool.pytest.ini_options]
test_categories_enforcement = "warn"
资源
- 📦 PyPI
- 📖 Documentation