Skip to content

Commit ba33f18

Browse files
committed
Implement multiple example tables
1 parent 55bd49d commit ba33f18

File tree

5 files changed

+125
-17
lines changed

5 files changed

+125
-17
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Unreleased
77
- Text after the `#` character is no longer stripped from the Scenario and Feature name.
88
- Gherkin keyword aliases can now be used and correctly reported in json and terminal output (see `Keywords <https://cucumber.io/docs/gherkin/reference/#keywords>` for permitted list).
99
- Added localization support. The language of the feature file can be specified using the `# language: <language>` directive at the beginning of the file.
10+
- Multiple example tables supported
11+
- Added filtering by tags against example tables
1012

1113
8.0.0b2
1214
----------

README.rst

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,71 @@ Example:
514514
assert cucumbers["start"] - cucumbers["eat"] == left
515515
516516
517+
Scenario Outlines with Multiple Example Tables
518+
----------------------------------------------
519+
520+
In `pytest-bdd`, you can use multiple example tables in a scenario outline to test
521+
different sets of input data under various conditions.
522+
You can define separate `Examples` blocks, each with its own table of data,
523+
and optionally tag them to differentiate between positive, negative, or any other conditions.
524+
525+
Example:
526+
527+
.. code-block:: gherkin
528+
529+
# content of scenario_outline.feature
530+
531+
Feature: Scenario outlines with multiple examples tables
532+
Scenario Outline: Outlined with multiple example tables
533+
Given there are <start> cucumbers
534+
When I eat <eat> cucumbers
535+
Then I should have <left> cucumbers
536+
537+
@positive
538+
Examples: Positive results
539+
| start | eat | left |
540+
| 12 | 5 | 7 |
541+
| 5 | 4 | 1 |
542+
543+
@negative
544+
Examples: Impossible negative results
545+
| start | eat | left |
546+
| 3 | 9 | -6 |
547+
| 1 | 4 | -3 |
548+
549+
.. code-block:: python
550+
551+
from pytest_bdd import scenarios, given, when, then, parsers
552+
553+
554+
scenarios("scenario_outline.feature")
555+
556+
557+
@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers")
558+
def given_cucumbers(start):
559+
return {"start": start, "eat": 0}
560+
561+
562+
@when(parsers.parse("I eat {eat:d} cucumbers"))
563+
def eat_cucumbers(cucumbers, eat):
564+
cucumbers["eat"] += eat
565+
566+
567+
@then(parsers.parse("I should have {left:d} cucumbers"))
568+
def should_have_left_cucumbers(cucumbers, left):
569+
assert cucumbers["start"] - cucumbers["eat"] == left
570+
571+
572+
When you filter scenarios by a tag, only the examples associated with that tag will be executed.
573+
This allows you to run a specific subset of your test cases based on the tag.
574+
For example, in the following scenario outline, if you filter by the @positive tag,
575+
only the examples under the "Positive results" table will be executed, and the "Negative results" table will be ignored.
576+
577+
.. code-block:: bash
578+
579+
pytest -k "positive"
580+
581+
517582
Datatables
518583
----------
519584

src/pytest_bdd/scenario.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
3333

3434
if TYPE_CHECKING:
35-
from _pytest.mark.structures import ParameterSet
3635
from _pytest.nodes import Node
3736

3837
from .parser import Feature, Scenario, ScenarioTemplate, Step
@@ -303,13 +302,17 @@ def scenario_wrapper(request: FixtureRequest, _pytest_bdd_example: dict[str, str
303302

304303
def collect_example_parametrizations(
305304
templated_scenario: ScenarioTemplate,
306-
) -> list[ParameterSet] | None:
305+
) -> list[pytest.ParameterSet] | None:
307306
parametrizations = []
308307
has_multiple_examples = len(templated_scenario.examples) > 1
309308

310309
for example_id, examples in enumerate(templated_scenario.examples):
311-
with warns(PytestUnknownMarkWarning, match=r"Unknown pytest\.mark\.tag"):
312-
example_marks = [pytest.mark.tag(tag) for tag in examples.tags]
310+
_tags = examples.tags or []
311+
example_marks = []
312+
if _tags:
313+
with warns(PytestUnknownMarkWarning, match=r"Unknown pytest\.mark\.\w+"):
314+
example_marks = [pytest.mark.__getattr__(tag) for tag in _tags]
315+
313316
for context in examples.as_contexts() or [{}]:
314317
test_id = "-".join((str(example_id), *context.values())) if has_multiple_examples else "-".join(
315318
context.values())
@@ -323,7 +326,6 @@ def collect_example_parametrizations(
323326

324327
return parametrizations or None
325328

326-
327329
def scenario(
328330
feature_name: str,
329331
scenario_name: str,

tests/feature/test_outline.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,22 @@ def test_outline(request):
8282
def test_multiple_outlined(pytester):
8383
pytester.makefile(
8484
".feature",
85-
outline=textwrap.dedent(
85+
outline_multi_example=textwrap.dedent(
8686
"""\
87-
Feature: Outline
87+
Feature: Outline With Multiple Examples
8888
Scenario Outline: Outlined given, when, thens with multiple examples tables
8989
Given there are <start> cucumbers
9090
When I eat <eat> cucumbers
9191
Then I should have <left> cucumbers
9292
9393
@positive
94-
Examples: Positive result
94+
Examples: Positive results
9595
| start | eat | left |
9696
| 12 | 5 | 7 |
9797
| 5 | 4 | 1 |
9898
9999
@negative
100-
Examples: Negative result
100+
Examples: Negative results
101101
| start | eat | left |
102102
| 3 | 9 | -6 |
103103
| 1 | 4 | -3 |
@@ -110,15 +110,10 @@ def test_multiple_outlined(pytester):
110110
pytester.makepyfile(
111111
textwrap.dedent(
112112
"""\
113-
from pytest_bdd import scenario
114-
115-
@scenario(
116-
"outline.feature",
117-
"Outlined given, when, thens with multiple examples tables",
118-
)
119-
def test_outline(request):
120-
pass
113+
from pytest_bdd import scenarios
121114
115+
scenarios('outline_multi_example.feature')
116+
122117
"""
123118
)
124119
)

tests/feature/test_tags.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,47 @@ def _():
226226

227227
result = pytester.runpytest("-m", "tag2", "-vv")
228228
result.assert_outcomes(passed=1, deselected=1)
229+
230+
231+
def test_tags_against_multiple_examples_tables(pytester):
232+
pytester.makefile(
233+
".feature",
234+
test="""\
235+
Feature: Scenario with tags over multiple lines
236+
237+
Scenario Outline: Tags
238+
Given I have a <item>
239+
240+
@food
241+
Examples: Food
242+
| item |
243+
| bun |
244+
| ice |
245+
246+
@drink
247+
Examples: Drinks
248+
| item |
249+
| water |
250+
| juice |
251+
""",
252+
)
253+
pytester.makepyfile(
254+
"""
255+
from pytest_bdd import given, scenarios, parsers
256+
257+
scenarios('test.feature')
258+
259+
@given(parsers.parse('I have a {item}'))
260+
def _(item: str):
261+
pass
262+
"""
263+
)
264+
265+
result = pytester.runpytest("-m", "food", "-vv")
266+
result.assert_outcomes(passed=2, deselected=2)
267+
268+
result = pytester.runpytest("-m", "drink", "-vv")
269+
result.assert_outcomes(passed=2, deselected=2)
270+
271+
result = pytester.runpytest("-m", "food or drink", "-vv")
272+
result.assert_outcomes(passed=4, deselected=0)

0 commit comments

Comments
 (0)