Merge pull request #1660 from hackebrot/parametrize-with-fixtures
Proposal: Parametrize with fixtures
This commit is contained in:
commit
35cd12e4de
|
@ -106,6 +106,9 @@
|
|||
Thanks `@Vogtinator`_ for reporting. Thanks to `@RedBeardCode`_ and
|
||||
`@tomviner`_ for PR.
|
||||
|
||||
* Add proposal to docs for a new feature that enables users to combine multiple
|
||||
fixtures into one. Thanks to `@hpk42`_ and `@hackebrot`_.
|
||||
|
||||
*
|
||||
|
||||
.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
=========================
|
||||
Parametrize with fixtures
|
||||
=========================
|
||||
|
||||
Problem
|
||||
-------
|
||||
|
||||
As a user I have functional tests that I would like to run against various
|
||||
scenarios.
|
||||
|
||||
In this particular example we want to generate a new project based on a
|
||||
cookiecutter template. We want to test default values but also data that
|
||||
emulates user input.
|
||||
|
||||
- use default values
|
||||
|
||||
- emulate user input
|
||||
|
||||
- specify 'author'
|
||||
|
||||
- specify 'project_slug'
|
||||
|
||||
- specify 'author' and 'project_slug'
|
||||
|
||||
This is how a functional test could look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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()
|
||||
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
* By using ``request.getfuncargvalue()`` we rely on actual fixture function
|
||||
execution to know what fixtures are involved, due to it's dynamic nature
|
||||
* More importantly, ``request.getfuncargvalue()`` cannot be combined with
|
||||
parametrized fixtures, such as ``extra_context``
|
||||
* This is very inconvenient if you wish to extend an existing test suite by
|
||||
certain parameters for fixtures that are already used by tests
|
||||
|
||||
pytest version 3.0 reports an error if you try to run above code::
|
||||
|
||||
Failed: The requested fixture has no parameter defined for the current
|
||||
test.
|
||||
|
||||
Requested fixture 'extra_context'
|
||||
|
||||
|
||||
Proposed solution
|
||||
-----------------
|
||||
|
||||
A new function that can be used in modules can be used to dynamically define
|
||||
fixtures from existing ones.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.define_combined_fixture(
|
||||
name='context',
|
||||
fixtures=['default_context', 'extra_context'],
|
||||
)
|
||||
|
||||
The new fixture ``context`` inherits the scope from the used fixtures and yield
|
||||
the following values.
|
||||
|
||||
- ``{}``
|
||||
|
||||
- ``{'author': 'alice'}``
|
||||
|
||||
- ``{'project_slug': 'helloworld'}``
|
||||
|
||||
- ``{'author': 'bob', 'project_slug': 'foobar'}``
|
||||
|
||||
Alternative approach
|
||||
--------------------
|
||||
|
||||
A new helper function named ``fixture_request`` tells pytest to yield all
|
||||
parameters of a fixture.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@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
|
||||
|
||||
The same helper can be used in combination with ``pytest.mark.parametrize``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
@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
|
Loading…
Reference in New Issue