良好的集成实践¶
使用 pip 安装包¶
对于开发,我们建议您使用 venv
作为虚拟环境,以及 pip 来安装您的应用程序和任何依赖项,以及 pytest
包本身。这可确保您的代码和依赖项与系统 Python 安装隔离。
在存储库的根目录中创建一个 pyproject.toml
文件,如 打包 Python 项目 中所述。前几行应如下所示
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "PACKAGENAME"
version = "PACKAGEVERSION"
其中 PACKAGENAME
和 PACKAGEVERSION
分别是您的包的名称和版本。
然后,您可以通过从同一目录运行以下命令以“可编辑”模式安装您的包
pip install -e .
这使您可以更改源代码(测试和应用程序)并随意重新运行测试。
Python 测试发现的约定¶
pytest
实现以下标准测试发现
如果没有指定参数,则从
testpaths
(如果已配置)或当前目录开始收集。或者,可以在目录、文件名或节点 ID 的任意组合中使用命令行参数。递归进入目录,除非它们与
norecursedirs
匹配。在这些目录中,搜索
test_*.py
或*_test.py
文件,由其 测试包名称 导入。从这些文件中收集测试项
test
前缀的测试函数或类外的测试方法。test
前缀的测试函数或Test
前缀的测试类中的测试方法(没有__init__
方法)。装饰有@staticmethod
和@classmethods
的方法也被考虑在内。
有关如何自定义测试发现的示例,请参阅 更改标准(Python)测试发现。
在 Python 模块中,pytest
还使用标准 unittest.TestCase 子类化技术来发现测试。
选择测试布局¶
pytest
支持两种常见的测试布局
应用程序代码之外的测试¶
如果你的功能测试很多,或者出于其他原因想将测试与实际的应用程序代码分开(通常是个好主意),那么将测试放入实际应用程序代码外部的额外目录中可能很有用
pyproject.toml
src/
mypkg/
__init__.py
app.py
view.py
tests/
test_app.py
test_view.py
...
这样做有以下好处
在执行
pip install .
之后,你的测试可以针对已安装的版本运行。在执行
pip install --editable .
之后,你的测试可以针对可编辑安装的本地副本运行。
对于新项目,我们建议使用 importlib
导入模式(有关详细说明,请参见 which-import-mode)。为此,将以下内容添加到你的 pyproject.toml
中
[tool.pytest.ini_options]
addopts = [
"--import-mode=importlib",
]
通常情况下,但尤其是在你使用默认导入模式 prepend
时,强烈建议使用 src
布局。在此,你的应用程序根包驻留在根目录的子目录中,即 src/mypkg/
而不是 mypkg
。
此布局避免了很多常见的陷阱,并且有很多好处,Ionel Cristian Mărieș 在这篇优秀的 博客文章 中对此进行了更好的解释。
注意
如果你不使用可编辑安装,并且使用如上所述的 src
布局,则需要扩展 Python 的模块文件搜索路径才能直接针对本地副本执行测试。你可以通过设置 PYTHONPATH
环境变量以临时方式执行此操作
PYTHONPATH=src pytest
或者通过使用 pythonpath
配置变量并向你的 pyproject.toml
中添加以下内容以永久方式执行此操作
[tool.pytest.ini_options]
pythonpath = "src"
注意
如果你不使用可编辑安装,并且不使用 src
布局(mypkg
直接位于根目录中),则你可以依赖 Python 默认将当前目录放入 sys.path
中以导入你的包并运行 python -m pytest
以直接针对本地副本执行测试。
有关调用 pytest
和 python -m pytest
之间差异的更多信息,请参见 调用 pytest 与 python -m pytest。
测试作为应用程序代码的一部分¶
如果你的测试与应用程序模块之间有直接关系,并且想将它们与你的应用程序一起分发,那么将测试目录内联到你的应用程序包中很有用
pyproject.toml
[src/]mypkg/
__init__.py
app.py
view.py
tests/
__init__.py
test_app.py
test_view.py
...
在此方案中,使用 --pyargs
选项运行测试非常容易
pytest --pyargs mypkg
pytest
将发现 mypkg
的安装位置并从中收集测试。
请注意,此布局还可以与上一部分中提到的 src
布局结合使用。
注意
你可以为你的应用程序使用命名空间包 (PEP420),但 pytest 仍会根据 __init__.py
文件的存在执行 测试包名称 发现。如果你使用上面推荐的两个文件系统布局之一,但从你的目录中删除 __init__.py
文件,它应该可以正常工作。但是,对于“内联测试”,你需要使用绝对导入来获取你的应用程序代码。
注意
在 prepend
和 append
导入模式中,如果 pytest 在递归进入文件系统时发现一个 "a/b/test_module.py"
测试文件,它将按如下方式确定导入名称
确定
basedir
:这是第一个不包含__init__.py
的“向上”(朝向根目录)目录。例如,如果a
和b
都包含一个__init__.py
文件,那么a
的父目录将成为basedir
。执行
sys.path.insert(0, basedir)
以使测试模块可以在完全限定的导入名称下导入。import a.b.test_module
其中路径是通过将路径分隔符/
转换为“.”字符来确定的。这意味着你必须遵循目录和文件名直接映射到导入名称的约定。
采用这种有点演化的导入技术的原因是,在较大的项目中,多个测试模块可能会相互导入,因此派生一个规范的导入名称有助于避免意外情况,例如测试模块被导入两次。
使用 --import-mode=importlib
时,事情会变得不那么复杂,因为 pytest 不需要更改 sys.path
或 sys.modules
,从而使事情变得不那么令人惊讶。
选择导入模式¶
出于历史原因,pytest 默认使用 prepend
导入模式,而不是我们为新项目推荐的 importlib
导入模式。原因在于 prepend
模式的工作方式
由于没有可用于派生完整包名称的包,pytest
会将你的测试文件导入为顶级模块。第一个示例(源布局)中的测试文件将通过将 tests/
添加到 sys.path
导入为 test_app
和 test_view
顶级模块。
与导入模式 importlib
相比,这会导致一个缺点:你的测试文件必须具有唯一名称。
如果你需要具有相同名称的测试模块,作为解决方法,你可以向 tests
文件夹及其子文件夹添加 __init__.py
文件,将它们更改为包
pyproject.toml
mypkg/
...
tests/
__init__.py
foo/
__init__.py
test_view.py
bar/
__init__.py
test_view.py
现在,pytest 将模块加载为 tests.foo.test_view
和 tests.bar.test_view
,允许你拥有具有相同名称的模块。但现在,这引入了一个小问题:为了从 tests
目录加载测试模块,pytest 会将存储库的根目录前置到 sys.path
,这会增加副作用,即现在 mypkg
也可导入。
如果你正在使用诸如 tox 的工具在虚拟环境中测试你的包,这是有问题的,因为你希望测试的是包的已安装版本,而不是存储库中的本地代码。
由于导入测试模块时不会更改 sys.path
,importlib
导入模式不存在上述任何缺点。
tox¶
一旦你完成工作并希望确保你的实际包通过所有测试,你可能希望了解 tox,即虚拟环境测试自动化工具。tox
帮助你使用预定义的依赖项设置虚拟环境,然后使用选项执行预配置的测试命令。它将针对已安装的包运行测试,而不是针对你的源代码检出,有助于检测打包故障。
不要通过 setuptools 运行¶
不建议与 setuptools 集成,即你不应使用 python setup.py test
或 pytest-runner
,将来可能停止工作。
由于它依赖于 setuptools 的已弃用功能,并依赖于破坏 pip 中安全机制的功能,因此已被弃用。例如,“setup_requires”和“tests_require”绕过了 pip --require-hashes
。有关更多信息和迁移说明,请参阅 pytest-runner 通知。另请参阅 pypa/setuptools#1684。
setuptools 打算 删除 test 命令。
使用 flake8-pytest-style 检查¶
为了确保在项目中正确使用 pytest,使用 flake8-pytest-style flake8 插件可能会有所帮助。
flake8-pytest-style 检查 pytest 代码中常见的错误和编码风格违规,例如不正确的固定装置、测试函数名称和标记使用。通过使用此插件,您可以在开发过程中尽早发现这些错误,并确保您的 pytest 代码一致且易于维护。
可以在其 PyPI 页面 上找到 flake8-pytest-style 检测到的 lints 列表。
注意
flake8-pytest-style 不是官方 pytest 项目。某些规则强制执行某些风格选择,例如使用 @pytest.fixture()
而不是 @pytest.fixture
,但您可以配置插件以适应您首选的风格。