将 pytest 与 src 层一起使用

Using pytest with a src layer

pytest recommends 包括一个额外的目录来分隔项目中的源代码:

my_package
├── src  # <-- no __init__.py on this layer
│   └── my_package
│       ├── __init__.py
│       └── util_module
│           ├── __init__.py
│           └── utils.py
└── tests
    ├── __init__.py
    └── test_util_module
        ├── __init__.py
        └── test_utils.py

可悲的是,他们什么也没说[1] 关于测试代码中的导入在这种情况下应该如何工作,这对我的 IDE 在 this naive example[2],但会导致pytest出现以下错误:

my_package $ pytest

====================== test session starts ======================
platform linux -- Python 3.6.4, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /home/user/workspace/my_package, inifile:
collected 0 items / 1 errors     
                                                                                                                                                                      
============================ ERRORS =============================
___ ERROR collecting tests/test_util_module/test_utils.py ___
ImportError while importing test module '/home/user/workspace/my_package/tests/test_util_module/test_utils.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_util_module/test_utils.py:1: in <module>
    from test.test_module.some_file import starify
E   ModuleNotFoundError: No module named 'my_package.util_module'
!!!! Interrupted: 1 errors during collection !!!!!

我可以通过将测试的导入更改为 from src.my_package.util_module.utils import starify 来解决这个问题,但是我的 IDE 抱怨 src 部分是多余的,所以我想别管它了。


[1]:不再是这样了。从版本 3.7.3 开始,pytest 推荐可编辑安装,该安装也包含在 @hoefling 的 good practices.

顶部的回答中。

[2]:设置为 virtualenv env -p python3.6; source env/bin/activate; pip install pytest

针对 pytest>=7 的推荐方法:使用 pythonpath 设置

最近,pytest 添加了一个新的核心插件,支持通过 pythonpath configuration value 进行 sys.path 修改。因此,现在的解决方案简单多了,不再需要任何解决方法:

pyproject.toml 示例:

[tool.pytest.ini_options]
pythonpath = [
  "src"
]

pytest.ini 示例:

[pytest]
pythonpath = src

路径条目是相对于 rootdir 计算的,因此在这种情况下 src 条目将 path/to/project/src 目录添加到 sys.path

也允许多个路径条目:对于布局

repo/
├── src/
|   └── lib.py
├── src2/
|   └── lib2.py
└── tests
    └── test_lib.py

配置

[tool.pytest.ini_options]
pythonpath = [
  "src", "src2",
]

[pytest]
pythonpath = src src2

会将 liblib2 模块添加到 sys.path,因此

import lib
import lib2

两者都可以。

原回答

调整 PYTHONPATH(如评论中所建议)是解决导入问题的一种可能性。另一个是在 src 目录中添加一个空的 conftest.py 文件:

$ touch src/conftest.py

pytest 会将 src 添加到 sys.path。这是欺骗 pytest 将代码库添加到 sys.path.

的简单方法

然而,src 布局通常在您打算构建一个发行版时选择,例如提供 setup.py 并(在本例中)明确指定根包目录:

from setuptools import find_packages, setup


setup(
    ...
    package_dir={'': 'src'},
    packages=find_packages(where='src'),
    ...
)

并在开发模式下安装软件包(通过 python setup.py developpip install --editable .),同时您仍在开发它。这样,您的包 my_package 就会正确地集成到 Python 的站点包结构中,并且不需要 fiddle 和 PYTHONPATH

PYTHONPATH 更新在使用 github 操作时对我不起作用(已知概率)。使用此 pytest-pythonpath 安装和 pytest.ini 文件对我有用:

pip install pytest-pythonpath # accompany with python_path in pytest.ini, so PYTHONPATH is updated with location for modules under test

有了这个,基本的 'pytest' 命令很高兴地找到了子目录中的所有测试,并根据我的 pytest.ini 找到了被测模块(设置为匹配 pycharm 中的源文件夹)

从 PyTest 7.0.0 开始,您现在可以使用 pythonpath 选项在 sys.path 中设置一些默认条目。这是最方便的方法。

pytest.ini中:

[pytest]
pythonpath = src/

pyproject.toml中:

[tool.pytest.ini_options]
pythonpath = "src/"

如果您使用 tox,那么您应该在其设置中 取消设置,这样您就不会不小心测试本地版本,而不是安装的版本。

tox.ini中:

[testenv]
commands = pytest ... -o pythonpath=

查看文档:https://docs.pytest.org/en/7.1.x/reference/reference.html#confval-pythonpath