-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1660 from hackebrot/parametrize-with-fixtures
Proposal: Parametrize with fixtures
- Loading branch information
Showing
2 changed files
with
151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |