良好的集成实践

Good Integration Practices

使用 pip 安装软件包

Install package with pip

对于开发,我们建议您使用 venv 来创建虚拟环境,并使用 pip 来安装您的应用程序及其任何依赖项,以及 pytest 包本身。这确保了您的代码和依赖项与系统 Python 安装隔离。

在您的代码库根目录下创建一个 pyproject.toml 文件,具体如 Packaging Python Projects 中所述。前几行应如下所示:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "PACKAGENAME"
version = "PACKAGEVERSION"

其中 PACKAGENAMEPACKAGEVERSION 分别是您包的名称和版本。

然后,您可以通过从同一目录运行以下命令以 “editable” 模式安装您的包:

pip install -e .

这让您可以随意更改源代码(包括测试和应用程序),并重新运行测试。

Python 测试发现的约定

Conventions for Python test discovery

pytest 实现了以下标准测试发现流程:

  • 如果未指定任何参数,则从 testpaths 开始收集(如果已配置),或者从当前目录开始。或者,可以使用任意组合的目录、文件名或节点 ID 作为命令行参数。

  • 递归进入目录,除非它们与 norecursedirs 匹配。

  • 在这些目录中,搜索 test_*.py*_test.py 文件,按其 test package name 导入。

  • 从这些文件中收集测试项:

  • test 为前缀的测试函数或类外的方法。

  • test 为前缀的测试函数或 Test 前缀测试类中的方法(不带 __init__ 方法)。使用 @staticmethod@classmethod 装饰的方法也被考虑在内。

有关如何自定义测试发现的示例,请参见 改变标准 (Python) 测试的发现机制

在 Python 模块中,pytest 还使用标准的 unittest.TestCase 子类化技术发现测试。

选择测试布局

Choosing a test layout

pytest 支持两种常见的测试布局:

在应用程序代码之外进行测试

Tests outside application code

将测试放置在实际应用代码之外的额外目录中可能很有用,特别是当您有许多功能测试或出于其他原因希望将测试与实际应用代码分开时(这通常是个好主意):

pyproject.toml
src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    test_app.py
    test_view.py
    ...

这有以下好处:

  • 在执行 pip install . 后,您的测试可以针对已安装的版本运行。

  • 在执行 pip install --editable . 后,您的测试可以针对本地副本进行可编辑安装。

对于新项目,我们建议使用 importlib import mode (有关详细解释,请参见 which-import-mode)。 为此,将以下内容添加到您的 pyproject.toml 中:

[tool.pytest.ini_options]
addopts = [
    "--import-mode=importlib",
]

通常,特别是如果您使用默认的导入模式 prepend**强烈**建议使用 src 布局。 在此布局中,您的应用程序根包位于根目录的子目录中,即 src/mypkg/ 而不是 mypkg

这种布局可以避免许多常见的陷阱,并且有许多好处,Ionel Cristian Mărieș 在这篇优秀的 blog post 中对此进行了更好的解释。

Note

如果您不使用可编辑安装并使用上述 src 布局,则需要扩展 Python 的模块文件搜索路径,以便直接对本地副本执行测试。您可以通过设置 PYTHONPATH 环境变量以临时方式实现:


PYTHONPATH=src pytest

或通过使用 pythonpath 配置变量并将以下内容添加到您的 pyproject.toml 中,以永久方式实现:

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

Note

如果您不使用可编辑安装且不使用 src 布局(mypkg 直接位于根目录中),则可以依赖于 Python 默认将当前目录放入 sys.path 中来导入您的包,并运行 python -m pytest 直接对本地副本执行测试。

有关调用 pytestpython -m pytest 之间差异的更多信息,请参见 调用 pytest 与 python -m pytest

作为应用程序代码的一部分进行测试

Tests as part of application code

将测试目录内联到您的应用程序包中是有用的,特别是当您有测试与应用程序模块之间的直接关系,并希望将它们与应用程序一起分发时:

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 布局结合使用。

Note

您可以为您的应用程序使用命名空间包(PEP420),但 pytest 仍将根据 __init__.py 文件的存在执行 test package name 发现。如果您使用上述两种推荐的文件系统布局之一,但将 __init__.py 文件从目录中省略,它应该可以正常工作。然而,对于“内联测试”,您需要使用绝对导入来访问应用程序代码。

Note

prependappend 导入模式中,如果 pytest 在递归文件系统时找到 "a/b/test_module.py" 测试文件,它将按以下方式确定导入名称:

  • 确定 basedir:这是第一个不包含 __init__.py 的“向上”目录(朝向根目录)。例如,如果 ab 都包含 __init__.py 文件,则 a 的父目录将成为 basedir

  • 执行 sys.path.insert(0, basedir) 以使测试模块可以通过完全限定的导入名称导入。

  • import a.b.test_module,路径通过将路径分隔符 / 转换为 “.” 字符来确定。这意味着您必须遵循目录和文件名称直接映射到导入名称的约定。

采用这种稍微复杂的导入技术的原因是,在较大的项目中,多个测试模块可能相互导入,因此推导出一个标准导入名称有助于避免意外情况,例如测试模块被导入两次。

使用 --import-mode=importlib,事情就简单多了,因为 pytest 不需要更改 sys.pathsys.modules,从而使事情变得不那么令人惊讶。

选择导入模式

Choosing an import mode

出于历史原因,pytest 默认使用 prepend import mode,而不是我们为新项目推荐的 importlib 导入模式。原因在于 prepend 模式的工作方式:

由于没有包可用来推导完整的包名,pytest 将把您的测试文件作为 顶级 模块导入。在第一个示例中(src layout),测试文件将作为 test_apptest_view 顶级模块导入,通过将 tests/ 添加到 sys.path

这导致与 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_viewtests.bar.test_view,允许您拥有相同名称的模块。但这会引入一个微妙的问题:为了从 tests 目录加载测试模块,pytest 会将仓库根目录添加到 sys.path,这会带来一个副作用,即 mypkg 也变得可以导入。

如果您使用像 tox 这样的工具在虚拟环境中测试您的包,这将是一个问题,因为您希望测试的是您包的 已安装 版本,而不是来自仓库的本地代码。

importlib 导入模式没有上述缺点,因为在导入测试模块时不会更改 sys.path

tox

一旦您完成工作并想确保您的实际包通过所有测试,您可能想要了解 tox,这是一个虚拟环境测试自动化工具。 tox 帮助您设置带有预定义依赖项的虚拟环境,然后执行带有选项的预配置测试命令。它将针对已安装的包运行测试,而不是针对您的源代码检出,从而帮助检测打包中的问题。

不要通过 setuptools 运行

Do not run via setuptools

与 setuptools 的集成 不推荐,即您不应使用 python setup.py testpytest-runner,并且未来可能会停止工作。

这是不推荐使用的,因为它依赖于 setuptools 的已弃用功能,并依赖于破坏 pip 安全机制的功能。例如,’setup_requires’ 和 ‘tests_require’ 绕过了 pip --require-hashes。有关更多信息和迁移说明,请参见 pytest-runner notice。另请参见 pypa/setuptools#1684

setuptools 打算 移除测试命令

使用 flake8-pytest-style 进行检查

Checking with flake8-pytest-style

为了确保在您的项目中正确使用 pytest,使用 flake8-pytest-style flake8 插件是非常有帮助的。

flake8-pytest-style 检查 pytest 代码中的常见错误和编码风格违规,例如不正确使用 fixtures、测试函数名称和标记。通过使用此插件,您可以在开发过程中及早捕获这些错误,并确保您的 pytest 代码一致且易于维护。

在其 PyPI 页面 上可以找到 flake8-pytest-style 检测到的 lint 列表。

Note

flake8-pytest-style 不是官方的 pytest 项目。某些规则强制执行特定的风格选择,例如使用 @pytest.fixture() 而不是 @pytest.fixture,但您可以配置插件以适应您喜欢的风格。