Skip to content

Commit 463bf19

Browse files
authored
Merge branch 'master' into master
2 parents ef7681b + 2fb5a11 commit 463bf19

File tree

11 files changed

+93
-575
lines changed

11 files changed

+93
-575
lines changed

CHANGES.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
Changelog
22
=========
33

4+
Unreleased
5+
----------
6+
7+
This release introduces breaking changes in order to be more in line with the official gherkin specification.
8+
9+
- Cleanup of the documentation and tests related to parametrization (elchupanebrej)
10+
- Removed feature level examples for the gherkin compatibility (olegpidsadnyi)
11+
- Removed vertical examples for the gherkin compatibility (olegpidsadnyi)
12+
- Step arguments are no longer fixtures (olegpidsadnyi)
13+
14+
15+
416
5.0.0
517
-----
618
This release introduces breaking changes, please refer to the :ref:`Migration from 4.x.x`.

README.rst

Lines changed: 42 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,9 @@ for `cfparse` parser
210210
@given(
211211
parsers.cfparse("there are {start:Number} cucumbers",
212212
extra_types=dict(Number=int)),
213-
target_fixture="start_cucumbers",
213+
target_fixture="cucumbers",
214214
)
215-
def start_cucumbers(start):
215+
def given_cucumbers(start):
216216
return dict(start=start, eat=0)
217217
218218
for `re` parser
@@ -224,9 +224,9 @@ for `re` parser
224224
@given(
225225
parsers.re(r"there are (?P<start>\d+) cucumbers"),
226226
converters=dict(start=int),
227-
target_fixture="start_cucumbers",
227+
target_fixture="cucumbers",
228228
)
229-
def start_cucumbers(start):
229+
def given_cucumbers(start):
230230
return dict(start=start, eat=0)
231231
232232
@@ -257,20 +257,19 @@ The code will look like:
257257
pass
258258
259259
260-
@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="start_cucumbers")
261-
def start_cucumbers(start):
260+
@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers")
261+
def given_cucumbers(start):
262262
return dict(start=start, eat=0)
263263
264264
265265
@when(parsers.parse("I eat {eat:d} cucumbers"))
266-
def eat_cucumbers(start_cucumbers, eat):
266+
def eat_cucumbers(cucumbers, eat):
267267
start_cucumbers["eat"] += eat
268268
269269
270270
@then(parsers.parse("I should have {left:d} cucumbers"))
271-
def should_have_left_cucumbers(start_cucumbers, start, left):
272-
assert start_cucumbers['start'] == start
273-
assert start - start_cucumbers['eat'] == left
271+
def should_have_left_cucumbers(cucumbers, left):
272+
assert cucumbers['start'] - cucumbers['eat'] == left
274273
275274
Example code also shows possibility to pass argument converters which may be useful if you need to postprocess step
276275
arguments after the parser.
@@ -303,21 +302,11 @@ You can implement your own step parser. It's interface is quite simple. The code
303302
return bool(self.regex.match(name))
304303
305304
306-
@given(parsers.parse("there are %start% cucumbers"), target_fixture="start_cucumbers")
307-
def start_cucumbers(start):
305+
@given(parsers.parse("there are %start% cucumbers"), target_fixture="cucumbers")
306+
def given_cucumbers(start):
308307
return dict(start=start, eat=0)
309308
310309
311-
Step arguments are fixtures as well!
312-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
313-
314-
Step arguments are injected into pytest `request` context as normal fixtures with the names equal to the names of the
315-
arguments. This opens a number of possibilities:
316-
317-
* you can access step's argument as a fixture in other step function just by mentioning it as an argument (just like any other pytest fixture)
318-
* if the name of the step argument clashes with existing fixture, it will be overridden by step's argument value; this way you can set/override the value for some fixture deeply inside of the fixture tree in a ad-hoc way by just choosing the proper name for the step argument.
319-
320-
321310
Override fixtures via given steps
322311
---------------------------------
323312

@@ -506,7 +495,7 @@ Scenario outlines
506495
Scenarios can be parametrized to cover few cases. In Gherkin the variable
507496
templates are written using corner braces as ``<somevalue>``.
508497
`Gherkin scenario outlines <http://behat.org/en/v3.0/user_guide/writing_scenarios.html#scenario-outlines>`_ are supported by pytest-bdd
509-
exactly as it's described in be behave_ docs.
498+
exactly as it's described in the behave_ docs.
510499

511500
Example:
512501

@@ -522,121 +511,6 @@ Example:
522511
| start | eat | left |
523512
| 12 | 5 | 7 |
524513
525-
pytest-bdd feature file format also supports example tables in different way:
526-
527-
528-
.. code-block:: gherkin
529-
530-
Feature: Scenario outlines
531-
Scenario Outline: Outlined given, when, then
532-
Given there are <start> cucumbers
533-
When I eat <eat> cucumbers
534-
Then I should have <left> cucumbers
535-
536-
Examples: Vertical
537-
| start | 12 | 2 |
538-
| eat | 5 | 1 |
539-
| left | 7 | 1 |
540-
541-
This form allows to have tables with lots of columns keeping the maximum text width predictable without significant
542-
readability change.
543-
544-
The code will look like:
545-
546-
.. code-block:: python
547-
548-
from pytest_bdd import given, when, then, scenario, parsers
549-
550-
551-
@scenario(
552-
"outline.feature",
553-
"Outlined given, when, then",
554-
)
555-
def test_outlined():
556-
pass
557-
558-
559-
@given(parsers.parse("there are {start:d} cucumbers", target_fixture="start_cucumbers"))
560-
def start_cucumbers(start):
561-
assert isinstance(start, int)
562-
return dict(start=start)
563-
564-
565-
@when(parsers.parse("I eat {eat:g} cucumbers"))
566-
def eat_cucumbers(start_cucumbers, eat):
567-
assert isinstance(eat, float)
568-
start_cucumbers["eat"] = eat
569-
570-
571-
@then(parsers.parse("I should have {left} cucumbers"))
572-
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
573-
assert isinstance(left, str)
574-
assert start - eat == int(left)
575-
assert start_cucumbers["start"] == start
576-
assert start_cucumbers["eat"] == eat
577-
578-
Example code also shows possibility to pass example converters which may be useful if you need parameter types
579-
different than strings.
580-
581-
582-
Feature examples
583-
^^^^^^^^^^^^^^^^
584-
585-
It's possible to declare example table once for the whole feature, and it will be shared
586-
among all the scenarios of that feature:
587-
588-
.. code-block:: gherkin
589-
590-
Feature: Outline
591-
592-
Examples:
593-
| start | eat | left |
594-
| 12 | 5 | 7 |
595-
| 5 | 4 | 1 |
596-
597-
Scenario Outline: Eat cucumbers
598-
Given there are <start> cucumbers
599-
When I eat <eat> cucumbers
600-
Then I should have <left> cucumbers
601-
602-
Scenario Outline: Eat apples
603-
Given there are <start> apples
604-
When I eat <eat> apples
605-
Then I should have <left> apples
606-
607-
For some more complex case, you might want to parametrize on both levels: feature and scenario.
608-
This is allowed as long as parameter names do not clash:
609-
610-
611-
.. code-block:: gherkin
612-
613-
Feature: Outline
614-
615-
Examples:
616-
| start | eat | left |
617-
| 12 | 5 | 7 |
618-
| 5 | 4 | 1 |
619-
620-
Scenario Outline: Eat fruits
621-
Given there are <start> <fruits>
622-
When I eat <eat> <fruits>
623-
Then I should have <left> <fruits>
624-
625-
Examples:
626-
| fruits |
627-
| oranges |
628-
| apples |
629-
630-
Scenario Outline: Eat vegetables
631-
Given there are <start> <vegetables>
632-
When I eat <eat> <vegetables>
633-
Then I should have <left> <vegetables>
634-
635-
Examples:
636-
| vegetables |
637-
| carrots |
638-
| tomatoes |
639-
640514
641515
Organizing your scenarios
642516
-------------------------
@@ -1114,6 +988,36 @@ As as side effect, the tool will validate the files for format errors, also some
1114988
ordering of the types of the steps.
1115989

1116990

991+
.. _Migration from 5.x.x:
992+
993+
Migration of your tests from versions 5.x.x
994+
-------------------------------------------
995+
996+
The primary focus of the pytest-bdd is the compatibility with the latest gherkin developments
997+
e.g. multiple scenario outline example tables with tags support etc.
998+
999+
In order to provide the best compatibility it is best to support the features described in the official
1000+
gherkin reference. This means deprecation of some non-standard features that were implemented in pytest-bdd.
1001+
1002+
1003+
Removal of the feature examples
1004+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1005+
The example tables on the feature level are no longer supported. The tests should be parametrized with the example tables
1006+
on the scenario level.
1007+
1008+
1009+
Removal of the vertical examples
1010+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1011+
Vertical example tables are no longer supported since the official gherkin doesn't support them.
1012+
The example tables should have horizontal orientation.
1013+
1014+
1015+
Step arguments are no longer fixtures
1016+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1017+
Step parsed arguments conflicted with the fixtures. Now they no longer define fixture.
1018+
If the fixture has to be defined by the step the target_fixture param should be used.
1019+
1020+
11171021
.. _Migration from 4.x.x:
11181022

11191023
Migration of your tests from versions 4.x.x

pytest_bdd/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
from pytest_bdd.scenario import scenario, scenarios
44
from pytest_bdd.steps import given, then, when
55

6-
__version__ = "5.0.0"
6+
__version__ = "6.0.0"
77

88
__all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__, scenarios.__name__]

pytest_bdd/parser.py

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
STEP_PREFIXES = [
1313
("Feature: ", types.FEATURE),
1414
("Scenario Outline: ", types.SCENARIO_OUTLINE),
15-
("Examples: Vertical", types.EXAMPLES_VERTICAL),
1615
("Examples:", types.EXAMPLES),
1716
("Scenario: ", types.SCENARIO),
1817
("Background:", types.BACKGROUND),
@@ -90,7 +89,6 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat
9089
line_number=1,
9190
name=None,
9291
tags=set(),
93-
examples=Examples(),
9492
background=None,
9593
description="",
9694
)
@@ -157,34 +155,12 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat
157155
feature.background = Background(feature=feature, line_number=line_number)
158156
elif mode == types.EXAMPLES:
159157
mode = types.EXAMPLES_HEADERS
160-
(scenario or feature).examples.line_number = line_number
161-
elif mode == types.EXAMPLES_VERTICAL:
162-
mode = types.EXAMPLE_LINE_VERTICAL
163-
(scenario or feature).examples.line_number = line_number
158+
scenario.examples.line_number = line_number
164159
elif mode == types.EXAMPLES_HEADERS:
165-
(scenario or feature).examples.set_param_names([l for l in split_line(parsed_line) if l])
160+
scenario.examples.set_param_names([l for l in split_line(parsed_line) if l])
166161
mode = types.EXAMPLE_LINE
167162
elif mode == types.EXAMPLE_LINE:
168-
(scenario or feature).examples.add_example([l for l in split_line(stripped_line)])
169-
elif mode == types.EXAMPLE_LINE_VERTICAL:
170-
param_line_parts = [l for l in split_line(stripped_line)]
171-
try:
172-
(scenario or feature).examples.add_example_row(param_line_parts[0], param_line_parts[1:])
173-
except exceptions.ExamplesNotValidError as exc:
174-
if scenario:
175-
raise exceptions.FeatureError(
176-
f"Scenario has not valid examples. {exc.args[0]}",
177-
line_number,
178-
clean_line,
179-
filename,
180-
)
181-
else:
182-
raise exceptions.FeatureError(
183-
f"Feature has not valid examples. {exc.args[0]}",
184-
line_number,
185-
clean_line,
186-
filename,
187-
)
163+
scenario.examples.add_example([l for l in split_line(stripped_line)])
188164
elif mode and mode not in (types.FEATURE, types.TAG):
189165
step = Step(name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword)
190166
if feature.background and not scenario:
@@ -201,12 +177,11 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat
201177
class Feature:
202178
"""Feature."""
203179

204-
def __init__(self, scenarios, filename, rel_filename, name, tags, examples, background, line_number, description):
180+
def __init__(self, scenarios, filename, rel_filename, name, tags, background, line_number, description):
205181
self.scenarios: typing.Dict[str, ScenarioTemplate] = scenarios
206182
self.rel_filename = rel_filename
207183
self.filename = filename
208184
self.tags = tags
209-
self.examples = examples
210185
self.name = name
211186
self.line_number = line_number
212187
self.description = description
@@ -264,7 +239,7 @@ def validate(self):
264239
:raises ScenarioValidationError: when scenario is not valid
265240
"""
266241
params = frozenset(sum((list(step.params) for step in self.steps), []))
267-
example_params = set(self.examples.example_params + self.feature.examples.example_params)
242+
example_params = set(self.examples.example_params)
268243
if params and example_params and params != example_params:
269244
raise exceptions.ScenarioExamplesNotValidError(
270245
"""Scenario "{}" in the feature "{}" has not valid examples. """
@@ -392,7 +367,6 @@ def __init__(self):
392367
"""Initialize examples instance."""
393368
self.example_params = []
394369
self.examples = []
395-
self.vertical_examples = []
396370
self.line_number = None
397371
self.name = None
398372

@@ -421,30 +395,20 @@ def add_example_row(self, param, values):
421395
f"""Example rows should contain unique parameters. "{param}" appeared more than once"""
422396
)
423397
self.example_params.append(param)
424-
self.vertical_examples.append(values)
425398

426399
def as_contexts(self) -> typing.Iterable[typing.Dict[str, typing.Any]]:
427-
param_count = len(self.example_params)
428-
if self.vertical_examples and not self.examples:
429-
for value_index in range(len(self.vertical_examples[0])):
430-
example = []
431-
for param_index in range(param_count):
432-
example.append(self.vertical_examples[param_index][value_index])
433-
self.examples.append(example)
434-
435400
if not self.examples:
436401
return
437402

438403
header, rows = self.example_params, self.examples
439404

440405
for row in rows:
441406
assert len(header) == len(row)
442-
443407
yield dict(zip(header, row))
444408

445409
def __bool__(self):
446410
"""Bool comparison."""
447-
return bool(self.vertical_examples or self.examples)
411+
return bool(self.examples)
448412

449413

450414
def get_tags(line):

0 commit comments

Comments
 (0)