165 lines
4.2 KiB
ReStructuredText
165 lines
4.2 KiB
ReStructuredText
:orphan:
|
|
|
|
===================================
|
|
PROPOSAL: Parametrize with fixtures
|
|
===================================
|
|
|
|
.. warning::
|
|
|
|
This document outlines a proposal around using fixtures as input
|
|
of parametrized tests or 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`` would tell pytest to yield
|
|
all parameters marked as a fixture.
|
|
|
|
.. note::
|
|
|
|
The `pytest-lazy-fixture <https://pypi.org/project/pytest-lazy-fixture/>`_ plugin implements a very
|
|
similar solution to the proposal below, make sure to check it out.
|
|
|
|
.. 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
|