pytest默认递归扫描test_.py和_test.py文件,但测试代码混入src/会导致误打包、IDE分析拖慢、导入污染;必须通过src/与tests/物理隔离、加__init__.py、配置pythonpath=["src"]及规范conftest.py作用域来保障解耦。

pytest 默认递归扫描当前目录下所有 test_<em>.py</em> 和 _test.py 文件,但不区分它们在哪个目录里。如果不分离,测试代码混进 src/ 或 myapp/ 里,会带来几个实际麻烦:
- 安装包时可能误打包测试文件(比如用
setuptools的find_packages()默认包含所有子包) - IDE 或类型检查工具(如 mypy、pyright)会对测试代码做冗余分析,拖慢响应速度
-
pip install -e .开发安装后,import myapp.tests可能意外成功,造成模块污染或导入冲突
所以分离不是“为了好看”,而是为了解耦发布、安装、静态分析和运行时行为。
怎么确保 src/tests 物理隔离且 import 不出错
关键不在“分开放”,而在“彼此不感知”:-
src/下必须有<strong>init</strong>.py,让它成为真正的 Python 包;tests/下也建议加<strong>init</strong>.py(CI 环境有时会 strict 检查包合法性) - 不要在
tests/里写from myapp import ...以外的跨包导入;尤其避免from tests.utils import ...这类反向依赖 - 运行测试时,务必从项目根目录执行
pytest,而不是进入tests/目录再跑 —— 否则 Python 路径可能丢失src/,导致ModuleNotFoundError - 如果用
pyproject.toml,推荐显式配置[tool.pytest.ini_options]中的pythonpath = ["src"],避免靠sys.path注入硬编码路径
conftest.py 放哪?为什么不能全扔根目录
conftest.py 的作用域是**按目录层级向上查找,同名 fixture 会被子目录完全屏蔽**,不是合并。
- 根目录的
conftest.py适用于全局 fixture(如数据库 session、日志配置) -
tests/api/conftest.py只对tests/api/及其子目录生效,可定义api_client这类局部 fixture - 如果你在
tests/api/下写了另一个conftest.py,里面也定义了叫db的 fixture,那它会彻底替代根目录的db,上层测试拿不到原始版本
常见错误:把所有 fixture 堆在根 conftest.py,结果某模块需要 mock 数据库但又不想影响其他用例——这时只能靠 @pytest.mark.usefixtures 或参数化绕开,反而更难维护。
数据文件放哪?读取路径容易崩在哪
测试数据(JSON/YAML/Excel)不能放在tests/ 目录里硬编码路径,因为:
-
pytest执行时工作目录不固定(取决于你从哪启动) - CI 环境常以不同用户权限运行,相对路径容易失效
正确做法:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 把数据统一放在
tests/data/(不是src/!否则会被打包) - 用
pytest-datadir插件 +@pytest.fixture提供shared_datadir,它会自动解析到tests/data的绝对路径 - 或者手写一个 fixture,基于
Path(<strong>file</strong>).parent / "data"获取当前测试文件所在目录的 sibling 路径,比os.getcwd()可靠得多
最常踩的坑是写 open("data/test.json") —— 这个路径只在你 cd 进 tests/ 后才有效,CI 里十次九崩。
复杂点在于路径逻辑和 fixture 作用域的叠加效应,稍不注意就会出现本地能过、CI 报错、或者某个测试突然拿不到预期 fixture 的情况。

















