提案:使用夹具进行参数化¶
警告
本文档概述了关于将夹具用作参数化测试或夹具输入的提案。
问题¶
作为用户,我有一些功能测试,希望针对各种场景运行它们。
在这个特定的例子中,我们希望基于 cookiecutter 模板生成一个新项目。我们想测试默认值,但也想测试模拟用户输入的数据。
使用默认值
模拟用户输入
指定“author”
指定“project_slug”
指定“author”和“project_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 产生所有标记为夹具的参数。
注意
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