Implement multiple example tables
This commit is contained in:
parent
55bd49d5ee
commit
ba33f18133
|
@ -7,6 +7,8 @@ Unreleased
|
||||||
- Text after the `#` character is no longer stripped from the Scenario and Feature name.
|
- 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).
|
- 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.
|
- 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
|
8.0.0b2
|
||||||
----------
|
----------
|
||||||
|
|
65
README.rst
65
README.rst
|
@ -514,6 +514,71 @@ Example:
|
||||||
assert cucumbers["start"] - cucumbers["eat"] == left
|
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
|
Datatables
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -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
|
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest.mark.structures import ParameterSet
|
|
||||||
from _pytest.nodes import Node
|
from _pytest.nodes import Node
|
||||||
|
|
||||||
from .parser import Feature, Scenario, ScenarioTemplate, Step
|
from .parser import Feature, Scenario, ScenarioTemplate, Step
|
||||||
|
@ -303,13 +302,17 @@ def _get_scenario_decorator(
|
||||||
|
|
||||||
def collect_example_parametrizations(
|
def collect_example_parametrizations(
|
||||||
templated_scenario: ScenarioTemplate,
|
templated_scenario: ScenarioTemplate,
|
||||||
) -> list[ParameterSet] | None:
|
) -> list[pytest.ParameterSet] | None:
|
||||||
parametrizations = []
|
parametrizations = []
|
||||||
has_multiple_examples = len(templated_scenario.examples) > 1
|
has_multiple_examples = len(templated_scenario.examples) > 1
|
||||||
|
|
||||||
for example_id, examples in enumerate(templated_scenario.examples):
|
for example_id, examples in enumerate(templated_scenario.examples):
|
||||||
with warns(PytestUnknownMarkWarning, match=r"Unknown pytest\.mark\.tag"):
|
_tags = examples.tags or []
|
||||||
example_marks = [pytest.mark.tag(tag) for tag in examples.tags]
|
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 [{}]:
|
for context in examples.as_contexts() or [{}]:
|
||||||
test_id = "-".join((str(example_id), *context.values())) if has_multiple_examples else "-".join(
|
test_id = "-".join((str(example_id), *context.values())) if has_multiple_examples else "-".join(
|
||||||
context.values())
|
context.values())
|
||||||
|
@ -323,7 +326,6 @@ def collect_example_parametrizations(
|
||||||
|
|
||||||
return parametrizations or None
|
return parametrizations or None
|
||||||
|
|
||||||
|
|
||||||
def scenario(
|
def scenario(
|
||||||
feature_name: str,
|
feature_name: str,
|
||||||
scenario_name: str,
|
scenario_name: str,
|
||||||
|
|
|
@ -82,22 +82,22 @@ def test_outlined(pytester):
|
||||||
def test_multiple_outlined(pytester):
|
def test_multiple_outlined(pytester):
|
||||||
pytester.makefile(
|
pytester.makefile(
|
||||||
".feature",
|
".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
|
Scenario Outline: Outlined given, when, thens with multiple examples tables
|
||||||
Given there are <start> cucumbers
|
Given there are <start> cucumbers
|
||||||
When I eat <eat> cucumbers
|
When I eat <eat> cucumbers
|
||||||
Then I should have <left> cucumbers
|
Then I should have <left> cucumbers
|
||||||
|
|
||||||
@positive
|
@positive
|
||||||
Examples: Positive result
|
Examples: Positive results
|
||||||
| start | eat | left |
|
| start | eat | left |
|
||||||
| 12 | 5 | 7 |
|
| 12 | 5 | 7 |
|
||||||
| 5 | 4 | 1 |
|
| 5 | 4 | 1 |
|
||||||
|
|
||||||
@negative
|
@negative
|
||||||
Examples: Negative result
|
Examples: Negative results
|
||||||
| start | eat | left |
|
| start | eat | left |
|
||||||
| 3 | 9 | -6 |
|
| 3 | 9 | -6 |
|
||||||
| 1 | 4 | -3 |
|
| 1 | 4 | -3 |
|
||||||
|
@ -110,15 +110,10 @@ def test_multiple_outlined(pytester):
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""\
|
"""\
|
||||||
from pytest_bdd import scenario
|
from pytest_bdd import scenarios
|
||||||
|
|
||||||
@scenario(
|
|
||||||
"outline.feature",
|
|
||||||
"Outlined given, when, thens with multiple examples tables",
|
|
||||||
)
|
|
||||||
def test_outline(request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
scenarios('outline_multi_example.feature')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -226,3 +226,47 @@ def test_multiline_tags(pytester):
|
||||||
|
|
||||||
result = pytester.runpytest("-m", "tag2", "-vv")
|
result = pytester.runpytest("-m", "tag2", "-vv")
|
||||||
result.assert_outcomes(passed=1, deselected=1)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue