Skip to content

第 11 章 · 配置文件详解

本章目标:搞清 pytest.toml/pytest.ini/pyproject.toml/tox.ini/setup.cfg 的发现顺序与优先级、rootdir 的判定逻辑,以及 addopts/testpaths/python_files 等核心选项的工程化用法。

11.1 五种配置文件与发现顺序

pytest 支持把配置写在多种文件里,按以下顺序查找,取第一个命中的文件(其余全部忽略):

text
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 配置:

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 按算法推导出的项目根目录,用于定位配置文件和相对路径。简化版规则:

  1. 从所有收集到的测试参数的共同祖先目录开始向上找;
  2. 找到第一个含配置文件的目录(pytest.tomlpytest.ini→…顺序),该目录即 rootdir;
  3. 若没有配置文件,则在祖先目录中找 setup.py/setup.cfg/tox.ini/pyproject.toml;仍找不到则回退到共同祖先本身。

运行输出的 header 会显示实际结果:

text
========== test session starts ==========
rootdir: /home/user/myproj
configfile: pyproject.toml

也可以在测试里直接读取这两个信息做自检:

python
# 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 里的 rootdirconfigfile 两行——十有八九是命中了不同的配置文件。也可以用 --rootdir= 显式指定。

11.3 高频选项精讲

addopts:每次运行自动追加的命令行参数,相当于把团队约定固化下来:

toml
[tool.pytest.ini_options]
# -ra 显示除 passed 外所有结果的摘要;--strict-markers 未注册标记直接报错
addopts = ["-ra", "--strict-markers", "--tb=short"]

addopts 与 CI 的冲突

addopts 里的选项无法被"反向取消"。如果本地想跳过某个全局选项,只能用 -o addopts="" 整体覆盖,或把重选项放进 PYTEST_ADDOPTS 环境变量分环境管理。

testpaths:不带参数运行 pytest 时只搜索这些目录,避免误入虚拟环境和第三方库:

toml
testpaths = ["tests"]          # 等价于默认执行 pytest tests/

python_files / python_classes / python_functions:自定义收集命名约定。默认值分别是 test_*.py *_test.pyTest*test*(注意类没有下划线通配,且 Test 开头的类若带 __init__ 不会被收集)。改法示例:

toml
python_files = ["check_*.py", "*_test.py"]
python_functions = ["should_*"]

改完后,下面的非标准命名模块也能被收集:

python
# 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 ...),例如前端资产目录:

toml
norecursedirs = [".git", "node_modules", "*.egg-info"]

minversion / required_plugins:环境防呆,版本不够或插件缺失直接报错而不是诡异失败:

toml
minversion = "9.0"
required_plugins = ["pytest-cov>=7.0", "pytest-xdist"]

11.4 用 -o 临时覆盖与环境分层

任何配置项都可用 -o/--override-ini 在命令行临时覆盖,可多次使用:

bash
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 章会实战这套组合。被测代码同样可以对环境分层写测试:

python
# 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 False

11.5 本章小结

  • 配置文件命中顺序:pytest.tomlpytest.initox.inisetup.cfgpyproject.toml,只取第一个含有效配置段的;官方推荐 toml 类格式并警告慎用 setup.cfg
  • rootdir 由算法推导而非运行目录,排查行为不一致先看 header 的 rootdir/configfile
  • addopts/testpaths/python_*/norecursedirs/minversion 六个选项覆盖 90% 的工程需求;
  • -o name=valuePYTEST_ADDOPTS 提供命令行级和环境级的覆盖通道。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 项目中同时存在 pytest.ini(有效内容)和 pyproject.toml(含 [tool.pytest.ini_options]),pytest 使用哪个?

2. pyproject.toml 中 pytest 配置应写在哪个表里?

3. 运行 pytest 后输出显示 configfile: tox.ini,但你想用根目录的 pyproject.toml,最可能的原因是?

4. 关于 testpaths 选项,说法正确的是?

🛠️ 动手实践

  1. 为你的项目补齐一份 pyproject.toml[tool.pytest.ini_options]:至少包含 addoptstestpathsmarkers 三项,并用 pytest 运行验证 header 中 configfile 正确指向它。
  2. 制造一次"双配置冲突":同时放置有效的 pytest.inipyproject.toml,观察 header 并解释原因,然后删掉 pytest.ini 恢复。
  3. -o 一次性覆盖两个选项运行你的测试套件(例如 -o testpaths=... -o python_files=...),记录输出差异。

下一章盘点 pytest 的插件生态:加载机制、常用插件与"装了就生效"背后的原理:第 12 章