pytest按文件名(test_.py或_test.py)和conftest.py所在路径层级决定作用域,需分层放置conftest.py、镜像源码结构命名测试文件、避免__init__.py污染,并在pytest.ini中固化标记语义与继承规则。

pytest目录结构要先满足conftest.py的自动发现规则
pytest不是靠目录名识别测试,而是按文件名(test_*.py 或 *_test.py)和conftest.py所在路径层级来决定作用域。这意味着你把conftest.py放在tests/根目录下,它只对同级及子目录里的测试生效;如果放在tests/unit/里,就只影响该目录及更深层的测试。
常见错误是把所有conftest.py堆在tests/根目录,结果fixture复用混乱、跨模块覆盖难调试。实际应按测试粒度分层放置:
-
tests/conftest.py:放全项目通用fixture,比如tmpdir、日志配置、基础mock工具 -
tests/unit/conftest.py:仅unit层用的fixture,如mock_database或fake_request -
tests/integration/conftest.py:启动真实依赖(DB、Redis),用scope="session"避免重复初始化
测试文件命名和位置必须匹配被测代码路径
别用tests/test_user_service.py配src/myapp/services/user.py——这种映射靠人脑维护,CI一加新模块就漏测。正确做法是镜像源码结构:
- 源码路径:
src/myapp/services/user.py→ 测试路径:tests/services/test_user.py - 源码路径:
src/myapp/utils/validators.py→ 测试路径:tests/utils/test_validators.py
这样pytest tests/services/就能精准运行所有service层测试,IDE跳转、Git diff过滤也自然对齐。注意tests/和src/必须同级,否则pytest无法解析相对导入。
立即学习“Python免费学习笔记(深入)”;
避免__init__.py污染测试目录
很多团队在tests/里放__init__.py,以为能让测试模块可导入,结果导致pytest把整个tests当成包扫描,意外执行非测试文件,甚至触发conftest.py重复加载。实际上:
- pytest 7+ 默认忽略
tests/下的__init__.py,但旧版本或自定义python_files配置时会出问题 - 真正需要导入时,用
-p no:python禁用自动包检测,或改用sys.path.insert(0, "src")在conftest.py里手动补路径 - 如果用了poetry或pip install -e .,确保
pyproject.toml里[tool.pytest.ini_options]没配python_paths = ["src"]之外的冗余路径
大型项目务必拆分pytest.ini并约束标记使用
上百个测试后,pytest -m "not slow"这种临时过滤会失效——因为没人统一维护@pytest.mark.slow的定义边界。解决方法是提前约定标记语义,并在配置中固化:
- 在
pytest.ini里声明常用标记:[tool:pytest] markers = unit: fast, no external deps integration: talks to DB/API slow: >1s runtime, skip by default flaky: known unstable, run only on demand - CI中明确指定:
pytest -m "unit and not flaky",本地开发用pytest -m integration - 禁止在测试函数里动态加
@pytest.mark.parametrize以外的标记,所有标记必须出现在函数定义上方紧邻处
最常被忽略的是标记继承问题:子目录里的conftest.py不能自动继承父目录标记定义,每个pytest.ini只作用于其所在目录及子目录,跨子项目必须复制或用addopts = --strict-markers强制校验。


















