PoC @scenario_class(...) class generator
This commit is contained in:
parent
bf88c44ff4
commit
3700cb4296
|
@ -1,7 +1,7 @@
|
|||
"""pytest-bdd public API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pytest_bdd.scenario import scenario, scenarios
|
||||
from pytest_bdd.scenario import scenario, scenario_class, scenarios
|
||||
from pytest_bdd.steps import given, step, then, when
|
||||
|
||||
__all__ = ["given", "when", "step", "then", "scenario", "scenarios"]
|
||||
|
|
|
@ -367,9 +367,54 @@ def scenarios(*feature_paths: str, **kwargs: Any) -> None:
|
|||
|
||||
for test_name in get_python_name_generator(scenario_name):
|
||||
if test_name not in caller_locals:
|
||||
# found an unique test name
|
||||
# found a unique test name
|
||||
caller_locals[test_name] = _scenario
|
||||
break
|
||||
found = True
|
||||
if not found:
|
||||
raise exceptions.NoScenariosFound(abs_feature_paths)
|
||||
|
||||
|
||||
# TODO: Test this
|
||||
def to_camel_case(string: str) -> str:
|
||||
"""Convert a string to camelCase."""
|
||||
words = [word.strip() for word in re.split(r"([A-Z][a-z]+)", string)]
|
||||
return "".join(word.capitalize() for word in words if word.isalpha())
|
||||
|
||||
|
||||
def scenario_class(feature_path: str, cls_name: str | None = None, **kwargs: Any) -> type[object]:
|
||||
caller_path = get_caller_module_path()
|
||||
|
||||
features_base_dir = kwargs.get("features_base_dir")
|
||||
if features_base_dir is None:
|
||||
features_base_dir = get_features_base_dir(caller_path)
|
||||
|
||||
if not os.path.isabs(feature_path):
|
||||
feature_path = os.path.abspath(os.path.join(features_base_dir, feature_path))
|
||||
|
||||
base, name = os.path.split(feature_path)
|
||||
feature = get_feature(base, name, **kwargs)
|
||||
|
||||
if cls_name is None:
|
||||
cls_name = to_camel_case(feature.name)
|
||||
|
||||
cls = type(cls_name, (), {})
|
||||
|
||||
seen_test_names = set()
|
||||
|
||||
for scenario_name, _ in feature.scenarios.items():
|
||||
|
||||
@staticmethod # TODO: We should not require this.
|
||||
@scenario(feature.filename, scenario_name, **kwargs)
|
||||
def _test_scenario() -> None:
|
||||
pass # pragma: no cover
|
||||
|
||||
for test_name in get_python_name_generator(scenario_name):
|
||||
# add it to the test class
|
||||
if test_name in seen_test_names:
|
||||
continue
|
||||
_test_scenario.__name__ = test_name
|
||||
setattr(cls, test_name, _test_scenario)
|
||||
break
|
||||
|
||||
return cls
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Test scenarios shortcut."""
|
||||
import textwrap
|
||||
|
||||
from pytest_bdd.utils import collect_dumped_objects
|
||||
|
||||
|
||||
def test_scenarios(testdir, pytest_params):
|
||||
"""Test scenarios shortcut (used together with @scenario for individual test override)."""
|
||||
|
@ -73,6 +75,100 @@ def test_scenarios(testdir, pytest_params):
|
|||
result.stdout.fnmatch_lines(["*test_test_scenario_1 *bar!", "PASSED"])
|
||||
|
||||
|
||||
def test_scenarios_class(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
from pytest_bdd import given, scenario_class
|
||||
from pytest_bdd.utils import dump_obj
|
||||
|
||||
@given('I have a bar')
|
||||
def _():
|
||||
dump_obj('bar')
|
||||
return 'bar'
|
||||
|
||||
|
||||
@given('I have a foo')
|
||||
def _():
|
||||
dump_obj('foo')
|
||||
return 'foo'
|
||||
|
||||
|
||||
TestMyFeature = scenario_class("my_feature.feature", name="TestMyFeature")
|
||||
"""
|
||||
)
|
||||
|
||||
testdir.makefile(
|
||||
"feature",
|
||||
my_feature="""\
|
||||
Feature: Test feature
|
||||
Scenario: Scenario foo
|
||||
Given I have a foo
|
||||
Scenario: Scenario bar
|
||||
Given I have a bar
|
||||
""",
|
||||
)
|
||||
|
||||
result = testdir.runpytest("-s", "-v", "-o", "console_output_style=classic")
|
||||
result.assert_outcomes(passed=2)
|
||||
result.stdout.fnmatch_lines(["*TestMyFeature::test_scenario_bar*", "PASSED"])
|
||||
result.stdout.fnmatch_lines(["*TestMyFeature::test_scenario_foo*", "PASSED"])
|
||||
objs = collect_dumped_objects(result)
|
||||
assert objs == ["bar", "foo"]
|
||||
|
||||
|
||||
def test_scenarios_class_can_override_generated_tests(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
from pytest_bdd import given, scenario_class, parsers, scenario
|
||||
from pytest_bdd.utils import dump_obj
|
||||
|
||||
@given(parsers.parse('I have a {value}'))
|
||||
def _(value: str) -> str:
|
||||
dump_obj(f"Given I have a {value}")
|
||||
return value
|
||||
|
||||
|
||||
class TestMyFeature(scenario_class("my_feature.feature")):
|
||||
# TODO: We should not be required to use @staticmethod here
|
||||
@staticmethod
|
||||
# TODO: We should not be required to pass "my_feature.feature" here
|
||||
@scenario("my_feature.feature", "Scenario foo")
|
||||
def test_my_scenario_foo():
|
||||
dump_obj("overriding scenario foo")
|
||||
|
||||
@pytest.mark.skip(reason="testing marker")
|
||||
# TODO: We should not be required to use @staticmethod here
|
||||
@staticmethod
|
||||
@scenario("my_feature.feature", "Scenario bar")
|
||||
def test_my_scenario_bar_skipped():
|
||||
dump_obj("this should not be executed")
|
||||
"""
|
||||
)
|
||||
|
||||
testdir.makefile(
|
||||
"feature",
|
||||
my_feature="""\
|
||||
Feature: Test feature
|
||||
Scenario: Scenario bar
|
||||
Given I have a bar
|
||||
Scenario: Scenario foo
|
||||
Given I have a foo
|
||||
Scenario: Scenario baz
|
||||
Given I have a baz
|
||||
""",
|
||||
)
|
||||
|
||||
result = testdir.runpytest("-s", "-v", "-o", "console_output_style=classic")
|
||||
result.assert_outcomes(passed=2, skipped=1)
|
||||
result.stdout.fnmatch_lines(["*TestMyFeature::test_my_scenario_foo*", "PASSED"])
|
||||
result.stdout.fnmatch_lines(["*TestMyFeature::test_my_scenario_bar_skipped*", "SKIPPED"])
|
||||
result.stdout.fnmatch_lines(["*TestMyFeature::test_scenario_baz*", "PASSED"])
|
||||
[skip_msgs] = collect_dumped_objects(result)
|
||||
assert skip_msgs == ["overriding scenario foo", "Given I have a baz"]
|
||||
|
||||
|
||||
def test_scenarios_none_found(testdir, pytest_params):
|
||||
"""Test scenarios shortcut when no scenarios found."""
|
||||
testpath = testdir.makepyfile(
|
||||
|
|
Loading…
Reference in New Issue