pytest + src 布局通过强制源码位于 src/ 下并配合 pyproject.toml 中 build-system、project.packages 和 pytest 配置,使模块导入路径唯一确定,彻底解决因当前目录被误判为包导致的导入冲突与 mock 失败问题。

为什么 pytest + src 布局能解决导入冲突
根本原因是 Python 的模块查找机制:当项目根目录被加入 sys.path(比如用 python -m pytest 启动),而你的模块又直接放在根下(如 myproject/main.py),pytest 会把根目录当成包来 import,导致 import myproject 和 from myproject import xxx 行为不一致——有时导入的是本地文件,有时是已安装的同名包,甚至报 ImportError: attempted relative import with no known parent package。
src 布局强制让所有源码只从 src/ 下发源,配合 pyproject.toml 中的 [build-system] 配置,pip 安装时自动把 src/ 加入路径,pytest 也能统一按包路径解析,彻底切断“当前目录即包”的歧义。
src 目录下怎么组织测试才能镜像又不冗余
tests/ 必须与 src/ 平级,且结构严格镜像,但不是复制粘贴。关键在“映射关系可预测”:
-
src/myapp/api/v1/endpoints.py→tests/test_api/test_v1/test_endpoints.py -
src/myapp/shared/utils.py→tests/test_shared/test_utils.py - 测试文件名统一加
test_前缀,模块内函数用def test_*命名,pytest 才能自动发现 - 不要在
tests/下建__init__.py——它不是包,只是 pytest 的扫描路径;加了反而可能触发意外 import
pyproject.toml 里哪些配置决定 pytest 能否正确识别 src
缺一不可的三项:
立即学习“Python免费学习笔记(深入)”;
-
[build-system]必须指定构建后端,例如requires = ["hatchling"],且build-backend = "hatchling.build";setuptools 也行,但 hatchling 对 src 支持更干净 -
[project]中的name和packages要匹配,推荐用packages = [{include = "myapp", from = "src"}](把src/myapp/当作安装包) -
[tool.pytest.ini_options]显式设testpaths = ["tests"]和python_files = ["test_*.py"],避免 pytest 错扫根目录下的临时脚本
漏掉 packages 配置,pip install -e . 就不会把 src/myapp/ 注册进 site-packages,测试里 from myapp import ... 仍会失败。
常见错误:测试里 mock 失败或找不到模块
90% 是路径错位导致的 patch 目标不对。比如你写:
from unittest.mock import patch
@patch("myapp.services.db.get_session")
def test_something(mock_session):
...
实际运行时却提示 ModuleNotFoundError: No module named 'myapp',说明 pytest 根本没把 myapp 当成可导入包——这时别急着改 patch 路径,先检查:
- 是否执行过
pip install -e .(不是pip install .) -
python -c "import myapp; print(myapp.__file__)"是否能成功输出路径?如果报错,说明安装未生效 - 测试文件里
import myapp是否报错?如果报错,patch 再准也没用 - mock 路径必须是“运行时 import 的路径”,不是文件物理路径。例如
src/myapp/services/db.py里的函数,在测试中应 patch"myapp.services.db.get_session",而非"db.get_session"
最隐蔽的坑是 IDE 缓存:VS Code 或 PyCharm 可能沿用旧的 Python 解释器环境,删掉 .venv 重装、重启 IDE,比调半天 patch 更快。


















