Removing clutter from the step_func

This commit is contained in:
Alessio Bogon 2022-07-07 16:30:25 +02:00
parent 533b5cd2f0
commit 6e1566bb68
4 changed files with 34 additions and 67 deletions

View File

@ -22,7 +22,7 @@ from _pytest.fixtures import FixtureLookupError, FixtureManager, FixtureRequest,
from . import exceptions
from .feature import get_feature, get_features
from .steps import get_step_fixture_name, inject_fixture
from .steps import StepFuncContext, get_step_fixture_name, inject_fixture
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
if TYPE_CHECKING:
@ -43,23 +43,22 @@ def find_argumented_step_fixture_name(
# happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
for fixturename, fixturedefs in list(fixturemanager._arg2fixturedefs.items()):
for fixturedef in fixturedefs:
parsers = getattr(fixturedef.func, "_pytest_bdd_parsers", [])
step_func = getattr(fixturedef.func, "_pytest_bdd_wrapped_step_func", None)
if step_func is None:
continue
# TODO: See if we can avoid this:
parsers = getattr(step_func, "_pytest_bdd_parsers", [])
for parser in parsers:
match = parser.is_matching(name)
if not match:
continue
parser_name = get_step_fixture_name(parser.name, type_)
if request:
try:
request.getfixturevalue(parser_name)
except FixtureLookupError:
continue
return parser_name
return None
def _find_step_function(request: FixtureRequest, step: Step, scenario: Scenario) -> Any:
def _find_step_function(request: FixtureRequest, step: Step, scenario: Scenario) -> StepFuncContext:
"""Match the step defined by the regular expression pattern.
:param request: PyTest request object.
@ -87,7 +86,9 @@ def _find_step_function(request: FixtureRequest, step: Step, scenario: Scenario)
) from e2
def _execute_step_function(request: FixtureRequest, scenario: Scenario, step: Step, step_func: Callable) -> None:
def _execute_step_function(
request: FixtureRequest, scenario: Scenario, step: Step, step_func_context: StepFuncContext
) -> None:
"""Execute step function.
:param request: PyTest request.
@ -96,28 +97,25 @@ def _execute_step_function(request: FixtureRequest, scenario: Scenario, step: St
:param function step_func: Step function.
:param example: Example table.
"""
step_func = step_func_context.func
kw = dict(request=request, feature=scenario.feature, scenario=scenario, step=step, step_func=step_func)
request.config.hook.pytest_bdd_before_step(**kw)
kw["step_func_args"] = {}
try:
# Get the step argument values.
converters = getattr(step_func, "converters", {})
kwargs = {}
parsers = getattr(step_func, "_pytest_bdd_parsers", [])
parser = step_func_context.parser
for arg, value in parser.parse_arguments(step.name).items():
if arg in converters:
value = converters[arg](value)
kwargs[arg] = value
for parser in parsers:
if not parser.is_matching(step.name):
continue
for arg, value in parser.parse_arguments(step.name).items():
if arg in converters:
value = converters[arg](value)
kwargs[arg] = value
break
kwargs = {arg: kwargs[arg] if arg in kwargs else request.getfixturevalue(arg) for arg in get_args(step_func)}
args = get_args(step_func)
kwargs = {arg: kwargs[arg] if arg in kwargs else request.getfixturevalue(arg) for arg in args}
kw["step_func_args"] = kwargs
request.config.hook.pytest_bdd_before_step_call(**kw)

View File

@ -37,11 +37,12 @@ def given_beautiful_article(article):
from __future__ import annotations
import typing
from dataclasses import dataclass
import pytest
from _pytest.fixtures import FixtureDef, FixtureRequest
from .parsers import get_parser
from .parsers import StepParser, get_parser
from .types import GIVEN, THEN, WHEN
from .utils import get_caller_module_locals, setdefault
@ -103,6 +104,12 @@ def then(name: Any, converters: dict[str, Callable] | None = None, target_fixtur
return _step_decorator(THEN, name, converters=converters, target_fixture=target_fixture)
@dataclass
class StepFuncContext:
func: Callable[..., Any]
parser: StepParser
def _step_decorator(
step_type: str,
step_name: Any,
@ -126,19 +133,12 @@ def _step_decorator(
# TODO: Try to not attach to both step_func and lazy_step_func
step_func.__name__ = str(parsed_step_name)
def lazy_step_func() -> StepFuncContext:
return StepFuncContext(func=step_func, parser=parser_instance)
def lazy_step_func() -> Callable:
return step_func
step_func.step_type = step_type
lazy_step_func.step_type = step_type
# Preserve the docstring
lazy_step_func.__doc__ = func.__doc__
lazy_step_func._pytest_bdd_wrapped_step_func = step_func
setdefault(step_func, "_pytest_bdd_parsers", []).append(parser_instance)
setdefault(lazy_step_func, "_pytest_bdd_parsers", []).append(parser_instance)
if converters:
step_func.converters = lazy_step_func.converters = converters

View File

@ -219,13 +219,13 @@ def test_local(testdir):
fixture = request.getfixturevalue(
get_step_fixture_name("I have a parent fixture", GIVEN)
)
).func
assert fixture() == "local"
fixture = request.getfixturevalue(
get_step_fixture_name("I have an overridable fixture", GIVEN)
)
).func
assert fixture() == "local"
"""

View File

@ -2,8 +2,6 @@
import textwrap
import pytest
def test_when_then(testdir):
"""Test when and then steps are callable functions.
@ -30,42 +28,13 @@ def test_when_then(testdir):
def test_when_then(request):
do_stuff_ = request.getfixturevalue(get_step_fixture_name("I do stuff", WHEN))
assert callable(do_stuff_)
assert callable(do_stuff_.func)
check_stuff_ = request.getfixturevalue(get_step_fixture_name("I check stuff", THEN))
assert callable(check_stuff_)
assert callable(check_stuff_.func)
"""
)
)
result = testdir.runpytest()
result.assert_outcomes(passed=1)
@pytest.mark.parametrize(
("step", "keyword"),
[("given", "Given"), ("when", "When"), ("then", "Then")],
)
def test_preserve_decorator(testdir, step, keyword):
"""Check that we preserve original function attributes after decorating it."""
testdir.makepyfile(
textwrap.dedent(
'''\
from pytest_bdd import {step}
from pytest_bdd.steps import get_step_fixture_name
@{step}("{keyword}")
def func():
"""Doc string."""
def test_decorator():
assert globals()[get_step_fixture_name("{keyword}", {step}.__name__)].__doc__ == "Doc string."
'''.format(
step=step, keyword=keyword
)
)
)
result = testdir.runpytest()
result.assert_outcomes(passed=1)