diff --git a/pytest_bdd/feature.py b/pytest_bdd/feature.py index c99ff9f..2832cc9 100644 --- a/pytest_bdd/feature.py +++ b/pytest_bdd/feature.py @@ -146,7 +146,7 @@ class Feature(object): if mode == SCENARIO: self.scenarios[line] = scenario = Scenario(line) else: - scenario.add_step(line) + scenario.add_step(step_name=line, step_type=mode) @classmethod def get_feature(cls, filename): @@ -176,7 +176,12 @@ class Scenario(object): self.params = set() self.steps = [] - def add_step(self, step): - """Add step.""" - self.params.update(get_step_params(step)) - self.steps.append(step) + def add_step(self, step_name, step_type): + """Add step to the scenario. + + :param step_name: Step name. + :param step_type: Step type. + + """ + self.params.update(get_step_params(step_name)) + self.steps.append((step_name, step_type)) diff --git a/pytest_bdd/scenario.py b/pytest_bdd/scenario.py index 6f9e172..d58b642 100644 --- a/pytest_bdd/scenario.py +++ b/pytest_bdd/scenario.py @@ -18,6 +18,7 @@ from _pytest import python from pytest_bdd.feature import Feature # pragma: no cover from pytest_bdd.steps import recreate_function +from pytest_bdd.types import GIVEN class ScenarioNotFound(Exception): # pragma: no cover @@ -28,6 +29,14 @@ class NotEnoughScenarioParams(Exception): # pragma: no cover """Scenario function doesn't take enough parameters in the arguments.""" +class StepTypeError(Exception): # pragma: no cover + """Step definition is not of the type expected in the scenario.""" + + +class GivenAlreadyUsed(Exception): # pragma: no cover + """Fixture that implements the Given has been already used.""" + + def _find_step_function(request, name): """Match the step defined by the regular expression pattern. @@ -92,10 +101,29 @@ def scenario(feature_name, scenario_name): ) ) + givens = set() # Execute scenario steps - for step in scenario.steps: - step_func = _find_step_function(request, step) + for step_name, step_type in scenario.steps: + step_func = _find_step_function(request, step_name) + + # Check the step types are called in the correct order + if step_func.step_type != step_type: + raise StepTypeError('Wrong step type "{0}", expected {1}.'.format(step_func.step_type, step_type)) + + # Check if the fixture that implements given step has not been yet used by another given step + if step_type == GIVEN: + if step_func.__name__ in givens: + raise GivenAlreadyUsed( + 'Fixture "{0}" that implements this given step "{1}" has been already used.'.format( + step_func.__name__, step_name, + ) + ) + givens.add(step_func.__name__) + + # Get the step argument values kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in inspect.getargspec(step_func).args) + + # Execute the step step_func(**kwargs) _scenario.pytestbdd_params = set() diff --git a/pytest_bdd/steps.py b/pytest_bdd/steps.py index 33476a2..3312958 100644 --- a/pytest_bdd/steps.py +++ b/pytest_bdd/steps.py @@ -48,7 +48,7 @@ PY3 = sys.version_info[0] >= 3 # pragma: no cover class StepError(Exception): # pragma: no cover """Step declaration error.""" -RE_TYPE = type(re.compile('')) +RE_TYPE = type(re.compile('')) # pragma: no cover def given(name, fixture=None): @@ -64,8 +64,12 @@ def given(name, fixture=None): if fixture is not None: module = get_caller_module() - func = lambda: lambda request: request.getfuncargvalue(fixture) - contribute_to_module(module, remove_prefix(name), pytest.fixture(func)) + step_func = lambda request: request.getfuncargvalue(fixture) + step_func.step_type = GIVEN + step_func.__name__ = fixture + func = pytest.fixture(lambda: step_func) + func.__doc__ = 'Alias for the "{0}" fixture.'.format(fixture) + contribute_to_module(module, remove_prefix(name), func) return _not_a_fixture_decorator return _step_decorator(GIVEN, name) @@ -134,12 +138,13 @@ def _step_decorator(step_type, step_name): step_func.__doc__ = func.__doc__ step_func.__name__ = step_name + step_func.step_type = step_type @pytest.fixture def lazy_step_func(): return step_func - # Preserve a docstring + # Preserve the docstring lazy_step_func.__doc__ = func.__doc__ if pattern: diff --git a/tests/regex/__init__.py b/tests/args/__init__.py similarity index 100% rename from tests/regex/__init__.py rename to tests/args/__init__.py diff --git a/tests/regex/regex_steps.feature b/tests/args/args_steps.feature similarity index 90% rename from tests/regex/regex_steps.feature rename to tests/args/args_steps.feature index a7a14e4..5d59789 100644 --- a/tests/regex/regex_steps.feature +++ b/tests/args/args_steps.feature @@ -1,6 +1,5 @@ Scenario: Every step takes a parameter with the same name Given I have 1 Euro - #And I have 2 Euro When I pay 2 Euro And I pay 1 Euro Then I should have 0 Euro diff --git a/tests/regex/conftest.py b/tests/args/conftest.py similarity index 100% rename from tests/regex/conftest.py rename to tests/args/conftest.py diff --git a/tests/regex/subfolder/__init__.py b/tests/args/subfolder/__init__.py similarity index 100% rename from tests/regex/subfolder/__init__.py rename to tests/args/subfolder/__init__.py diff --git a/tests/regex/subfolder/regex.feature b/tests/args/subfolder/args.feature similarity index 65% rename from tests/regex/subfolder/regex.feature rename to tests/args/subfolder/args.feature index ea66a27..e9b1092 100644 --- a/tests/regex/subfolder/regex.feature +++ b/tests/args/subfolder/args.feature @@ -1,4 +1,4 @@ -Scenario: Executed with steps matching regex step definitons +Scenario: Executed with steps matching step definitons with arguments Given I have a foo fixture with value "foo" And there is a list When I append 1 to the list diff --git a/tests/regex/subfolder/test_regex.py b/tests/args/subfolder/test_args.py similarity index 76% rename from tests/regex/subfolder/test_regex.py rename to tests/args/subfolder/test_args.py index c71b5c7..364bd00 100644 --- a/tests/regex/subfolder/test_regex.py +++ b/tests/args/subfolder/test_args.py @@ -1,7 +1,10 @@ from pytest_bdd import scenario, given, then -test_steps = scenario('regex.feature', 'Executed with steps matching regex step definitons') +test_steps = scenario( + 'args.feature', + 'Executed with steps matching step definitons with arguments', +) @given('I have a foo fixture with value "foo"') diff --git a/tests/regex/test_regex_steps.py b/tests/args/test_args_steps.py similarity index 88% rename from tests/regex/test_regex_steps.py rename to tests/args/test_args_steps.py index a974b48..061ed74 100644 --- a/tests/regex/test_regex_steps.py +++ b/tests/args/test_args_steps.py @@ -4,14 +4,13 @@ from pytest_bdd import scenario, given, when, then test_steps = scenario( - 'regex_steps.feature', + 'args_steps.feature', 'Every step takes a parameter with the same name' ) @pytest.fixture def values(): - #return ['1', '2', '2', '1', '0', '999999'] return ['1', '2', '1', '0', '999999'] diff --git a/tox.ini b/tox.ini index 955f882..7d76597 100644 --- a/tox.ini +++ b/tox.ini @@ -5,18 +5,17 @@ indexserver= pypi = https://pypi.python.org/simple [testenv] -commands= py.test --pep8 --junitxml={envlogdir}/junit-{envname}.xml +commands= py.test tests --pep8 --junitxml={envlogdir}/junit-{envname}.xml deps = -r{toxinidir}/requirements-testing.txt [testenv:py27-coverage] -commands= py.test --cov=pytest_bdd --pep8 --junitxml={envlogdir}/junit-{envname}.xml +commands= py.test tests --cov=pytest_bdd --pep8 --junitxml={envlogdir}/junit-{envname}.xml [testenv:py27-xdist] basepython=python2.7 commands= - py.test -n3 -rfsxX \ + py.test tests -n3 -rfsxX \ --junitxml={envlogdir}/junit-{envname}.xml [pytest] -addopts=tests pep8maxlinelength=120