非常游戏网
Python pytest怎么对大型项目进行目录组织_遵循src与tests分离最佳实践

Python pytest怎么对大型项目进行目录组织_遵循src与tests分离最佳实践

2026-05-25日常编程189770

必须将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.pyintegration_tests.py 就直接跳过。

解决方式只有两个:

  • 老老实实改名:用 test_auth.pytest_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 的不是源码而是缓存或旧包。