forked from test_framework/pytest-bdd
Merge pull request #530 from pytest-dev/fix-parsed-step-aliases
Handle multiple parsers connected to a step function
This commit is contained in:
commit
7393b46137
25
CHANGES.rst
25
CHANGES.rst
|
@ -1,21 +1,26 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
6.0.1
|
||||
-----
|
||||
- Fix regression introduced in 6.0.0 where a step function decorated multiple using a parsers times would not be executed correctly. `#530 <https://github.com/pytest-dev/pytest-bdd/pull/530>`_ `#528 <https://github.com/pytest-dev/pytest-bdd/issues/528>`_
|
||||
|
||||
|
||||
6.0.0
|
||||
-----
|
||||
|
||||
This release introduces breaking changes in order to be more in line with the official gherkin specification.
|
||||
|
||||
- Cleanup of the documentation and tests related to parametrization (elchupanebrej) https://github.com/pytest-dev/pytest-bdd/pull/469
|
||||
- Removed feature level examples for the gherkin compatibility (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/490
|
||||
- Removed vertical examples for the gherkin compatibility (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/492
|
||||
- Step arguments are no longer fixtures (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/493
|
||||
- Drop support of python 3.6, pytest 4 (elchupanebrej) https://github.com/pytest-dev/pytest-bdd/pull/495 https://github.com/pytest-dev/pytest-bdd/pull/504
|
||||
- Step definitions can have "yield" statements again (4.0 release broke it). They will be executed as normal fixtures: code after the yield is executed during teardown of the test. (youtux) https://github.com/pytest-dev/pytest-bdd/pull/503
|
||||
- Scenario outlines unused example parameter validation is removed (olegpidsadnyi) https://github.com/pytest-dev/pytest-bdd/pull/499
|
||||
- Add type annotations (youtux) https://github.com/pytest-dev/pytest-bdd/pull/505
|
||||
- ``pytest_bdd.parsers.StepParser`` now is an Abstract Base Class. Subclasses must make sure to implement the abstract methods. (youtux) https://github.com/pytest-dev/pytest-bdd/pull/505
|
||||
- Angular brackets in step definitions are only parsed in "Scenario Outline" (previously they were parsed also in normal "Scenario"s) (youtux) https://github.com/pytest-dev/pytest-bdd/pull/524.
|
||||
- Cleanup of the documentation and tests related to parametrization (elchupanebrej) `#469 <https://github.com/pytest-dev/pytest-bdd/pull/469>`_
|
||||
- Removed feature level examples for the gherkin compatibility (olegpidsadnyi) `#490 <https://github.com/pytest-dev/pytest-bdd/pull/490>`_
|
||||
- Removed vertical examples for the gherkin compatibility (olegpidsadnyi) `#492 <https://github.com/pytest-dev/pytest-bdd/pull/492>`_
|
||||
- Step arguments are no longer fixtures (olegpidsadnyi) `#493 <https://github.com/pytest-dev/pytest-bdd/pull/493>`_
|
||||
- Drop support of python 3.6, pytest 4 (elchupanebrej) `#495 <https://github.com/pytest-dev/pytest-bdd/pull/495>`_ `#504 <https://github.com/pytest-dev/pytest-bdd/issues/504>`_
|
||||
- Step definitions can have "yield" statements again (4.0 release broke it). They will be executed as normal fixtures: code after the yield is executed during teardown of the test. (youtux) `#503 <https://github.com/pytest-dev/pytest-bdd/issues/503>`_
|
||||
- Scenario outlines unused example parameter validation is removed (olegpidsadnyi) `#499 <https://github.com/pytest-dev/pytest-bdd/pull/499>`_
|
||||
- Add type annotations (youtux) `#505 <https://github.com/pytest-dev/pytest-bdd/pull/505>`_
|
||||
- ``pytest_bdd.parsers.StepParser`` now is an Abstract Base Class. Subclasses must make sure to implement the abstract methods. (youtux) `#505 <https://github.com/pytest-dev/pytest-bdd/pull/505>`_
|
||||
- Angular brackets in step definitions are only parsed in "Scenario Outline" (previously they were parsed also in normal "Scenario"s) (youtux) `#524 <https://github.com/pytest-dev/pytest-bdd/pull/524>`_.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,6 @@ from __future__ import annotations
|
|||
from pytest_bdd.scenario import scenario, scenarios
|
||||
from pytest_bdd.steps import given, then, when
|
||||
|
||||
__version__ = "6.0.0"
|
||||
__version__ = "6.0.1"
|
||||
|
||||
__all__ = ["given", "when", "then", "scenario", "scenarios"]
|
||||
|
|
|
@ -43,20 +43,19 @@ 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:
|
||||
parser = getattr(fixturedef.func, "parser", None)
|
||||
if parser is None:
|
||||
continue
|
||||
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:
|
||||
parsers = getattr(fixturedef.func, "_pytest_bdd_parsers", [])
|
||||
for parser in parsers:
|
||||
match = parser.is_matching(name)
|
||||
if not match:
|
||||
continue
|
||||
return parser_name
|
||||
|
||||
parser_name = get_step_fixture_name(parser.name, type_)
|
||||
if request:
|
||||
try:
|
||||
request.getfixturevalue(parser_name)
|
||||
except FixtureLookupError:
|
||||
continue
|
||||
return parser_name
|
||||
return None
|
||||
|
||||
|
||||
|
@ -107,12 +106,16 @@ def _execute_step_function(request: FixtureRequest, scenario: Scenario, step: St
|
|||
converters = getattr(step_func, "converters", {})
|
||||
kwargs = {}
|
||||
|
||||
parser = getattr(step_func, "parser", None)
|
||||
if parser is not None:
|
||||
parsers = getattr(step_func, "_pytest_bdd_parsers", [])
|
||||
|
||||
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)}
|
||||
kw["step_func_args"] = kwargs
|
||||
|
|
|
@ -43,7 +43,7 @@ from _pytest.fixtures import FixtureDef, FixtureRequest
|
|||
|
||||
from .parsers import get_parser
|
||||
from .types import GIVEN, THEN, WHEN
|
||||
from .utils import get_caller_module_locals
|
||||
from .utils import get_caller_module_locals, setdefault
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
@ -124,6 +124,8 @@ def _step_decorator(
|
|||
parser_instance = get_parser(step_name)
|
||||
parsed_step_name = parser_instance.name
|
||||
|
||||
# TODO: Try to not attach to both step_func and lazy_step_func
|
||||
|
||||
step_func.__name__ = str(parsed_step_name)
|
||||
|
||||
def lazy_step_func() -> Callable:
|
||||
|
@ -135,7 +137,9 @@ def _step_decorator(
|
|||
# Preserve the docstring
|
||||
lazy_step_func.__doc__ = func.__doc__
|
||||
|
||||
step_func.parser = lazy_step_func.parser = parser_instance
|
||||
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
|
||||
|
||||
|
|
|
@ -4,16 +4,18 @@ from __future__ import annotations
|
|||
import base64
|
||||
import pickle
|
||||
import re
|
||||
import typing
|
||||
from inspect import getframeinfo, signature
|
||||
from sys import _getframe
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.pytester import RunResult
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
CONFIG_STACK: list[Config] = []
|
||||
|
||||
|
||||
|
@ -69,3 +71,12 @@ def collect_dumped_objects(result: RunResult) -> list:
|
|||
stdout = result.stdout.str() # pytest < 6.2, otherwise we could just do str(result.stdout)
|
||||
payloads = re.findall(rf"{_DUMP_START}(.*?){_DUMP_END}", stdout)
|
||||
return [pickle.loads(base64.b64decode(payload)) for payload in payloads]
|
||||
|
||||
|
||||
def setdefault(obj: object, name: str, default: T) -> T:
|
||||
"""Just like dict.setdefault, but for objects."""
|
||||
try:
|
||||
return getattr(obj, name)
|
||||
except AttributeError:
|
||||
setattr(obj, name, default)
|
||||
return default
|
||||
|
|
|
@ -72,6 +72,58 @@ def test_steps(testdir):
|
|||
result.assert_outcomes(passed=1, failed=0)
|
||||
|
||||
|
||||
def test_step_function_can_be_decorated_multiple_times(testdir):
|
||||
testdir.makefile(
|
||||
".feature",
|
||||
steps=textwrap.dedent(
|
||||
"""\
|
||||
Feature: Steps decoration
|
||||
|
||||
Scenario: Step function can be decorated multiple times
|
||||
Given there is a foo with value 42
|
||||
And there is a second foo with value 43
|
||||
When I do nothing
|
||||
And I do nothing again
|
||||
Then I make no mistakes
|
||||
And I make no mistakes again
|
||||
|
||||
"""
|
||||
),
|
||||
)
|
||||
testdir.makepyfile(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
from pytest_bdd import given, when, then, scenario, parsers
|
||||
|
||||
@scenario("steps.feature", "Step function can be decorated multiple times")
|
||||
def test_steps():
|
||||
pass
|
||||
|
||||
|
||||
@given(parsers.parse("there is a foo with value {value}"), target_fixture="foo")
|
||||
@given(parsers.parse("there is a second foo with value {value}"), target_fixture="second_foo")
|
||||
def foo(value):
|
||||
return value
|
||||
|
||||
|
||||
@when("I do nothing")
|
||||
@when("I do nothing again")
|
||||
def do_nothing():
|
||||
pass
|
||||
|
||||
|
||||
@then("I make no mistakes")
|
||||
@then("I make no mistakes again")
|
||||
def no_errors():
|
||||
assert True
|
||||
|
||||
"""
|
||||
)
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.assert_outcomes(passed=1, failed=0)
|
||||
|
||||
|
||||
def test_all_steps_can_provide_fixtures(testdir):
|
||||
"""Test that given/when/then can all provide fixtures."""
|
||||
testdir.makefile(
|
||||
|
|
Loading…
Reference in New Issue