建议:用夹具参数化

警告

本文件概述了使用夹具作为参数化测试或夹具输入的建议。

问题

作为一个用户,我有一些功能测试,我想在不同的场景下运行它们。

在这个特定的示例中,我们希望生成一个基于cookiecutter模板的新项目。我们要测试默认值,但也要测试模拟用户输入的数据。

  • 使用默认值

  • 模拟用户输入

    • 指定“作者”

    • 指定“项目u slug”

    • 指定“author”和“projectu slug”

这就是功能测试的样子:

import pytest


@pytest.fixture
def default_context():
    return {"extra_context": {}}


@pytest.fixture(
    params=[
        {"author": "alice"},
        {"project_slug": "helloworld"},
        {"author": "bob", "project_slug": "foobar"},
    ]
)
def extra_context(request):
    return {"extra_context": request.param}


@pytest.fixture(params=["default", "extra"])
def context(request):
    if request.param == "default":
        return request.getfuncargvalue("default_context")
    else:
        return request.getfuncargvalue("extra_context")


def test_generate_project(cookies, context):
    """Call the cookiecutter API to generate a new project from a
    template.
    """
    result = cookies.bake(extra_context=context)

    assert result.exit_code == 0
    assert result.exception is None
    assert result.project.isdir()

问题

  • 通过使用 request.getfuncargvalue() 由于夹具的动态特性,我们依靠实际的夹具功能执行来了解所涉及的夹具。

  • 更重要的是, request.getfuncargvalue() 不能与参数化设备组合,例如 extra_context

  • 如果您希望通过测试已经使用的设备的某些参数来扩展现有的测试套件,这是非常不方便的。

如果尝试运行以上代码,pytest 3.0版将报告一个错误::

Failed: The requested fixture has no parameter defined for the current
test.

Requested fixture 'extra_context'

建议的解决方案

可以在模块中使用的新功能可用于从现有功能动态定义装置。

pytest.define_combined_fixture(
    name="context", fixtures=["default_context", "extra_context"]
)

新型夹具 context 从使用的设备继承作用域并生成以下值。

  • {}

  • {'author': 'alice'}

  • {'project_slug': 'helloworld'}

  • {'author': 'bob', 'project_slug': 'foobar'}

替代方法

名为 fixture_request 会告诉pytest生成标记为fixture的所有参数。

注解

这个 pytest-lazy-fixture 插件实现了一个与下面的建议非常相似的解决方案,请务必检查它。

@pytest.fixture(
    params=[
        pytest.fixture_request("default_context"),
        pytest.fixture_request("extra_context"),
    ]
)
def context(request):
    """Returns all values for ``default_context``, one-by-one before it
    does the same for ``extra_context``.

    request.param:
        - {}
        - {'author': 'alice'}
        - {'project_slug': 'helloworld'}
        - {'author': 'bob', 'project_slug': 'foobar'}
    """
    return request.param

同一个助手可以与 pytest.mark.parametrize .

@pytest.mark.parametrize(
    "context, expected_response_code",
    [
        (pytest.fixture_request("default_context"), 0),
        (pytest.fixture_request("extra_context"), 0),
    ],
)
def test_generate_project(cookies, context, exit_code):
    """Call the cookiecutter API to generate a new project from a
    template.
    """
    result = cookies.bake(extra_context=context)

    assert result.exit_code == exit_code