从 pip 转移到 poetry,现在 pytest-cov 不会收集覆盖率数据

Moved from pip to poetry and now pytest-cov won't collect coverage data

我最近开始使用 poetry 来管理项目依赖关系, 而不是使用 requirements.txttest-requirements.txtpip.

进行更改后,我无法进行覆盖测试 正确。在这两种情况下,我都使用 tox 来驱动测试(而且我 安装了 tox-poetry 扩展。

我的 tox.ini 目前是这样的:

[tox]
isolated_build = True
envlist = pep8,unit

[testenv]
whitelist_externals = poetry

[testenv:venv]
commands = {posargs}

[testenv:pep8]
commands =
    poetry run flake8 {posargs:symtool}

[testenv:unit]
commands =
    poetry run pytest --cov=symtool {posargs} tests/unit

以前是这样的:

[tox]
envlist = pep8,unit

[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
deps = -r{toxinidir}/requirements.txt
    -r{toxinidir}/test-requirements.txt

[testenv:venv]
commands = {posargs}

[testenv:pep8]
commands =
    flake8 {posargs:symtool}

[testenv:unit]
commands =
    pytest --cov=symtool {posargs} tests/unit

自从更改为 poetry 后,当我 运行 例如tox -e unit,我看到了:

unit run-test: commands[0] | poetry run pytest --cov=symtool tests/unit
===================================== test session starts =====================================
platform linux -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
cachedir: .tox/unit/.pytest_cache
rootdir: /home/lars/projects/symtool, configfile: tox.ini
plugins: cov-2.11.1
collected 14 items

tests/unit/test_disasm.py .....                                                         [ 35%]
tests/unit/test_symtool.py .........                                                    [100%]r
Coverage.py warning: No data was collected. (no-data-collected)

我正在尝试解决 no-data-collected 问题。根据 pytest --help--cov参数设置路径包 姓名:

 --cov=[SOURCE]        Path or package name to measure during execution

存储库的根目录(在上面的输出中是 rootdir 来自 tox) 看起来像这样:

asm
pyproject.toml
README.md
reference
symtool
tests
tox.ini

肯定有一个 symtool 目录包含这个包。 即使测试以某种方式不在项目根目录中 运行ning 目录, symtool 包安装在测试环境中, 单元测试实际上通过了这一事实(所有 其中包括 import symtool).

的一些变体

如何让保险恢复正常?

如果您使用诗歌,您可能也喜欢在 tox 中使用 pytest、flake8 等的锁定依赖项。这可以通过 tox-poetry-installer.

来实现

为了确保您的测试 运行 是针对构建和安装的包而不是本地文件,您必须为 pytest 使用 --import-mode 标志并将其设置为 importlib

要使用 pytest-cov 测量覆盖率,您必须指向 {envsitepackagesdir}

总而言之,您的 tox.ini 可能如下所示:

[tox]
isolated_build = true
requires =
    tox-poetry-installer[poetry] == 0.6.0
envlist = py39

[testenv]
locked_deps =
    pytest
    pytest-cov
commands = pytest --cov {envsitepackagesdir}/mypackage --import-mode=importlib

--import-mode在做什么?

为了运行测试,pytest需要导入测试模块。传统上,这是通过将发现的测试文件夹所在的根文件夹的路径添加到 sys.path 来完成的。通常测试文件夹和包文件夹共享同一个根文件夹。所以添加到 sys.path 的副作用是,测试 运行 针对包文件夹而不是安装的包,因为 python 首先找到这个文件夹。

可以建议 pytest 将发现的根文件夹附加到它,而不是添加到 sys.path 之前。这样做 python 将在尝试导入时首先查看 site-packages 文件夹。因此可以针对已安装的软件包进行测试。

操纵 sys.path 几乎总是一个坏主意,因为它会导致不必要的副作用。 pytestdocs描述一个:

Same as prepend, requires test module names to be unique when the test directory tree is not arranged in packages, because the modules will put in sys.modules after importing.

importlib 的第三个选项是在 pytest 6 中引入的。它使用 python 的内置 importlib 来动态加载测试模块而无需操作 sys.path .他们计划在未来的版本中将此选项设为默认选项。

将我的评论变成答案:

这是 pytest-covtox 的老问题(首次报告于 issue #38)。 tox 将您的项目安装为第三方包,pytest 将从站点导入它(例如,如果作业名为 unit,则从 .tox/unit/lib/python3.X/site-packages 导入),而 --cov=symtool 指示 pytest-cov 收集项目根目录中 symtool 目录的覆盖率。

一种解决方案是切换到 src 布局:

├── pyproject.toml
├── README.md
├── reference
├── src
|   └── symtool
├── tests
└── tox.ini

在您的 pyproject.toml 中,您需要将 poetry 指向源目录:

[tool.poetry]
...
packages = [
    { include = "symtool", from = "src" },
]

现在 src 将阻止从源代码树导入代码,因此 --cov=symtool 将收集已安装模块的覆盖率,这是唯一的选择。对于开发过程的其余部分,您不应该遇到麻烦,因为 poetry install 以可编辑模式安装项目,因此其余部分应该可以正常工作。

另一种选择是使用 tox 跳过包安装,而是以可编辑模式安装。放置在 tox.ini 中的示例片段:

[testenv]
whitelist_externals =
    poetry
skip_install = true
commands_pre =
    poetry install
commands =
    poetry run pytest ...

虽然这几乎杀死了对已安装模块的测试(您停止明确测试您的项目是否可以安装在空白 venv 中,而是使用源代码树中的内容),所以我会选择 src 布局选项。