pytest 标记:标记整个目录/包

Lov*_*ode 7 python testing pytest

我正在使用 Pytest 测试多个版本的组件。有些测试可以在所有版本上运行,有些是特定于版本的。例如

tests
|
|-- version1_tests
|   |-- test_feature_1_1.py
|   |-- test_feature_1_2.py
|   |-- test_feature_1_n.py
| 
|-- version2_tests
|   |-- test_feature_2_1.py
|   |-- test_feature_2_2.py
|   |-- test_feature_2_n.py
|
|-- common_tests
|   |-- test_feature_common_1.py
|   |-- test_feature_common_2.py
|   |-- test_feature_common_n.py
Run Code Online (Sandbox Code Playgroud)

我想标记我的测试,以便我可以选择是否要从命令行测试版本 1 (version1_tests + common_tests) 或版本 2 (version2_tests + common_tests)。

我目前这样做的方式是针对每个测试模块,我添加一个 pytest 标记,然后从命令行指定标记。例如,在test_feature_1_1.py

import pytest
pytestmark = pytest.mark.version1

class TestSpecificFeature(object):
    ...
Run Code Online (Sandbox Code Playgroud)

然后运行: python -m pytest -m "common and version1"

这工作正常,但我必须手动将标记添加到每个模块,这很乏味,因为实际上有几十个(而不是示例中的 3 个)。

我们曾经使用 Robot Framework,通过在__init__.robot文件中添加标签来“标记”整个文件夹是微不足道的。在 Pytest 中是否有任何等效的方法可以做到这一点,或者是否将每个模块标记为我能做的最好的?

hoe*_*ing 11

您可以在运行时使用item.add_marker()方法将标记注册到收集的测试中。这是在 中注册标记的示例pytest_collection_modifyitems

import pathlib
import pytest


def pytest_collection_modifyitems(config, items):
    # python 3.4/3.5 compat: rootdir = pathlib.Path(str(config.rootdir))
    rootdir = pathlib.Path(config.rootdir)
    for item in items:
        rel_path = pathlib.Path(item.fspath).relative_to(rootdir)
        mark_name = next((part for part in rel_path.parts if part.endswith('_tests')), '').rstrip('_tests')
        if mark_name:
            mark = getattr(pytest.mark, mark_name)
            item.add_marker(mark)
Run Code Online (Sandbox Code Playgroud)

将代码写入conftest.py项目根目录并尝试:

$ pytest -m "common or version2" --collect-only -q
tests/common_tests/test_feature_common_1.py::test_spam
tests/common_tests/test_feature_common_1.py::test_eggs
tests/common_tests/test_feature_common_2.py::test_spam
tests/common_tests/test_feature_common_2.py::test_eggs
tests/common_tests/test_feature_common_n.py::test_spam
tests/common_tests/test_feature_common_n.py::test_eggs
tests/version2_tests/test_feature_2_1.py::test_spam
tests/version2_tests/test_feature_2_1.py::test_eggs
tests/version2_tests/test_feature_2_2.py::test_spam
tests/version2_tests/test_feature_2_2.py::test_eggs
tests/version2_tests/test_feature_2_n.py::test_spam
tests/version2_tests/test_feature_2_n.py::test_eggs
Run Code Online (Sandbox Code Playgroud)

仅选择了common_tests和下的测试version2_tests

解释

对于每个收集到的测试项,我们提取相对于项目根目录的路径(rel_path),rel_path以 结尾的第一部分_tests将用作标记名称提取的来源。例如,collect_tests是标记名称collect等的来源。一旦我们有了标记名称,我们就创建标记(使用,getattr因为我们不能使用属性访问)并通过 附加标记item.add_marker(mark)。您可以编写自己的,不那么抽象的版本,例如

for item in items:
    if `common_tests` in str(item.fspath):
        item.add_marker(pytest.mark.common)
    elif `version1_tests` in str(item.fspath):
        item.add_marker(pytest.mark.version1)
    elif `version2_tests` in str(item.fspath):
        item.add_marker(pytest.mark.version2)
Run Code Online (Sandbox Code Playgroud)

注册标记

使用最新版本的pytest,您应该收到 ,PytestUnknownMarkWarning因为动态生成的标记未注册。查看为解决方案注册标记部分- 您可以在以下位置添加标记名称pytest.ini

[pytest]
markers =
    common
    version1
    version2
Run Code Online (Sandbox Code Playgroud)

或通过pytest_configure钩子动态添加它们,例如

def pytest_configure(config):
    rootdir = pathlib.Path(config.rootdir)
    for dir_ in rootdir.rglob('*_tests'):
        mark_name = dir_.stem.rstrip('_tests')
        config.addinivalue_line('markers', mark_name)
Run Code Online (Sandbox Code Playgroud)