diff --git a/CHANGES.rst b/CHANGES.rst index a214d99..829241c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,9 @@ Changelog ----- - Pure pytest parametrization for scenario outlines (bubenkoff) -- Splitting scenario decorated and non-decorated variants (bubenkoff) +- Argumented steps now support converters (transformations) (bubenkoff) +- scenario supports only decorator form (bubenkoff) +- Code generation refactoring and cleanup (bubenkoff) 1.0.0 diff --git a/README.rst b/README.rst index a14903a..d1a4ec3 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ containing the side effects of the Gherkin imperative declarations. Install pytest-bdd -================== +------------------ :: @@ -31,7 +31,7 @@ Install pytest-bdd Example -======= +------- publish\_article.feature: @@ -54,7 +54,9 @@ test\_publish\_article.py: from pytest_bdd import scenario, given, when, then - test_publish = scenario('publish_article.feature', 'Publishing the article') + @scenario('publish_article.feature', 'Publishing the article') + def test_publish(): + pass @given('I have an article') @@ -85,7 +87,7 @@ test\_publish\_article.py: Step aliases -============ +------------ Sometimes it is needed to declare the same fixtures or steps with the different names for better readability. In order to use the same step @@ -118,7 +120,7 @@ default author. Step arguments -============== +-------------- Often it's possible to reuse steps giving them a parameter(s). This allows to have single implementation and multiple use, so less code. @@ -145,7 +147,11 @@ The code will look like: import re from pytest_bdd import scenario, given, when, then - test_arguments = scenario('arguments.feature', 'Arguments for given, when, thens') + + @scenario('arguments.feature', 'Arguments for given, when, thens') + def test_arguments(): + pass + @given(re.compile('there are (?P\d+) cucumbers'), converters=dict(start=int)) def start_cucumbers(start): @@ -167,15 +173,15 @@ different than strings. Scenario parameters -=================== -Scenario function/decorator can accept such optional keyword arguments: +------------------- +Scenario decorator can accept such optional keyword arguments: * `encoding` - decode content of feature file in specific encoding. UTF-8 is default. * `example_converters` - mapping to pass functions to convert example values provided in feature files. Scenario outlines -================= +----------------- Scenarios can be parametrized to cover few cases. In Gherkin the variable templates are written using corner braces as . @@ -195,6 +201,24 @@ Example: | start | eat | left | | 12 | 5 | 7 | +pytest-bdd feature file format also supports example tables in different way: + + +.. code-block:: feature + + Scenario Outline: Outlined given, when, thens + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + Examples: Vertical + | start | 12 | 2 | + | eat | 5 | 1 | + | left | 7 | 1 | + +This form allows to have tables with lots of columns keeping the maximum text width predictable without significant +readability change. + The code will look like: @@ -203,11 +227,13 @@ The code will look like: from pytest_bdd import given, when, then, scenario - test_outlined = scenario( + @scenario( 'outline.feature', 'Outlined given, when, thens', example_converters=dict(start=int, eat=float, left=str) ) + def test_outlined(): + pass @given('there are cucumbers') @@ -242,6 +268,7 @@ The code will look like: import pytest from pytest_bdd import mark, given, when, then + # Here we use pytest to parametrize the test with the parameters table @pytest.mark.parametrize( ['start', 'eat', 'left'], @@ -276,7 +303,7 @@ The significant downside of this approach is inability to see the test table fro Test setup -========== +---------- Test setup is implemented within the Given section. Even though these steps are executed imperatively to apply possible side-effects, pytest-bdd is trying @@ -366,7 +393,7 @@ Will raise an exception if the step is using the regular expression pattern. Reusing fixtures -================ +---------------- Sometimes scenarios define new names for the fixture that can be inherited. Fixtures can be reused with other names using given(): @@ -377,7 +404,7 @@ inherited. Fixtures can be reused with other names using given(): Reusing steps -============= +------------- It is possible to define some common steps in the parent conftest.py and simply expect them in the child test file. @@ -410,14 +437,16 @@ test\_common.py: .. code-block:: python - test_conftest = scenario('common_steps.feature', 'All steps are declared in the conftest') + @scenario('common_steps.feature', 'All steps are declared in the conftest') + def test_conftest(): + pass There are no definitions of the steps in the test file. They were collected from the parent conftests. Feature file paths -================== +------------------ But default, pytest-bdd will use current module’s path as base path for finding feature files, but this behaviour can be changed by having @@ -436,11 +465,14 @@ test\_publish\_article.py: def pytestbdd_feature_base_dir(): return '/home/user/projects/foo.bar/features' - test_publish = scenario('publish_article.feature', 'Publishing the article') + + @scenario('publish_article.feature', 'Publishing the article') + def test_publish(): + pass Avoid retyping the feature file name -==================================== +------------------------------------ If you want to avoid retyping the feature file name when defining your scenarios in a test file, use functools.partial. This will make your life much easier when defining multiple scenarios in a test file. @@ -459,15 +491,22 @@ test\_publish\_article.py: scenario = partial(pytest_bdd.scenario, '/path/to/publish_article.feature') - test_publish = scenario('Publishing the article') - test_publish_unprivileged = scenario('Publishing the article as unprivileged user') + + @scenario('Publishing the article') + def test_publish(): + pass + + + @scenario('Publishing the article as unprivileged user') + def test_publish_unprivileged(): + pass You can learn more about `functools.partial `_ in the Python docs. Hooks -===== +----- pytest-bdd exposes several pytest `hooks `_ which might be helpful building useful reporting, visualization, etc on top of it: @@ -488,7 +527,15 @@ which might be helpful building useful reporting, visualization, etc on top of i Browser testing -=============== +--------------- + +Tools recommended to use for browser testing: + + * pytest-splinter - pytest splinter integration for the real browser testing + + +Migration of your tests from versions 0.x.x-1.x.x +------------------------------------------------- Tools recommended to use for browser testing: @@ -496,7 +543,7 @@ Tools recommended to use for browser testing: License -======= +------- This software is licensed under the `MIT license `_. diff --git a/pytest_bdd/exceptions.py b/pytest_bdd/exceptions.py new file mode 100644 index 0000000..e59febb --- /dev/null +++ b/pytest_bdd/exceptions.py @@ -0,0 +1,25 @@ +"""pytest-bdd Exceptions.""" + + +class ScenarioIsDecoratorOnly(Exception): + """Scenario can be only used as decorator.""" + + +class ScenarioValidationError(Exception): + """Base class for scenario validation.""" + + +class ScenarioNotFound(ScenarioValidationError): # pragma: no cover + """Scenario Not Found""" + + +class ScenarioExamplesNotValidError(ScenarioValidationError): # pragma: no cover + """Scenario steps argumets do not match declared scenario examples.""" + + +class StepTypeError(ScenarioValidationError): # pragma: no cover + """Step definition is not of the type expected in the scenario.""" + + +class GivenAlreadyUsed(ScenarioValidationError): # pragma: no cover + """Fixture that implements the Given has been already used.""" diff --git a/pytest_bdd/feature.py b/pytest_bdd/feature.py index b933422..786fc24 100644 --- a/pytest_bdd/feature.py +++ b/pytest_bdd/feature.py @@ -26,10 +26,8 @@ one line. import re # pragma: no cover import sys # pragma: no cover -from pytest_bdd.types import ( - FEATURE, SCENARIO, SCENARIO_OUTLINE, EXAMPLES, EXAMPLES_HEADERS, EXAMPLE_LINE, GIVEN, WHEN, - THEN # pragma: no cover -) +from pytest_bdd import types # pragma: no cover +from pytest_bdd import exceptions # pragma: no cover class FeatureError(Exception): # pragma: no cover @@ -48,16 +46,17 @@ class FeatureError(Exception): # pragma: no cover features = {} # pragma: no cover -STEP_PREFIXES = { # pragma: no cover - 'Feature: ': FEATURE, - 'Scenario Outline: ': SCENARIO_OUTLINE, - 'Examples:': EXAMPLES, - 'Scenario: ': SCENARIO, - 'Given ': GIVEN, - 'When ': WHEN, - 'Then ': THEN, - 'And ': None, # Unknown step type -} +STEP_PREFIXES = [ # pragma: no cover + ('Feature: ', types.FEATURE), + ('Scenario Outline: ', types.SCENARIO_OUTLINE), + ('Examples: Vertical', types.EXAMPLES_VERTICAL), + ('Examples:', types.EXAMPLES), + ('Scenario: ', types.SCENARIO), + ('Given ', types.GIVEN), + ('When ', types.WHEN), + ('Then ', types.THEN), + ('And ', None), # Unknown step type +] COMMENT_SYMBOLS = '#' # pragma: no cover @@ -70,9 +69,9 @@ def get_step_type(line): :param line: Line of the Feature file :return: SCENARIO, GIVEN, WHEN, THEN, or `None` if can't be detected. """ - for prefix in STEP_PREFIXES: + for prefix, _type in STEP_PREFIXES: if line.startswith(prefix): - return STEP_PREFIXES[prefix] + return _type def get_step_params(name): @@ -104,7 +103,7 @@ def remove_prefix(line): :return: Line without the prefix. """ - for prefix in STEP_PREFIXES: + for prefix, _ in STEP_PREFIXES: if line.startswith(prefix): return line[len(prefix):].strip() return line @@ -156,20 +155,21 @@ class Feature(object): continue mode = get_step_type(line) or mode - if mode == GIVEN and prev_mode not in (GIVEN, SCENARIO, SCENARIO_OUTLINE): + if mode == types.GIVEN and prev_mode not in (types.GIVEN, types.SCENARIO, types.SCENARIO_OUTLINE): raise FeatureError('Given steps must be the first in withing the Scenario', line_number, line) - if mode == WHEN and prev_mode not in (SCENARIO, SCENARIO_OUTLINE, GIVEN, WHEN): + if mode == types.WHEN and prev_mode not in ( + types.SCENARIO, types.SCENARIO_OUTLINE, types.GIVEN, types.WHEN): raise FeatureError('When steps must be the first or follow Given steps', line_number, line) - if mode == THEN and prev_mode not in (GIVEN, WHEN, THEN): + if mode == types.THEN and prev_mode not in (types.GIVEN, types.WHEN, types.THEN): raise FeatureError('Then steps must follow Given or When steps', line_number, line) - if mode == FEATURE: - if prev_mode != FEATURE: + if mode == types.FEATURE: + if prev_mode != types.FEATURE: self.name = remove_prefix(line) else: description.append(line) @@ -178,16 +178,21 @@ class Feature(object): # Remove Feature, Given, When, Then, And line = remove_prefix(line) - if mode in [SCENARIO, SCENARIO_OUTLINE]: - self.scenarios[line] = scenario = Scenario(line) - elif mode == EXAMPLES: - mode = EXAMPLES_HEADERS - elif mode == EXAMPLES_HEADERS: + if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]: + self.scenarios[line] = scenario = Scenario(self, line) + elif mode == types.EXAMPLES: + mode = types.EXAMPLES_HEADERS + elif mode == types.EXAMPLES_VERTICAL: + mode = types.EXAMPLE_LINE_VERTICAL + elif mode == types.EXAMPLES_HEADERS: scenario.set_param_names([l.strip() for l in line.split('|') if l.strip()]) - mode = EXAMPLE_LINE - elif mode == EXAMPLE_LINE: + mode = types.EXAMPLE_LINE + elif mode == types.EXAMPLE_LINE: scenario.add_example([l.strip() for l in line.split('|') if l.strip()]) - elif mode and mode != FEATURE: + elif mode == types.EXAMPLE_LINE_VERTICAL: + line = [l.strip() for l in line.split('|') if l.strip()] + scenario.add_example_row(line[0], line[1:]) + elif mode and mode != types.FEATURE: scenario.add_step(step_name=line, step_type=mode) self.description = u'\n'.join(description) @@ -215,12 +220,15 @@ class Feature(object): class Scenario(object): """Scenario.""" - def __init__(self, name): + def __init__(self, feature, name, example_converters=None): + self.feature = feature self.name = name self.params = set() self.steps = [] self.example_params = [] self.examples = [] + self.vertical_examples = [] + self.example_converters = example_converters def add_step(self, step_name, step_type): """Add step to the scenario. @@ -249,6 +257,58 @@ class Scenario(object): """ self.examples.append(values) + def add_example_row(self, param, values): + """Add example row. + + :param param: `str` parameter name + :param values: `list` of `string` parameter values + + """ + if param in self.example_params: + raise exceptions.ScenarioExamplesNotValidError( + """Scenario "{0}" in the feature "{1}" has not valid examples. """ + """Example rows should contain unique parameters. {2} appeared more than once.""".format( + self.name, self.feature.filename, param, + ) + ) + self.example_params.append(param) + self.vertical_examples.append(values) + + def get_params(self): + """Get scenario pytest parametrization table.""" + param_count = len(self.example_params) + if self.vertical_examples and not self.examples: + for value_index in range(len(self.vertical_examples[0])): + example = [] + for param_index in range(param_count): + example.append(self.vertical_examples[param_index][value_index]) + self.examples.append(example) + + if self.examples: + params = [] + for example in self.examples: + for index, param in enumerate(self.example_params): + if self.example_converters and param in self.example_converters: + example[index] = self.example_converters[param](example[index]) + params.append(example) + return [self.example_params, params] + else: + return [] + + def validate(self): + """Validate the scenario. + + :raises: `ScenarioValidationError` + + """ + if self.params and self.example_params and self.params != set(self.example_params): + raise exceptions.ScenarioExamplesNotValidError( + """Scenario "{0}" in the feature "{1}" has not valid examples. """ + """Set of step parameters {2} should match set of example values {3}.""".format( + self.name, self.feature.filename, sorted(self.params), sorted(self.example_params), + ) + ) + class Step(object): """Step.""" diff --git a/pytest_bdd/scenario.py b/pytest_bdd/scenario.py index 864a16f..7102bec 100644 --- a/pytest_bdd/scenario.py +++ b/pytest_bdd/scenario.py @@ -26,34 +26,11 @@ from _pytest import python from pytest_bdd.feature import Feature, force_encode # pragma: no cover from pytest_bdd.steps import execute, recreate_function, get_caller_module, get_caller_function from pytest_bdd.types import GIVEN +from pytest_bdd import exceptions from pytest_bdd import plugin -class ScenarioIsDecoratorOnly(Exception): - """Scenario can be only used as decorator.""" - - -class ScenarioValidationError(Exception): - """Base class for scenario validation.""" - - -class ScenarioNotFound(ScenarioValidationError): # pragma: no cover - """Scenario Not Found""" - - -class ScenarioExamplesNotValidError(ScenarioValidationError): # pragma: no cover - """Scenario steps argumets do not match declared scenario examples.""" - - -class StepTypeError(ScenarioValidationError): # pragma: no cover - """Step definition is not of the type expected in the scenario.""" - - -class GivenAlreadyUsed(ScenarioValidationError): # pragma: no cover - """Fixture that implements the Given has been already used.""" - - def _inject_fixture(request, arg, value): """Inject fixture into pytest fixture request. @@ -122,17 +99,6 @@ def _find_step_function(request, name, encoding): raise -def _validate_scenario(feature, scenario): - """Validate the scenario.""" - if scenario.params and scenario.example_params and scenario.params != set(scenario.example_params): - raise ScenarioExamplesNotValidError( - """Scenario "{0}" in the feature "{1}" has not valid examples. """ - """Set of step parameters {2} should match set of example values {3}.""".format( - scenario.name, feature.filename, sorted(scenario.params), sorted(scenario.example_params), - ) - ) - - def _execute_step_function(request, feature, step, step_func, example=None): """Execute step function.""" kwargs = {} @@ -176,20 +142,20 @@ def _execute_scenario(feature, scenario, request, encoding, example=None): try: # Check the step types are called in the correct order if step_func.step_type != step.type: - raise StepTypeError( + raise exceptions.StepTypeError( 'Wrong step type "{0}" while "{1}" is expected.'.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.fixture in givens: - raise GivenAlreadyUsed( + raise exceptions.GivenAlreadyUsed( 'Fixture "{0}" that implements this "{1}" given step has been already used.'.format( step_func.fixture, step.name, ) ) givens.add(step_func.fixture) - except ScenarioValidationError as exception: + except exceptions.ScenarioValidationError as exception: request.config.hook.pytest_bdd_step_validation_error( request=request, feature=feature, scenario=scenario, step=step, step_func=step_func, exception=exception) @@ -228,47 +194,15 @@ def get_fixture(caller_module, fixture, path=None, module=None): return get_fixture(caller_module, fixture, path=os.path.dirname(path), module=module) -def scenario( - feature_name, scenario_name, encoding='utf-8', example_converters=None, - caller_module=None, caller_function=None): - """Scenario.""" - - caller_module = caller_module or get_caller_module() - caller_function = caller_function or get_caller_function() - - # Get the feature - base_path = get_fixture(caller_module, 'pytestbdd_feature_base_dir') - feature_path = op.abspath(op.join(base_path, feature_name)) - feature = Feature.get_feature(feature_path, encoding=encoding) - - # Get the scenario - try: - scenario = feature.scenarios[scenario_name] - except KeyError: - raise ScenarioNotFound( - 'Scenario "{0}" in feature "{1}" is not found.'.format(scenario_name, feature_name) - ) - - # Validate the scenario - _validate_scenario(feature, scenario) - - if scenario.examples: - params = [] - for example in scenario.examples: - for index, param in enumerate(scenario.example_params): - if example_converters and param in example_converters: - example[index] = example_converters[param](example[index]) - params.append(example) - params = [scenario.example_params, params] - else: - params = [] - - g = globals().copy() - g.update(locals()) +def _get_scenario_decorator( + feature, feature_name, scenario, scenario_name, caller_module, caller_function, encoding): + """Get scenario decorator.""" + g = locals() + g['_execute_scenario'] = _execute_scenario def decorator(_pytestbdd_function): if isinstance(_pytestbdd_function, python.FixtureRequest): - raise ScenarioIsDecoratorOnly( + raise exceptions.ScenarioIsDecoratorOnly( 'scenario function can only be used as a decorator. Refer to the documentation.') g.update(locals()) @@ -293,6 +227,8 @@ def scenario( _scenario = recreate_function( g[_pytestbdd_function.__name__], module=caller_module, firstlineno=caller_function.f_lineno) + params = scenario.get_params() + if params: _scenario = pytest.mark.parametrize(*params)(_scenario) @@ -300,6 +236,34 @@ def scenario( feature_name=feature_name, scenario_name=scenario_name) return _scenario - decorator = recreate_function(decorator, module=caller_module, firstlineno=caller_function.f_lineno) + return recreate_function(decorator, module=caller_module, firstlineno=caller_function.f_lineno) - return decorator + +def scenario( + feature_name, scenario_name, encoding='utf-8', example_converters=None, + caller_module=None, caller_function=None): + """Scenario.""" + + caller_module = caller_module or get_caller_module() + caller_function = caller_function or get_caller_function() + + # Get the feature + base_path = get_fixture(caller_module, 'pytestbdd_feature_base_dir') + feature_path = op.abspath(op.join(base_path, feature_name)) + feature = Feature.get_feature(feature_path, encoding=encoding) + + # Get the scenario + try: + scenario = feature.scenarios[scenario_name] + except KeyError: + raise exceptions.ScenarioNotFound( + 'Scenario "{0}" in feature "{1}" is not found.'.format(scenario_name, feature_name) + ) + + scenario.example_converters = example_converters + + # Validate the scenario + scenario.validate() + + return _get_scenario_decorator( + feature, feature_name, scenario, scenario_name, caller_module, caller_function, encoding) diff --git a/pytest_bdd/types.py b/pytest_bdd/types.py index 8bd9f50..f8748f7 100644 --- a/pytest_bdd/types.py +++ b/pytest_bdd/types.py @@ -3,8 +3,10 @@ FEATURE = 'feature' # pragma: no cover SCENARIO_OUTLINE = 'scenario outline' # pragma: no cover EXAMPLES = 'examples' # pragma: no cover +EXAMPLES_VERTICAL = 'examples vertical' # pragma: no cover EXAMPLES_HEADERS = 'example headers' # pragma: no cover EXAMPLE_LINE = 'example line' # pragma: no cover +EXAMPLE_LINE_VERTICAL = 'example line vertical' # pragma: no cover SCENARIO = 'scenario' # pragma: no cover GIVEN = 'given' # pragma: no cover WHEN = 'when' # pragma: no cover diff --git a/tests/args/test_args_steps.py b/tests/args/test_args_steps.py index 4f1ab04..271dfd8 100644 --- a/tests/args/test_args_steps.py +++ b/tests/args/test_args_steps.py @@ -4,7 +4,7 @@ import functools import re import pytest from pytest_bdd import scenario, given, when, then -from pytest_bdd.scenario import GivenAlreadyUsed +from pytest_bdd import exceptions @scenario( @@ -73,5 +73,5 @@ def test_multiple_given(request): ) def test(): pass - with pytest.raises(GivenAlreadyUsed): + with pytest.raises(exceptions.GivenAlreadyUsed): test(request) diff --git a/tests/feature/outline.feature b/tests/feature/outline.feature index 07f27dc..b8e6368 100644 --- a/tests/feature/outline.feature +++ b/tests/feature/outline.feature @@ -28,3 +28,14 @@ Scenario Outline: Outlined with some examples failing | start | eat | left | | 0 | 5 | 5 | | 12 | 5 | 7 | + + +Scenario Outline: Outlined with vertical example table + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + Examples: Vertical + | start | 12 | 2 | + | eat | 5 | 1 | + | left | 7 | 1 | diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index e60bce4..8bf1c19 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -4,7 +4,7 @@ import re import pytest from pytest_bdd import given, when, then, scenario -from pytest_bdd.scenario import ScenarioExamplesNotValidError +from pytest_bdd import exceptions @scenario( @@ -13,7 +13,8 @@ from pytest_bdd.scenario import ScenarioExamplesNotValidError example_converters=dict(start=int, eat=float, left=str) ) def test_outlined(): - assert 1 + assert test_outlined.parametrize.args == ( + [u'start', u'eat', u'left'], [[12, 5.0, '7'], [5, 4.0, '1']]) @given('there are cucumbers') @@ -39,7 +40,7 @@ def should_have_left_cucumbers(start_cucumbers, start, eat, left): def test_wrongly_outlined(request): """Test parametrized scenario when the test function lacks parameters.""" - with pytest.raises(ScenarioExamplesNotValidError) as exc: + with pytest.raises(exceptions.ScenarioExamplesNotValidError) as exc: @scenario( 'outline.feature', 'Outlined with wrong examples', @@ -67,3 +68,14 @@ def other_fixture(request): ) def test_outlined_with_other_fixtures(other_fixture): """Test outlined scenario also using other parametrized fixture.""" + + +@scenario( + 'outline.feature', + 'Outlined with vertical example table', + example_converters=dict(start=int, eat=float, left=str) +) +def test_vertical_example(): + """Test outlined scenario with vertical examples table.""" + assert test_vertical_example.parametrize.args == ( + [u'start', u'eat', u'left'], [[12, 5.0, '7'], [2, 1.0, '1']]) diff --git a/tests/feature/test_scenario.py b/tests/feature/test_scenario.py index 95a7efc..79e67af 100644 --- a/tests/feature/test_scenario.py +++ b/tests/feature/test_scenario.py @@ -1,13 +1,14 @@ +"""Test scenario decorator.""" import pytest from pytest_bdd import scenario -from pytest_bdd.scenario import ScenarioNotFound +from pytest_bdd import exceptions def test_scenario_not_found(request): """Test the situation when scenario is not found.""" - with pytest.raises(ScenarioNotFound): + with pytest.raises(exceptions.ScenarioNotFound): scenario( 'not_found.feature', 'NOT FOUND' diff --git a/tests/feature/test_steps.py b/tests/feature/test_steps.py index 3ac287f..0fdf726 100644 --- a/tests/feature/test_steps.py +++ b/tests/feature/test_steps.py @@ -1,7 +1,7 @@ import pytest from pytest_bdd import scenario, given, when, then -from pytest_bdd.scenario import GivenAlreadyUsed +from pytest_bdd import exceptions @scenario('steps.feature', 'Executed step by step') @@ -84,7 +84,7 @@ def test_multiple_given(request): def test(): pass - with pytest.raises(GivenAlreadyUsed): + with pytest.raises(exceptions.GivenAlreadyUsed): test(request) diff --git a/tests/feature/test_wrong.py b/tests/feature/test_wrong.py index 0edf901..ea8db09 100644 --- a/tests/feature/test_wrong.py +++ b/tests/feature/test_wrong.py @@ -1,9 +1,11 @@ """Test wrong feature syntax.""" +import re + import pytest from pytest_bdd import scenario, given, when, then from pytest_bdd.feature import FeatureError -from pytest_bdd.scenario import StepTypeError +from pytest_bdd import exceptions @given('something') @@ -57,10 +59,9 @@ def test_wrong_type_order(request, scenario_name): def test_wrong_type_order(request): pass - with pytest.raises(StepTypeError) as excinfo: + with pytest.raises(exceptions.StepTypeError) as excinfo: test_wrong_type_order(request) - - excinfo # TODO: assert the exception args from parameters + assert re.match(r'Wrong step type \"(\w+)\" while \"(\w+)\" is expected\.', excinfo.value.args[0]) def test_verbose_output(request):