diff --git a/CHANGES.rst b/CHANGES.rst index 2afab31d..4236ea86 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,14 @@ Changelog ========= +Unreleased +---------- + +- Cleanup of the documentation and tests related to parametrization (elchupanebrej) +- Removed feature level examples for gherkin compatibility (olegpidsadnyi) + + + 5.0.0 ----- This release introduces breaking changes, please refer to the :ref:`Migration from 4.x.x`. diff --git a/README.rst b/README.rst index 2a35b437..f48b7f95 100644 --- a/README.rst +++ b/README.rst @@ -506,7 +506,7 @@ Scenario outlines Scenarios can be parametrized to cover few cases. In Gherkin the variable templates are written using corner braces as ````. `Gherkin scenario outlines `_ are supported by pytest-bdd -exactly as it's described in be behave_ docs. +exactly as it's described in the behave_ docs. Example: @@ -579,64 +579,6 @@ Example code also shows possibility to pass example converters which may be usef different than strings. -Feature examples -^^^^^^^^^^^^^^^^ - -It's possible to declare example table once for the whole feature, and it will be shared -among all the scenarios of that feature: - -.. code-block:: gherkin - - Feature: Outline - - Examples: - | start | eat | left | - | 12 | 5 | 7 | - | 5 | 4 | 1 | - - Scenario Outline: Eat cucumbers - Given there are cucumbers - When I eat cucumbers - Then I should have cucumbers - - Scenario Outline: Eat apples - Given there are apples - When I eat apples - Then I should have apples - -For some more complex case, you might want to parametrize on both levels: feature and scenario. -This is allowed as long as parameter names do not clash: - - -.. code-block:: gherkin - - Feature: Outline - - Examples: - | start | eat | left | - | 12 | 5 | 7 | - | 5 | 4 | 1 | - - Scenario Outline: Eat fruits - Given there are - When I eat - Then I should have - - Examples: - | fruits | - | oranges | - | apples | - - Scenario Outline: Eat vegetables - Given there are - When I eat - Then I should have - - Examples: - | vegetables | - | carrots | - | tomatoes | - Organizing your scenarios ------------------------- diff --git a/pytest_bdd/__init__.py b/pytest_bdd/__init__.py index eb574b1d..8268e2d6 100644 --- a/pytest_bdd/__init__.py +++ b/pytest_bdd/__init__.py @@ -3,6 +3,6 @@ from pytest_bdd.scenario import scenario, scenarios from pytest_bdd.steps import given, then, when -__version__ = "5.0.0" +__version__ = "6.0.0" __all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__, scenarios.__name__] diff --git a/pytest_bdd/parser.py b/pytest_bdd/parser.py index 8cbfbb14..f9275afe 100644 --- a/pytest_bdd/parser.py +++ b/pytest_bdd/parser.py @@ -90,7 +90,6 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat line_number=1, name=None, tags=set(), - examples=Examples(), background=None, description="", ) @@ -157,34 +156,27 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat feature.background = Background(feature=feature, line_number=line_number) elif mode == types.EXAMPLES: mode = types.EXAMPLES_HEADERS - (scenario or feature).examples.line_number = line_number + scenario.examples.line_number = line_number elif mode == types.EXAMPLES_VERTICAL: mode = types.EXAMPLE_LINE_VERTICAL - (scenario or feature).examples.line_number = line_number + scenario.examples.line_number = line_number elif mode == types.EXAMPLES_HEADERS: - (scenario or feature).examples.set_param_names([l for l in split_line(parsed_line) if l]) + scenario.examples.set_param_names([l for l in split_line(parsed_line) if l]) mode = types.EXAMPLE_LINE elif mode == types.EXAMPLE_LINE: - (scenario or feature).examples.add_example([l for l in split_line(stripped_line)]) + scenario.examples.add_example([l for l in split_line(stripped_line)]) elif mode == types.EXAMPLE_LINE_VERTICAL: param_line_parts = [l for l in split_line(stripped_line)] try: - (scenario or feature).examples.add_example_row(param_line_parts[0], param_line_parts[1:]) + scenario.examples.add_example_row(param_line_parts[0], param_line_parts[1:]) except exceptions.ExamplesNotValidError as exc: - if scenario: - raise exceptions.FeatureError( - f"Scenario has not valid examples. {exc.args[0]}", - line_number, - clean_line, - filename, - ) - else: - raise exceptions.FeatureError( - f"Feature has not valid examples. {exc.args[0]}", - line_number, - clean_line, - filename, - ) + raise exceptions.FeatureError( + f"Scenario has not valid examples. {exc.args[0]}", + line_number, + clean_line, + filename, + ) + elif mode and mode not in (types.FEATURE, types.TAG): step = Step(name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword) if feature.background and not scenario: @@ -201,12 +193,11 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat class Feature: """Feature.""" - def __init__(self, scenarios, filename, rel_filename, name, tags, examples, background, line_number, description): + def __init__(self, scenarios, filename, rel_filename, name, tags, background, line_number, description): self.scenarios: typing.Dict[str, ScenarioTemplate] = scenarios self.rel_filename = rel_filename self.filename = filename self.tags = tags - self.examples = examples self.name = name self.line_number = line_number self.description = description @@ -264,7 +255,7 @@ def validate(self): :raises ScenarioValidationError: when scenario is not valid """ params = frozenset(sum((list(step.params) for step in self.steps), [])) - example_params = set(self.examples.example_params + self.feature.examples.example_params) + example_params = set(self.examples.example_params) if params and example_params and params != example_params: raise exceptions.ScenarioExamplesNotValidError( """Scenario "{}" in the feature "{}" has not valid examples. """ diff --git a/pytest_bdd/scenario.py b/pytest_bdd/scenario.py index c9446e38..3436abf9 100644 --- a/pytest_bdd/scenario.py +++ b/pytest_bdd/scenario.py @@ -199,19 +199,10 @@ def collect_example_parametrizations( ) -> "typing.Optional[typing.List[ParameterSet]]": # We need to evaluate these iterators and store them as lists, otherwise # we won't be able to do the cartesian product later (the second iterator will be consumed) - feature_contexts = list(templated_scenario.feature.examples.as_contexts()) - scenario_contexts = list(templated_scenario.examples.as_contexts()) - - contexts = [ - {**feature_context, **scenario_context} - # We must make sure that we always have at least one element in each list, otherwise - # the cartesian product will result in an empty list too, even if one of the 2 sets - # is non empty. - for feature_context in feature_contexts or [{}] - for scenario_context in scenario_contexts or [{}] - ] - if contexts == [{}]: + contexts = list(templated_scenario.examples.as_contexts()) + if not contexts: return None + return [pytest.param(context, id="-".join(context.values())) for context in contexts] diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 3628db8d..4a73b827 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -162,47 +162,6 @@ def test_outline(request): ) -def test_wrong_vertical_examples_feature(testdir): - """Test parametrized feature vertical example table has wrong format.""" - testdir.makefile( - ".feature", - outline=textwrap.dedent( - """\ - Feature: Outlines - - Examples: Vertical - | start | 12 | 2 | - | start | 10 | 1 | - | left | 7 | 1 | - - Scenario Outline: Outlined with wrong vertical example table - Given there are cucumbers - When I eat cucumbers - Then I should have cucumbers - """ - ), - ) - testdir.makeconftest(textwrap.dedent(STEPS)) - - testdir.makepyfile( - textwrap.dedent( - """\ - from pytest_bdd import scenario - - @scenario("outline.feature", "Outlined with wrong vertical example table") - def test_outline(request): - pass - """ - ) - ) - result = testdir.runpytest() - assert_outcomes(result, errors=1) - result.stdout.fnmatch_lines( - "*Feature has not valid examples. Example rows should contain unique parameters. " - '"start" appeared more than once.*' - ) - - def test_outlined_with_other_fixtures(testdir): """Test outlined scenario also using other parametrized fixture.""" testdir.makefile( @@ -300,85 +259,6 @@ def test_outline(): # fmt: on -def test_outlined_feature(testdir): - testdir.makefile( - ".feature", - outline=textwrap.dedent( - """\ - Feature: Outline - - Examples: - | start | eat | left | - | 12 | 5 | 7 | - | 5 | 4 | 1 | - - Scenario Outline: Outlined given, when, thens - Given there are - When I eat - Then I should have - - Examples: - | fruits | - | oranges | - | apples | - """ - ), - ) - - testdir.makepyfile( - textwrap.dedent( - """\ - from pytest_bdd import given, when, then, scenario, parsers - from pytest_bdd.utils import dump_obj - - @scenario( - "outline.feature", - "Outlined given, when, thens", - ) - def test_outline(): - pass - - @given(parsers.parse("there are {start:d} {fruits}"), target_fixture="start_fruits") - def start_fruits(start, fruits): - dump_obj(start, fruits) - - assert isinstance(start, int) - return {fruits: dict(start=start)} - - - @when(parsers.parse("I eat {eat:g} {fruits}")) - def eat_fruits(start_fruits, eat, fruits): - dump_obj(eat, fruits) - - assert isinstance(eat, float) - start_fruits[fruits]["eat"] = eat - - - @then(parsers.parse("I should have {left} {fruits}")) - def should_have_left_fruits(start_fruits, start, eat, left, fruits): - dump_obj(left, fruits) - - assert isinstance(left, str) - assert start - eat == int(left) - assert start_fruits[fruits]["start"] == start - assert start_fruits[fruits]["eat"] == eat - - """ - ) - ) - result = testdir.runpytest("-s") - result.assert_outcomes(passed=4) - parametrizations = collect_dumped_objects(result) - # fmt: off - assert parametrizations == [ - 12, "oranges", 5.0, "oranges", "7", "oranges", - 12, "apples", 5.0, "apples", "7", "apples", - 5, "oranges", 4.0, "oranges", "1", "oranges", - 5, "apples", 4.0, "apples", "1", "apples", - ] - # fmt: on - - def test_outline_with_escaped_pipes(testdir): """Test parametrized feature example table with escaped pipe characters in input.""" testdir.makefile(