Skip to content

Commit

Permalink
Feature examples removal. Compatibility with official gherkin
Browse files Browse the repository at this point in the history
  • Loading branch information
olegpidsadnyi committed Jan 15, 2022
1 parent f4ed62d commit e0a1437
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 215 deletions.
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
60 changes: 1 addition & 59 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<somevalue>``.
`Gherkin scenario outlines <http://behat.org/en/v3.0/user_guide/writing_scenarios.html#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:

Expand Down Expand Up @@ -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 <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Scenario Outline: Eat apples
Given there are <start> apples
When I eat <eat> apples
Then I should have <left> 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 <start> <fruits>
When I eat <eat> <fruits>
Then I should have <left> <fruits>
Examples:
| fruits |
| oranges |
| apples |
Scenario Outline: Eat vegetables
Given there are <start> <vegetables>
When I eat <eat> <vegetables>
Then I should have <left> <vegetables>
Examples:
| vegetables |
| carrots |
| tomatoes |

Organizing your scenarios
-------------------------
Expand Down
2 changes: 1 addition & 1 deletion pytest_bdd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__]
37 changes: 14 additions & 23 deletions pytest_bdd/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="",
)
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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. """
Expand Down
15 changes: 3 additions & 12 deletions pytest_bdd/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]


Expand Down
120 changes: 0 additions & 120 deletions tests/feature/test_outline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> 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(
Expand Down Expand Up @@ -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 <start> <fruits>
When I eat <eat> <fruits>
Then I should have <left> <fruits>
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(
Expand Down

0 comments on commit e0a1437

Please sign in to comment.