第 11 章 · 配置文件详解
本章目标:搞清
pytest.toml/pytest.ini/pyproject.toml/tox.ini/setup.cfg的发现顺序与优先级、rootdir 的判定逻辑,以及addopts/testpaths/python_files等核心选项的工程化用法。
11.1 五种配置文件与发现顺序
pytest 支持把配置写在多种文件里,按以下顺序查找,取第一个命中的文件(其余全部忽略):
pytest.toml → pytest.ini → tox.ini (含 [pytest] 段) → setup.cfg (含 [tool:pytest] 段) → pyproject.toml (含 [tool.pytest.ini_options] 表)注意两个细节:
tox.ini/setup.cfg只有在确实包含 pytest 配置段时才算数;pyproject.toml是最流行的选择——它让整个项目的工具配置(构建、lint、pytest)集中在一处。
官方对 setup.cfg 的警告
官方文档明确不推荐 setup.cfg:.cfg 文件使用的解析器与 ini/toml 不同,容易造成难以排查的问题。新项目请直接用 pyproject.toml。另外从 pytest 9 开始还支持纯 TOML 格式的 pytest.toml。
一个典型的 pyproject.toml 配置:
# content of pyproject.toml
[tool.pytest.ini_options]
addopts = "-ra --strict-markers"
testpaths = ["tests"]
python_files = ["test_*.py"]
markers = [
"slow: 标记运行缓慢的测试",
"smoke: 冒烟测试子集",
]
filterwarnings = [
"error", # 把警告升级为错误
'ignore::DeprecationWarning:pkg_resources', # 再对特定来源豁免
]11.2 rootdir 判定:路径混乱的根源
rootdir 不是"你运行 pytest 时所在的目录",而是 pytest 按算法推导出的项目根目录,用于定位配置文件和相对路径。简化版规则:
- 从所有收集到的测试参数的共同祖先目录开始向上找;
- 找到第一个含配置文件的目录(
pytest.toml→pytest.ini→…顺序),该目录即 rootdir; - 若没有配置文件,则在祖先目录中找
setup.py/setup.cfg/tox.ini/pyproject.toml;仍找不到则回退到共同祖先本身。
运行输出的 header 会显示实际结果:
========== test session starts ==========
rootdir: /home/user/myproj
configfile: pyproject.toml也可以在测试里直接读取这两个信息做自检:
# content of test_config_sanity.py —— 环境自检测试
def test_rootdir_is_project_root(request):
# request.config 提供了 rootpath 与 configfile 信息
assert request.config.rootpath.name == "myproj"
assert str(request.config.rootpath / "pyproject.toml").endswith("pyproject.toml")排查心法
遇到"pytest 在 A 目录能跑、B 目录跑起来行为不同",第一反应就是看 header 里的 rootdir 和 configfile 两行——十有八九是命中了不同的配置文件。也可以用 --rootdir= 显式指定。
11.3 高频选项精讲
addopts:每次运行自动追加的命令行参数,相当于把团队约定固化下来:
[tool.pytest.ini_options]
# -ra 显示除 passed 外所有结果的摘要;--strict-markers 未注册标记直接报错
addopts = ["-ra", "--strict-markers", "--tb=short"]addopts 与 CI 的冲突
addopts 里的选项无法被"反向取消"。如果本地想跳过某个全局选项,只能用 -o addopts="" 整体覆盖,或把重选项放进 PYTEST_ADDOPTS 环境变量分环境管理。
testpaths:不带参数运行 pytest 时只搜索这些目录,避免误入虚拟环境和第三方库:
testpaths = ["tests"] # 等价于默认执行 pytest tests/python_files / python_classes / python_functions:自定义收集命名约定。默认值分别是 test_*.py *_test.py、Test*、test*(注意类没有下划线通配,且 Test 开头的类若带 __init__ 不会被收集)。改法示例:
python_files = ["check_*.py", "*_test.py"]
python_functions = ["should_*"]改完后,下面的非标准命名模块也能被收集:
# content of check_cart.py —— 得益于 python_files 配置而被发现
class TestCart: # 默认 Test* 前缀即可收集
def should_merge_duplicate_items(self): # 得益于 python_functions 配置
cart = Cart()
cart.add("apple")
cart.add("apple")
assert len(cart.items()) == 1 # 合并重复商品
def helper_not_a_test(): # 前缀不匹配,不会被收集
...norecursedirs:递归时跳过的目录模式(默认 .git .tox venv dist build ...),例如前端资产目录:
norecursedirs = [".git", "node_modules", "*.egg-info"]minversion / required_plugins:环境防呆,版本不够或插件缺失直接报错而不是诡异失败:
minversion = "9.0"
required_plugins = ["pytest-cov>=7.0", "pytest-xdist"]11.4 用 -o 临时覆盖与环境分层
任何配置项都可用 -o/--override-ini 在命令行临时覆盖,可多次使用:
pytest -o console_output_style=count -o cache_dir=/tmp/mycache
pytest -o addopts="" # 清空全局 addopts,跑一次"纯净"的 pytest配合环境变量 PYTEST_ADDOPTS 可以做环境分层:仓库里 pyproject.toml 保持最小公共配置,CI 镜像里设置 PYTEST_ADDOPTS="--cov=src --cov-fail-under=90" 注入覆盖率门禁——第 13 章会实战这套组合。被测代码同样可以对环境分层写测试:
# content of test_env_layering.py
import os
def resolve_strictness() -> bool:
"""CI 环境默认开启严格模式,本地默认关闭。"""
return os.environ.get("CI", "") == "true"
def test_ci_mode(monkeypatch):
monkeypatch.setenv("CI", "true")
assert resolve_strictness() is True
def test_local_mode(monkeypatch):
monkeypatch.delenv("CI", raising=False)
assert resolve_strictness() is False11.5 本章小结
- 配置文件命中顺序:
pytest.toml→pytest.ini→tox.ini→setup.cfg→pyproject.toml,只取第一个含有效配置段的;官方推荐 toml 类格式并警告慎用setup.cfg; - rootdir 由算法推导而非运行目录,排查行为不一致先看 header 的
rootdir/configfile; addopts/testpaths/python_*/norecursedirs/minversion六个选项覆盖 90% 的工程需求;-o name=value与PYTEST_ADDOPTS提供命令行级和环境级的覆盖通道。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 项目中同时存在 pytest.ini(有效内容)和 pyproject.toml(含 [tool.pytest.ini_options]),pytest 使用哪个?
2. pyproject.toml 中 pytest 配置应写在哪个表里?
3. 运行 pytest 后输出显示 configfile: tox.ini,但你想用根目录的 pyproject.toml,最可能的原因是?
4. 关于 testpaths 选项,说法正确的是?
🛠️ 动手实践
- 为你的项目补齐一份
pyproject.toml的[tool.pytest.ini_options]:至少包含addopts、testpaths、markers三项,并用pytest运行验证 header 中configfile正确指向它。 - 制造一次"双配置冲突":同时放置有效的
pytest.ini和pyproject.toml,观察 header 并解释原因,然后删掉pytest.ini恢复。 - 用
-o一次性覆盖两个选项运行你的测试套件(例如-o testpaths=... -o python_files=...),记录输出差异。
下一章盘点 pytest 的插件生态:加载机制、常用插件与"装了就生效"背后的原理:第 12 章。