From 4f11344b0cb7a26875e0ad4886deb7755079c977 Mon Sep 17 00:00:00 2001 From: edvatar <88481784+toroleapinc@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:47:17 -0400 Subject: [PATCH 1/2] feat: use scenarios() shortcut in code generation template (#796) Replace individual @scenario() decorators with the scenarios() shortcut function in the code generation template. This produces cleaner, more concise generated test code. Before: @scenario('feature.feature', 'Scenario 1') def test_scenario_1(): ... @scenario('feature.feature', 'Scenario 2') def test_scenario_2(): ... After: scenarios('feature.feature') Closes #535 Signed-off-by: edvatar <88481784+toroleapinc@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- src/pytest_bdd/templates/test.py.mak | 16 ++++++++-------- tests/scripts/test_generate.py | 21 ++++++--------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/pytest_bdd/templates/test.py.mak b/src/pytest_bdd/templates/test.py.mak index 9f7901539..d48b52205 100644 --- a/src/pytest_bdd/templates/test.py.mak +++ b/src/pytest_bdd/templates/test.py.mak @@ -1,22 +1,22 @@ +<% + feature_paths = sorted(set(f.rel_filename for f in features)) +%>\ % if features: """${ features[0].name or features[0].rel_filename } feature tests.""" from pytest_bdd import ( given, - scenario, + scenarios, then, when, ) - -% endif -% for scenario in sorted(scenarios, key=lambda scenario: scenario.name): -@scenario('${scenario.feature.rel_filename}', ${ make_string_literal(scenario.name)}) -def test_${ make_python_name(scenario.name)}(): - ${make_python_docstring(scenario.name)} +% for feature_path in feature_paths: +scenarios('${feature_path}') +% endfor -% endfor +% endif % for step in steps: @${step.type}(${ make_string_literal(step.name)}) def _(): diff --git a/tests/scripts/test_generate.py b/tests/scripts/test_generate.py index e194076d1..098099dd4 100644 --- a/tests/scripts/test_generate.py +++ b/tests/scripts/test_generate.py @@ -42,15 +42,12 @@ def test_generate(pytester, monkeypatch, capsys): from pytest_bdd import ( given, - scenario, + scenarios, then, when, ) - - @scenario('scripts/generate.feature', 'Given and when using the same fixture should not evaluate it twice') - def test_given_and_when_using_the_same_fixture_should_not_evaluate_it_twice(): - """Given and when using the same fixture should not evaluate it twice.""" + scenarios('scripts/generate.feature') @given('1 have a fixture (appends 1 to a list) in reuse syntax') @@ -108,15 +105,12 @@ def test_generate_with_quotes(pytester): from pytest_bdd import ( given, - scenario, + scenarios, then, when, ) - - @scenario('generate_with_quotes.feature', 'A step definition with quotes should be escaped as needed') - def test_a_step_definition_with_quotes_should_be_escaped_as_needed(): - """A step definition with quotes should be escaped as needed.""" + scenarios('generate_with_quotes.feature') @given('I have a fixture with "double" quotes') @@ -184,15 +178,12 @@ def test_unicode_characters(pytester, monkeypatch): from pytest_bdd import ( given, - scenario, + scenarios, then, when, ) - - @scenario('unicode_characters.feature', 'Calculating the circumference of a circle') - def test_calculating_the_circumference_of_a_circle(): - """Calculating the circumference of a circle.""" + scenarios('unicode_characters.feature') @given('We have a circle') From 6a805b8b47ed52a111c5ff55093c2d28bbdb6506 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 5 Jul 2026 20:02:30 +0200 Subject: [PATCH 2/2] Address review comments from #796 - Move the feature_paths computation next to the loop that uses it, inside the 'if features' block (review comment from youtux). - Add a test proving generation is idempotent: invoking the script twice yields identical output, and applying the generated code twice to the same module does not duplicate tests (scenarios() skips already bound scenarios). - Add the changelog entry that was missing from #796. --- CHANGES.rst | 1 + src/pytest_bdd/templates/test.py.mak | 6 +++--- tests/scripts/test_generate.py | 31 ++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7ca202acc..b181fde06 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,7 @@ Added Changed +++++++ +* The code generation template (``pytest-bdd generate`` and ``--generate-missing``) now uses the ``scenarios()`` shortcut instead of one ``@scenario()`` decorator per scenario (`#535 `_, `#796 `_). * Relaxed `gherkin-official` dependency requirement to `>=29.0.0` to allow for newer versions of the `gherkin-official` package. * Excluded `gherkin-official` `31.0.0` and `32.0.0`, which crash with ``StopIteration`` when parsing empty descriptions (fixed upstream in `32.0.1`). diff --git a/src/pytest_bdd/templates/test.py.mak b/src/pytest_bdd/templates/test.py.mak index d48b52205..3b276db6d 100644 --- a/src/pytest_bdd/templates/test.py.mak +++ b/src/pytest_bdd/templates/test.py.mak @@ -1,6 +1,3 @@ -<% - feature_paths = sorted(set(f.rel_filename for f in features)) -%>\ % if features: """${ features[0].name or features[0].rel_filename } feature tests.""" @@ -11,6 +8,9 @@ from pytest_bdd import ( when, ) +<% + feature_paths = sorted(set(f.rel_filename for f in features)) +%>\ % for feature_path in feature_paths: scenarios('${feature_path}') % endfor diff --git a/tests/scripts/test_generate.py b/tests/scripts/test_generate.py index 098099dd4..5bfd83d53 100644 --- a/tests/scripts/test_generate.py +++ b/tests/scripts/test_generate.py @@ -151,6 +151,37 @@ def _(): ) +def test_generate_is_idempotent(pytester): + """Test that invoking the generation script twice yields the same output. + + Also make sure that applying the generated code twice to the same module + does not duplicate tests, since scenarios() skips already bound scenarios. + """ + pytester.makefile( + ".feature", + idempotency=textwrap.dedent( + """\ + Feature: Idempotent code generation + + Scenario: First scenario + Given I have a step + + Scenario: Second scenario + Given I have a step + """ + ), + ) + + first = pytester.run("pytest-bdd", "generate", "idempotency.feature") + second = pytester.run("pytest-bdd", "generate", "idempotency.feature") + assert str(first.stdout) == str(second.stdout) + + pytester.makepyfile(str(first.stdout) + "\n\nscenarios('idempotency.feature')\n") + result = pytester.runpytest("--collect-only", "-q") + collected = [line for line in result.stdout.lines if "::test_" in line] + assert len(collected) == 2 + + def test_unicode_characters(pytester, monkeypatch): """Test generating code with unicode characters.