Implement multiple example tables

This commit is contained in:
Jason Allen 2024-11-11 20:16:48 +00:00
parent 55bd49d5ee
commit ba33f18133
5 changed files with 125 additions and 17 deletions

View File

@ -7,6 +7,8 @@ Unreleased
- Text after the `#` character is no longer stripped from the Scenario and Feature name.
- 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).
- Added localization support. The language of the feature file can be specified using the `# language: <language>` directive at the beginning of the file.
- Multiple example tables supported
- Added filtering by tags against example tables
8.0.0b2
----------

View File

@ -514,6 +514,71 @@ Example:
assert cucumbers["start"] - cucumbers["eat"] == left
Scenario Outlines with Multiple Example Tables
----------------------------------------------
In `pytest-bdd`, you can use multiple example tables in a scenario outline to test
different sets of input data under various conditions.
You can define separate `Examples` blocks, each with its own table of data,
and optionally tag them to differentiate between positive, negative, or any other conditions.
Example:
.. code-block:: gherkin
# content of scenario_outline.feature
Feature: Scenario outlines with multiple examples tables
Scenario Outline: Outlined with multiple example tables
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
@positive
Examples: Positive results
| start | eat | left |
| 12 | 5 | 7 |
| 5 | 4 | 1 |
@negative
Examples: Impossible negative results
| start | eat | left |
| 3 | 9 | -6 |
| 1 | 4 | -3 |
.. code-block:: python
from pytest_bdd import scenarios, given, when, then, parsers
scenarios("scenario_outline.feature")
@given(parsers.parse("there are {start:d} cucumbers"), target_fixture="cucumbers")
def given_cucumbers(start):
return {"start": start, "eat": 0}
@when(parsers.parse("I eat {eat:d} cucumbers"))
def eat_cucumbers(cucumbers, eat):
cucumbers["eat"] += eat
@then(parsers.parse("I should have {left:d} cucumbers"))
def should_have_left_cucumbers(cucumbers, left):
assert cucumbers["start"] - cucumbers["eat"] == left
When you filter scenarios by a tag, only the examples associated with that tag will be executed.
This allows you to run a specific subset of your test cases based on the tag.
For example, in the following scenario outline, if you filter by the @positive tag,
only the examples under the "Positive results" table will be executed, and the "Negative results" table will be ignored.
.. code-block:: bash
pytest -k "positive"
Datatables
----------

View File

@ -32,7 +32,6 @@ from .steps import StepFunctionContext, get_step_fixture_name
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
if TYPE_CHECKING:
from _pytest.mark.structures import ParameterSet
from _pytest.nodes import Node
from .parser import Feature, Scenario, ScenarioTemplate, Step
@ -303,13 +302,17 @@ def _get_scenario_decorator(
def collect_example_parametrizations(
templated_scenario: ScenarioTemplate,
) -> list[ParameterSet] | None:
) -> list[pytest.ParameterSet] | None:
parametrizations = []
has_multiple_examples = len(templated_scenario.examples) > 1
for example_id, examples in enumerate(templated_scenario.examples):
with warns(PytestUnknownMarkWarning, match=r"Unknown pytest\.mark\.tag"):
example_marks = [pytest.mark.tag(tag) for tag in examples.tags]
_tags = examples.tags or []
example_marks = []
if _tags:
with warns(PytestUnknownMarkWarning, match=r"Unknown pytest\.mark\.\w+"):
example_marks = [pytest.mark.__getattr__(tag) for tag in _tags]
for context in examples.as_contexts() or [{}]:
test_id = "-".join((str(example_id), *context.values())) if has_multiple_examples else "-".join(
context.values())
@ -323,7 +326,6 @@ def collect_example_parametrizations(
return parametrizations or None
def scenario(
feature_name: str,
scenario_name: str,

View File

@ -82,22 +82,22 @@ def test_outlined(pytester):
def test_multiple_outlined(pytester):
pytester.makefile(
".feature",
outline=textwrap.dedent(
outline_multi_example=textwrap.dedent(
"""\
Feature: Outline
Feature: Outline With Multiple Examples
Scenario Outline: Outlined given, when, thens with multiple examples tables
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
@positive
Examples: Positive result
Examples: Positive results
| start | eat | left |
| 12 | 5 | 7 |
| 5 | 4 | 1 |
@negative
Examples: Negative result
Examples: Negative results
| start | eat | left |
| 3 | 9 | -6 |
| 1 | 4 | -3 |
@ -110,15 +110,10 @@ def test_multiple_outlined(pytester):
pytester.makepyfile(
textwrap.dedent(
"""\
from pytest_bdd import scenario
@scenario(
"outline.feature",
"Outlined given, when, thens with multiple examples tables",
)
def test_outline(request):
pass
from pytest_bdd import scenarios
scenarios('outline_multi_example.feature')
"""
)
)

View File

@ -226,3 +226,47 @@ def test_multiline_tags(pytester):
result = pytester.runpytest("-m", "tag2", "-vv")
result.assert_outcomes(passed=1, deselected=1)
def test_tags_against_multiple_examples_tables(pytester):
pytester.makefile(
".feature",
test="""\
Feature: Scenario with tags over multiple lines
Scenario Outline: Tags
Given I have a <item>
@food
Examples: Food
| item |
| bun |
| ice |
@drink
Examples: Drinks
| item |
| water |
| juice |
""",
)
pytester.makepyfile(
"""
from pytest_bdd import given, scenarios, parsers
scenarios('test.feature')
@given(parsers.parse('I have a {item}'))
def _(item: str):
pass
"""
)
result = pytester.runpytest("-m", "food", "-vv")
result.assert_outcomes(passed=2, deselected=2)
result = pytester.runpytest("-m", "drink", "-vv")
result.assert_outcomes(passed=2, deselected=2)
result = pytester.runpytest("-m", "food or drink", "-vv")
result.assert_outcomes(passed=4, deselected=0)