Scenario outline implementation based on pure pytest parametrization

This commit is contained in:
Anatoly Bubenkov 2014-03-11 15:05:25 +01:00
parent f20c02a571
commit bca5206677
17 changed files with 314 additions and 174 deletions

View File

@ -1,6 +1,13 @@
Changelog
=========
2.0.0
-----
- Pure pytest parametrization for scenario outlines (bubenkoff)
- Splitting scenario decorated and non-decorated variants (bubenkoff)
1.0.0
-----

View File

@ -21,6 +21,7 @@ mentioned in the feature steps with dependency injection, which allows a true BD
just-enough specification of the requirements without maintaining any context object
containing the side effects of the Gherkin imperative declarations.
Install pytest-bdd
==================
@ -28,6 +29,7 @@ Install pytest-bdd
pip install pytest-bdd
Example
=======
@ -81,6 +83,7 @@ test\_publish\_article.py:
article.refresh() # Refresh the object in the SQLAlchemy session
assert article.is_published
Step aliases
============
@ -113,6 +116,7 @@ default author.
Given I'm the admin
And there is an article
Step arguments
==============
@ -143,28 +147,32 @@ The code will look like:
test_arguments = scenario('arguments.feature', 'Arguments for given, when, thens')
@given(re.compile('there are (?P<start>\d+) cucumbers'))
@given(re.compile('there are (?P<start>\d+) cucumbers'), converters=dict(start=int))
def start_cucumbers(start):
# note that you always get step arguments as strings, convert them on demand
start = int(start)
return dict(start=start, eat=0)
@when(re.compile('I eat (?P<eat>\d+) cucumbers'))
@when(re.compile('I eat (?P<eat>\d+) cucumbers'), converters=dict(eat=int))
def eat_cucumbers(start_cucumbers, eat):
eat = int(eat)
start_cucumbers['eat'] += eat
@then(re.compile('I should have (?P<left>\d+) cucumbers'))
@then(re.compile('I should have (?P<left>\d+) cucumbers'), converters=dict(left=int))
def should_have_left_cucumbers(start_cucumbers, start, left):
start, left = int(start), int(left)
assert start_cucumbers['start'] == start
assert start - start_cucumbers['eat'] == left
Example code also shows possibility to pass argument converters which may be useful if you need argument types
different than strings.
Scenario parameters
===================
Scenario can accept `encoding` param to decode content of feature file in specific encoding. UTF-8 is default.
Scenario function/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
=================
@ -174,7 +182,6 @@ templates are written using corner braces as <somevalue>.
`Scenario outlines <http://docs.behat.org/guides/1.gherkin.html#scenario-outlines>`_ are supported by pytest-bdd
exactly as it's described in be behave docs.
Example:
.. code-block:: feature
@ -199,42 +206,47 @@ The code will look like:
test_outlined = scenario(
'outline.feature',
'Outlined given, when, thens',
example_converters=dict(start=int, eat=float, left=str)
)
@given('there are <start> cucumbers')
def start_cucumbers(start):
return dict(start=int(start))
assert isinstance(start, int)
return dict(start=start)
@when('I eat <eat> cucumbers')
def eat_cucumbers(start_cucumbers, start, eat):
start_cucumbers['eat'] = int(eat)
def eat_cucumbers(start_cucumbers, eat):
assert isinstance(eat, float)
start_cucumbers['eat'] = eat
@then('I should have <left> cucumbers')
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
assert int(start) - int(eat) == int(left)
assert start_cucumbers['start'] == int(start)
assert start_cucumbers['eat'] == int(eat)
assert isinstance(left, str)
assert start - eat == int(left)
assert start_cucumbers['start'] == start
assert start_cucumbers['eat'] == eat
It's also possible to parametrize the scenario on the python side. This is done using pytest parametrization.
The reason for this is that it is very often that some simple pythonic type
is needed in the parameters like a datetime or a dictionary, which makes it
more difficult to express in the text files and preserve the correct format.
Example code also shows possibility to pass example converters which may be useful if you need parameter types
different than strings.
It's also possible to parametrize the scenario on the python side.
The reason for this is that it is sometimes not needed to mention example table for every scenario.
The code will look like:
.. code-block:: python
import pytest
from pytest_bdd import scenario, given, when, then
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'],
[(12, 5, 7)])
@scenario(
@mark.scenario(
'parametrized.feature',
'Parametrized given, when, thens',
)
@ -453,6 +465,7 @@ test\_publish\_article.py:
You can learn more about `functools.partial <http://docs.python.org/2/library/functools.html#functools.partial>`_ in the Python docs.
Hooks
=====
@ -474,15 +487,13 @@ which might be helpful building useful reporting, visualization, etc on top of i
* pytest_bdd_step_func_lookup_error(request, feature, scenario, step, exception) - Called when step lookup failed
Subplugins
==========
Browser testing
===============
The pytest BDD has plugin support, and the main purpose of plugins
(subplugins) is to provide useful and specialized fixtures.
Tools recommended to use for browser testing:
List of known subplugins:
* pytest-splinter - pytest splinter integration for the real browser testing
* pytest-bdd-splinter - collection of fixtures for the real browser BDD testing
License
=======

View File

@ -1,5 +1,5 @@
from pytest_bdd.steps import given, when, then # pragma: no cover
from pytest_bdd.scenario import scenario # pragma: no cover
from pytest_bdd import mark # pragma: no cover
__all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__] # pragma: no cover
__all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__, mark.__name__] # pragma: no cover

View File

@ -229,8 +229,9 @@ class Scenario(object):
:param step_type: Step type.
"""
self.params.update(get_step_params(step_name))
self.steps.append(Step(name=step_name, type=step_type))
params = get_step_params(step_name)
self.params.update(params)
self.steps.append(Step(name=step_name, type=step_type, params=params))
def set_param_names(self, keys):
"""Set parameter names.
@ -238,8 +239,7 @@ class Scenario(object):
:param names: `list` of `string` parameter names
"""
self.params.update(keys)
self.example_params = keys
self.example_params = [str(key) for key in keys]
def add_example(self, values):
"""Add example.
@ -253,6 +253,7 @@ class Scenario(object):
class Step(object):
"""Step."""
def __init__(self, name, type):
def __init__(self, name, type, params):
self.name = name
self.type = type
self.params = params

26
pytest_bdd/mark.py Normal file
View File

@ -0,0 +1,26 @@
"""Pytest-bdd markers."""
import inspect
from pytest_bdd.steps import recreate_function, get_caller_module, get_caller_function
from pytest_bdd import scenario as bdd_scenario
def scenario(feature_name, scenario_name, encoding='utf-8', example_converters=None):
"""Scenario. May be called both as decorator and as just normal function."""
caller_module = get_caller_module()
caller_function = get_caller_function()
def decorator(request):
_scenario = bdd_scenario(
feature_name, scenario_name, encoding=encoding, example_converters=example_converters,
caller_module=caller_module, caller_function=caller_function)
args = inspect.getargspec(request).args
_scenario = recreate_function(_scenario, name=request.__name__, module=caller_module, add_args=args)
return _scenario
return recreate_function(decorator, module=caller_module, firstlineno=caller_function.f_lineno)

View File

@ -11,15 +11,26 @@ test_publish_article = scenario(
)
"""
import collections
import os
import imp
import sys
import inspect # pragma: no cover
from os import path as op # pragma: no cover
import pytest
from future import utils as future_utils
from _pytest import python
from pytest_bdd.feature import Feature, force_encode # pragma: no cover
from pytest_bdd.steps import recreate_function, get_caller_module, get_caller_function
from pytest_bdd.types import GIVEN
from pytest_bdd import plugin
class ScenarioValidationError(Exception):
"""Base class for scenario validation."""
@ -29,8 +40,8 @@ class ScenarioNotFound(ScenarioValidationError): # pragma: no cover
"""Scenario Not Found"""
class NotEnoughScenarioParams(ScenarioValidationError): # pragma: no cover
"""Scenario function doesn't take enough parameters in the arguments."""
class ScenarioExamplesNotValidError(ScenarioValidationError): # pragma: no cover
"""Scenario steps argumets do not match declared scenario examples."""
class StepTypeError(ScenarioValidationError): # pragma: no cover
@ -101,7 +112,10 @@ def _find_step_function(request, name, encoding):
match = pattern.match(name) if pattern else None
if match:
converters = getattr(fixturedef.func, 'converters', {})
for arg, value in match.groupdict().items():
if arg in converters:
value = converters[arg](value)
_inject_fixture(request, arg, value)
return request.getfuncargvalue(pattern.pattern)
raise
@ -109,26 +123,58 @@ def _find_step_function(request, name, encoding):
def _validate_scenario(feature, scenario, request):
"""Validate the scenario."""
resolved_params = scenario.params.intersection(request.fixturenames)
if scenario.params != resolved_params:
raise NotEnoughScenarioParams(
"""Scenario "{0}" in the feature "{1}" was not able to resolve all declared parameters."""
"""Should resolve params: {2}, but resolved only: {3}.""".format(
scenario.name, feature.filename, sorted(scenario.params), sorted(resolved_params),
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_scenario_outline(feature, scenario, request, encoding):
def _execute_scenario_outline(feature, scenario, request, encoding, example_converters=None):
"""Execute the scenario outline."""
errors = []
# tricky part, basically here we clear pytest request cache
for example in scenario.examples:
for key, value in dict(zip(scenario.example_params, example)).items():
request._funcargs = {}
request._arg2index = {}
try:
_execute_scenario(feature, scenario, request, encoding, example=dict(zip(scenario.example_params, example)))
except Exception as e:
errors.append([e, sys.exc_info()[2]])
for error in errors:
raise future_utils.raise_with_traceback(error[0], error[1])
def _execute_step_function(request, feature, step, step_func, example=None):
"""Execute step function."""
kwargs = {}
if example:
for key in step.params:
value = example[key]
if step_func.converters and key in step_func.converters:
value = step_func.converters[key](value)
_inject_fixture(request, key, value)
_execute_scenario(feature, scenario, request, encoding)
try:
# Get the step argument values
kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in inspect.getargspec(step_func).args)
request.config.hook.pytest_bdd_before_step(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
step_func_args=kwargs)
# Execute the step
step_func(**kwargs)
request.config.hook.pytest_bdd_after_step(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
step_func_args=kwargs)
except Exception as exception:
request.config.hook.pytest_bdd_step_error(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
step_func_args=kwargs, exception=exception)
raise
def _execute_scenario(feature, scenario, request, encoding):
def _execute_scenario(feature, scenario, request, encoding, example=None):
"""Execute the scenario."""
_validate_scenario(feature, scenario, request)
@ -165,68 +211,77 @@ def _execute_scenario(feature, scenario, request, encoding):
exception=exception)
raise
kwargs = {}
try:
# Get the step argument values
kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in inspect.getargspec(step_func).args)
request.config.hook.pytest_bdd_before_step(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
step_func_args=kwargs)
# Execute the step
step_func(**kwargs)
request.config.hook.pytest_bdd_after_step(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
step_func_args=kwargs)
except Exception as exception:
request.config.hook.pytest_bdd_step_error(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
step_func_args=kwargs, exception=exception)
raise
_execute_step_function(request, feature, step, step_func, example=example)
def scenario(feature_name, scenario_name, encoding='utf-8'):
"""Scenario. May be called both as decorator and as just normal function."""
FakeRequest = collections.namedtuple('FakeRequest', ['module'])
caller_module = get_caller_module()
caller_function = get_caller_function()
def decorator(request):
def get_fixture(caller_module, fixture, path=None, module=None):
"""Get first conftest module from given one."""
def call_fixture(function):
args = []
if 'request' in inspect.getargspec(function).args:
args = [FakeRequest(module=caller_module)]
return function(*args)
def _scenario(request):
# Get the feature
base_path = request.getfuncargvalue('pytestbdd_feature_base_dir')
feature_path = op.abspath(op.join(base_path, feature_name))
feature = Feature.get_feature(feature_path, encoding=encoding)
if not module:
module = caller_module
# 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)
)
if hasattr(module, fixture):
return call_fixture(getattr(module, fixture))
if scenario.examples:
_execute_scenario_outline(feature, scenario, request, encoding)
else:
_execute_scenario(feature, scenario, request, encoding)
if path is None:
path = os.path.dirname(module.__file__)
if os.path.exists(os.path.join(path, '__init__.py')):
file_path = os.path.join(path, 'conftest.py')
if os.path.exists(file_path):
conftest = imp.load_source('conftest', file_path)
if hasattr(conftest, fixture):
return get_fixture(caller_module, fixture, module=conftest)
else:
return get_fixture(caller_module, fixture, module=plugin)
return get_fixture(caller_module, fixture, path=os.path.dirname(path), module=module)
_scenario.pytestbdd_params = set()
if isinstance(request, python.FixtureRequest):
# Called as a normal function.
_scenario = recreate_function(_scenario, module=caller_module)
return _scenario(request)
def scenario(
feature_name, scenario_name, encoding='utf-8', example_converters=None,
caller_module=None, caller_function=None):
"""Scenario."""
# Used as a decorator. Modify the returned function to add parameters from a decorated function.
func_args = inspect.getargspec(request).args
if 'request' in func_args:
func_args.remove('request')
_scenario = recreate_function(_scenario, name=request.__name__, add_args=func_args, module=caller_module)
_scenario.pytestbdd_params = set(func_args)
caller_module = caller_module or get_caller_module()
caller_function = caller_function or get_caller_function()
return _scenario
# 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)
decorator = recreate_function(decorator, module=caller_module, firstlineno=caller_function.f_lineno)
# 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)
)
return decorator
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 = []
def _scenario(request, *args, **kwargs):
_execute_scenario(feature, scenario, request, encoding)
_scenario = recreate_function(
_scenario, module=caller_module, firstlineno=caller_function.f_lineno,
add_args=scenario.example_params)
if params:
_scenario = pytest.mark.parametrize(*params)(_scenario)
return _scenario

View File

@ -51,11 +51,13 @@ class StepError(Exception): # pragma: no cover
RE_TYPE = type(re.compile('')) # pragma: no cover
def given(name, fixture=None):
def given(name, fixture=None, converters=None):
"""Given step decorator.
:param name: Given step name.
:param fixture: Optional name of the fixture to reuse.
:param converters: Optional `dict` of the argument or parameter converters in form
{<param_name>: <converter function>}.
:raises: StepError in case of wrong configuration.
:note: Can't be used as a decorator when the fixture is specified.
@ -66,6 +68,7 @@ def given(name, fixture=None):
module = get_caller_module()
step_func = lambda request: request.getfuncargvalue(fixture)
step_func.step_type = GIVEN
step_func.converters = converters
step_func.__name__ = name
step_func.fixture = fixture
func = pytest.fixture(lambda: step_func)
@ -73,29 +76,33 @@ def given(name, fixture=None):
contribute_to_module(module, remove_prefix(name), func)
return _not_a_fixture_decorator
return _step_decorator(GIVEN, name)
return _step_decorator(GIVEN, name, converters=converters)
def when(name):
def when(name, converters=None):
"""When step decorator.
:param name: Step name.
:param converters: Optional `dict` of the argument or parameter converters in form
{<param_name>: <converter function>}.
:raises: StepError in case of wrong configuration.
"""
return _step_decorator(WHEN, name)
return _step_decorator(WHEN, name, converters=converters)
def then(name):
def then(name, converters=None):
"""Then step decorator.
:param name: Step name.
:param converters: Optional `dict` of the argument or parameter converters in form
{<param_name>: <converter function>}.
:raises: StepError in case of wrong configuration.
"""
return _step_decorator(THEN, name)
return _step_decorator(THEN, name, converters=converters)
def _not_a_fixture_decorator(func):
@ -109,7 +116,7 @@ def _not_a_fixture_decorator(func):
raise StepError('Cannot be used as a decorator when the fixture is specified')
def _step_decorator(step_type, step_name):
def _step_decorator(step_type, step_name, converters=None):
"""Step decorator for the type and the name.
:param step_type: Step type (GIVEN, WHEN or THEN).
@ -141,6 +148,7 @@ def _step_decorator(step_type, step_name):
step_func.__name__ = step_name
step_func.step_type = step_type
step_func.converters = converters
@pytest.fixture
def lazy_step_func():
@ -151,6 +159,8 @@ def _step_decorator(step_type, step_name):
if pattern:
lazy_step_func.pattern = pattern
if converters:
lazy_step_func.converters = converters
contribute_to_module(
get_caller_module(),
@ -162,7 +172,7 @@ def _step_decorator(step_type, step_name):
return decorator
def recreate_function(func, module=None, name=None, add_args=(), firstlineno=None):
def recreate_function(func, module=None, name=None, add_args=[], firstlineno=None):
"""Recreate a function, replacing some info.
:param func: Function object.
@ -188,6 +198,10 @@ def recreate_function(func, module=None, name=None, add_args=(), firstlineno=Non
if PY3:
argnames.insert(1, 'co_kwonlyargcount')
for arg in inspect.getargspec(func).args:
if arg in add_args:
add_args.remove(arg)
args = []
code = get_code(func)
for arg in argnames:

View File

@ -6,7 +6,7 @@ from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.0.0'
version = '2.0.0'
class Tox(TestCommand):
@ -54,6 +54,7 @@ setup(
cmdclass={'test': Tox},
install_requires=[
'pytest',
'future'
],
# the following makes a plugin available to py.test
entry_points={

View File

@ -1,11 +1,11 @@
Scenario: Every step takes a parameter with the same name
Given I have 1 Euro
When I pay 2 Euro
And I pay 1 Euro
Then I should have 0 Euro
And I should have 999999 Euro # In my dream...
Given I have 1 Euro
When I pay 2 Euro
And I pay 1 Euro
Then I should have 0 Euro
And I should have 999999 Euro # In my dream...
Scenario: Using the same given fixture raises an error
Given I have 1 Euro
And I have 2 Euro
Given I have 1 Euro
And I have 2 Euro

View File

@ -19,19 +19,24 @@ test_argument_in_when_step_2 = sc('Argument in when, step 2')
@pytest.fixture
def values():
return ['1', '2', '1', '0', '999999']
return [1, 2, 1, 0, 999999]
@given(re.compile(r'I have (?P<euro>\d+) Euro'))
@given(re.compile(r'I have (?P<euro>\d+) Euro'), converters=dict(euro=int))
def i_have(euro, values):
assert euro == values.pop(0)
@when(re.compile(r'I pay (?P<euro>\d+) Euro'))
@when(re.compile(r'I pay (?P<euro>\d+) Euro'), converters=dict(euro=int))
def i_pay(euro, values, request):
assert euro == values.pop(0)
@then(re.compile(r'I should have (?P<euro>\d+) Euro'), converters=dict(euro=int))
def i_should_have(euro, values):
assert euro == values.pop(0)
@given('I have an argument')
def argument():
"""I have an argument."""
@ -44,11 +49,6 @@ def get_argument(argument, arg):
argument['arg'] = arg
@then(re.compile(r'I should have (?P<euro>\d+) Euro'))
def i_should_have(euro, values):
assert euro == values.pop(0)
@then(re.compile('My argument should be (?P<arg>\d+)'))
def assert_that_my_argument_is_arg(argument, arg):
"""Assert that arg from when equals arg."""

View File

@ -6,3 +6,24 @@ Scenario Outline: Outlined given, when, thens
Examples:
| start | eat | left |
| 12 | 5 | 7 |
Scenario Outline: Outlined with wrong examples
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Examples:
| start | eat | left | unknown_param |
| 12 | 5 | 7 | value |
Scenario Outline: Outlined with some examples failing
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Examples:
| start | eat | left |
| 0 | 5 | 5 |
| 12 | 5 | 7 |

View File

@ -20,8 +20,6 @@ def pytestbdd_feature_base_dir():
def test_feature_path(request, scenario_name):
"""Test feature base dir."""
sc = scenario('steps.feature', scenario_name)
with pytest.raises(IOError) as exc:
sc(request)
scenario('steps.feature', scenario_name)
assert os.path.join('/does/not/exist/', 'steps.feature') in str(exc.value)

View File

@ -1,25 +1,55 @@
"""Scenario Outline tests."""
import re
import pytest
from pytest_bdd import given, when, then, scenario
from pytest_bdd import mark
from pytest_bdd.scenario import ScenarioExamplesNotValidError
test_outlined = scenario(
'outline.feature',
'Outlined given, when, thens',
example_converters=dict(start=int, eat=float, left=str)
)
@given('there are <start> cucumbers')
def start_cucumbers(start):
return dict(start=int(start))
assert isinstance(start, int)
return dict(start=start)
@when('I eat <eat> cucumbers')
def eat_cucumbers(start_cucumbers, start, eat):
start_cucumbers['eat'] = int(eat)
def eat_cucumbers(start_cucumbers, eat):
assert isinstance(eat, float)
start_cucumbers['eat'] = eat
@then('I should have <left> cucumbers')
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
assert int(start) - int(eat) == int(left)
assert start_cucumbers['start'] == int(start)
assert start_cucumbers['eat'] == int(eat)
assert isinstance(left, str)
assert start - eat == int(left)
assert start_cucumbers['start'] == start
assert start_cucumbers['eat'] == eat
def test_wrongly_outlined(request):
"""Test parametrized scenario when the test function lacks parameters."""
@mark.scenario(
'outline.feature',
'Outlined with wrong examples',
)
def wrongly_outlined(request):
pass
with pytest.raises(ScenarioExamplesNotValidError) as exc:
wrongly_outlined(request, 1, 2, 3, 4)
assert re.match(
"""Scenario \"Outlined with wrong examples\" in the feature \"(.+)\" has not valid examples\. """
"""Set of step parameters (.+) should match set of example values """
"""(.+)\.""",
exc.value.args[0]
)

View File

@ -1,14 +1,12 @@
import pytest
from pytest_bdd.scenario import NotEnoughScenarioParams
from pytest_bdd import given, when, then, scenario
from pytest_bdd import given, when, then, mark
@pytest.mark.parametrize(
['start', 'eat', 'left'],
[(12, 5, 7)])
@scenario(
@mark.scenario(
'parametrized.feature',
'Parametrized given, when, thens',
)
@ -18,37 +16,18 @@ def test_parametrized(request, start, eat, left):
@pytest.fixture(params=[1, 2])
def foo_bar(request):
return 'foo_bar' * request.param
return 'bar' * request.param
@pytest.mark.parametrize(
['start', 'eat', 'left'],
[(12, 5, 7)])
@scenario(
@mark.scenario(
'parametrized.feature',
'Parametrized given, when, thens',
)
def test_parametrized_with_other_fixtures(request, start, eat, left, foo_bar):
"""Test parametrized scenario, but also with other fixtures."""
def test_parametrized_wrongly(request):
"""Test parametrized scenario when the test function lacks parameters."""
@scenario(
'parametrized.feature',
'Parametrized given, when, thens',
)
def wrongly_parametrized(request):
pass
with pytest.raises(NotEnoughScenarioParams) as exc:
wrongly_parametrized(request)
assert exc.value.args == (
"""Scenario "Parametrized given, when, thens" in the feature "parametrized.feature" was not able to """
"""resolve all declared parameters. """
"""Should resolve params: [\'eat\', \'left\', \'start\'], but resolved only: []."""
)
"""Test parametrized scenario, but also with other parametrized fixtures."""
@given('there are <start> cucumbers')

View File

@ -6,10 +6,9 @@ from pytest_bdd.scenario import ScenarioNotFound
def test_scenario_not_found(request):
"""Test the situation when scenario is not found."""
test_not_found = scenario(
'not_found.feature',
'NOT FOUND'
)
with pytest.raises(ScenarioNotFound):
test_not_found(request)
scenario(
'not_found.feature',
'NOT FOUND'
)

View File

@ -96,7 +96,7 @@ def test_step_hooks(testdir):
""")
testdir.makepyfile("""
import pytest
from pytest_bdd import given, when, scenario
from pytest_bdd import given, when, mark
@given('I have a bar')
def i_have_bar():
@ -118,15 +118,15 @@ def test_step_hooks(testdir):
def when_dependency_fails(dependency):
pass
@scenario('test.feature', "When step's dependency a has failure")
@mark.scenario('test.feature', "When step's dependency a has failure")
def test_when_dependency_fails():
pass
@scenario('test.feature', 'When step has hook on failure')
@mark.scenario('test.feature', 'When step has hook on failure')
def test_when_fails():
pass
@scenario('test.feature', 'When step is not found')
@mark.scenario('test.feature', 'When step is not found')
def test_when_not_found():
pass
@ -134,7 +134,7 @@ def test_step_hooks(testdir):
def foo():
return 'foo'
@scenario('test.feature', 'When step validation error happens')
@mark.scenario('test.feature', 'When step validation error happens')
def test_when_step_validation_error():
pass
""")
@ -185,7 +185,7 @@ def test_step_trace(testdir):
""")
testdir.makepyfile("""
import pytest
from pytest_bdd import given, when, scenario
from pytest_bdd import given, when, scenario, mark
@given('I have a bar')
def i_have_bar():
@ -197,7 +197,7 @@ def test_step_trace(testdir):
test_when_fails_inline = scenario('test.feature', 'When step has failure')
@scenario('test.feature', 'When step has failure')
@mark.scenario('test.feature', 'When step has failure')
def test_when_fails_decorated():
pass

View File

@ -33,9 +33,8 @@ def then_nevermind():
def test_wrong(request, feature, scenario_name):
"""Test wrong feature scenarios."""
sc = scenario(feature, scenario_name)
with pytest.raises(FeatureError):
sc(request)
scenario(feature, scenario_name)
# TODO: assert the exception args from parameters
@ -60,9 +59,8 @@ def test_wrong_type_order(request, scenario_name):
def test_verbose_output(request):
"""Test verbose output of failed feature scenario"""
sc = scenario('when_after_then.feature', 'When after then')
with pytest.raises(FeatureError) as excinfo:
sc(request)
scenario('when_after_then.feature', 'When after then')
msg, line_number, line = excinfo.value.args