必须将tests/与src/置于项目根目录平级,否则会导致import失败、路径混乱及CI崩溃;因pytest默认仅将当前目录加入sys.path,src不在其中则无法解析myapp模块,且易误导入已安装旧包而非正在开发的源码。
必须把 tests/ 和 src/ 放在项目根目录下平级,否则 import 会错、路径会乱、CI 会挂。
为什么不能把 tests 塞进 src 里
常见错误是建出 src/myapp/tests/ 这种结构。后果很直接:pytest 跑的时候默认把当前目录加进 sys.path,但 src/ 不在路径里 —— 所以 import myapp 就报 ModuleNotFoundError: No module named 'myapp'。
更隐蔽的问题是:如果本地装过这个包(比如 pip install -e .),测试可能阴差阳错导入了已安装的旧版本,而不是你正在改的源码,导致“改了代码但测试不生效”。
- 根本原因不是 pytest 不懂路径,而是 Python 导入机制只认
sys.path里的位置 -
pyproject.toml里设pythonpath = ["src"]只对 pytest 本身生效,不影响被测模块的 import 行为 - 哪怕用
setup.py配了packages,只要没显式声明package_dir={"": "src"},安装后路径照样错
标准目录结构长什么样
推荐结构(所有路径都基于项目根目录):
myproject/
├── pyproject.toml
├── src/
│ └── myapp/
│ ├── __init__.py
│ ├── orders/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── services.py
│ └── auth/
│ └── ...
└── tests/
├── __init__.py
├── test_orders.py
└── orders/
├── __init__.py
├── test_models.py
└── test_services.py
关键点:
-
tests/目录结构尽量镜像src/myapp/,比如src/myapp/orders/services.py对应tests/orders/test_services.py -
tests/和src/下都要有__init__.py,确保它们是合法的 Python 包(尤其 CI 环境有时会 strict 检查) - 不要在
tests/里放业务逻辑或脚本,它只负责验证;临时调试脚本统一扔进scripts/目录
conftest.py 怎么分层写才不互相覆盖
conftest.py 的作用域严格按目录层级向上查找,且**同名 fixture 会被子目录的完全屏蔽**,不会合并或报错。
例如:
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
-
tests/conftest.py定义了@pytest.fixture def db_engine() -
tests/integration/conftest.py也定义了同名db_engine→ 所有tests/integration/下的测试都会用这个,父级的被忽略 -
tests/unit/conftest.py没定义 → 它会回退到tests/conftest.py的版本
安全做法:
- 全局 fixture(如数据库引擎、日志配置)只放在
tests/conftest.py,别用autouse=True,除非真要影响全部测试 - 模块级 fixture(如
orders_api_client)放在对应子目录,比如tests/orders/conftest.py - 想跨目录复用 fixture?显式导入再声明,别依赖自动发现:
from tests.conftest import db_engine,然后在子conftest.py里@pytest.fixture包一层
pytest 发现不了你的 test_*.py 文件
默认规则很死:pytest 只扫描文件名匹配 test_*.py 或 *_test.py 的 Python 文件。写成 check_auth.py 或 integration_tests.py 就直接跳过。
解决方式只有两个:
- 老老实实改名:用
test_auth.py、test_orders_integration.py—— 团队协作时最省心 - 非要用自定义名?在
pyproject.toml显式配置:[tool.pytest.ini_options] python_files = ["check_*.py", "validate_*.py"]
但得同步告诉所有人,CI 配置也要加这一段,漏一处就失效
函数名同理:def test_create_order(): 可以跑,def verify_order_flow(): 不会执行 —— 没警告,静默跳过。
最常被忽略的是 __init__.py 缺失和 pyproject.toml 里没配 pythonpath。这两处一错,整个测试结构就浮在半空,看着能跑,实际 import 的不是源码而是缓存或旧包。