Scenario outline implementation based on pure pytest parametrization
This commit is contained in:
parent
f20c02a571
commit
bca5206677
|
@ -1,6 +1,13 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
2.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Pure pytest parametrization for scenario outlines (bubenkoff)
|
||||||
|
- Splitting scenario decorated and non-decorated variants (bubenkoff)
|
||||||
|
|
||||||
|
|
||||||
1.0.0
|
1.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
65
README.rst
65
README.rst
|
@ -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
|
just-enough specification of the requirements without maintaining any context object
|
||||||
containing the side effects of the Gherkin imperative declarations.
|
containing the side effects of the Gherkin imperative declarations.
|
||||||
|
|
||||||
|
|
||||||
Install pytest-bdd
|
Install pytest-bdd
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ Install pytest-bdd
|
||||||
|
|
||||||
pip install pytest-bdd
|
pip install pytest-bdd
|
||||||
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
@ -81,6 +83,7 @@ test\_publish\_article.py:
|
||||||
article.refresh() # Refresh the object in the SQLAlchemy session
|
article.refresh() # Refresh the object in the SQLAlchemy session
|
||||||
assert article.is_published
|
assert article.is_published
|
||||||
|
|
||||||
|
|
||||||
Step aliases
|
Step aliases
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -113,6 +116,7 @@ default author.
|
||||||
Given I'm the admin
|
Given I'm the admin
|
||||||
And there is an article
|
And there is an article
|
||||||
|
|
||||||
|
|
||||||
Step arguments
|
Step arguments
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -143,28 +147,32 @@ The code will look like:
|
||||||
|
|
||||||
test_arguments = scenario('arguments.feature', 'Arguments for given, when, thens')
|
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):
|
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)
|
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):
|
def eat_cucumbers(start_cucumbers, eat):
|
||||||
eat = int(eat)
|
|
||||||
start_cucumbers['eat'] += 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):
|
def should_have_left_cucumbers(start_cucumbers, start, left):
|
||||||
start, left = int(start), int(left)
|
|
||||||
assert start_cucumbers['start'] == start
|
assert start_cucumbers['start'] == start
|
||||||
assert start - start_cucumbers['eat'] == left
|
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 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
|
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
|
`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.
|
exactly as it's described in be behave docs.
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
.. code-block:: feature
|
.. code-block:: feature
|
||||||
|
@ -199,42 +206,47 @@ The code will look like:
|
||||||
test_outlined = scenario(
|
test_outlined = scenario(
|
||||||
'outline.feature',
|
'outline.feature',
|
||||||
'Outlined given, when, thens',
|
'Outlined given, when, thens',
|
||||||
|
example_converters=dict(start=int, eat=float, left=str)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@given('there are <start> cucumbers')
|
@given('there are <start> cucumbers')
|
||||||
def start_cucumbers(start):
|
def start_cucumbers(start):
|
||||||
return dict(start=int(start))
|
assert isinstance(start, int)
|
||||||
|
return dict(start=start)
|
||||||
|
|
||||||
|
|
||||||
@when('I eat <eat> cucumbers')
|
@when('I eat <eat> cucumbers')
|
||||||
def eat_cucumbers(start_cucumbers, start, eat):
|
def eat_cucumbers(start_cucumbers, eat):
|
||||||
start_cucumbers['eat'] = int(eat)
|
assert isinstance(eat, float)
|
||||||
|
start_cucumbers['eat'] = eat
|
||||||
|
|
||||||
|
|
||||||
@then('I should have <left> cucumbers')
|
@then('I should have <left> cucumbers')
|
||||||
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
||||||
assert int(start) - int(eat) == int(left)
|
assert isinstance(left, str)
|
||||||
assert start_cucumbers['start'] == int(start)
|
assert start - eat == int(left)
|
||||||
assert start_cucumbers['eat'] == int(eat)
|
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.
|
Example code also shows possibility to pass example converters which may be useful if you need parameter types
|
||||||
The reason for this is that it is very often that some simple pythonic type
|
different than strings.
|
||||||
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.
|
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:
|
The code will look like:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import pytest
|
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
|
# Here we use pytest to parametrize the test with the parameters table
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
['start', 'eat', 'left'],
|
['start', 'eat', 'left'],
|
||||||
[(12, 5, 7)])
|
[(12, 5, 7)])
|
||||||
@scenario(
|
@mark.scenario(
|
||||||
'parametrized.feature',
|
'parametrized.feature',
|
||||||
'Parametrized given, when, thens',
|
'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.
|
You can learn more about `functools.partial <http://docs.python.org/2/library/functools.html#functools.partial>`_ in the Python docs.
|
||||||
|
|
||||||
|
|
||||||
Hooks
|
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
|
* 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
|
Tools recommended to use for browser testing:
|
||||||
(subplugins) is to provide useful and specialized fixtures.
|
|
||||||
|
|
||||||
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
|
License
|
||||||
=======
|
=======
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from pytest_bdd.steps import given, when, then # pragma: no cover
|
from pytest_bdd.steps import given, when, then # pragma: no cover
|
||||||
from pytest_bdd.scenario import scenario # 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__, mark.__name__] # pragma: no cover
|
||||||
__all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__] # pragma: no cover
|
|
||||||
|
|
|
@ -229,8 +229,9 @@ class Scenario(object):
|
||||||
:param step_type: Step type.
|
:param step_type: Step type.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.params.update(get_step_params(step_name))
|
params = get_step_params(step_name)
|
||||||
self.steps.append(Step(name=step_name, type=step_type))
|
self.params.update(params)
|
||||||
|
self.steps.append(Step(name=step_name, type=step_type, params=params))
|
||||||
|
|
||||||
def set_param_names(self, keys):
|
def set_param_names(self, keys):
|
||||||
"""Set parameter names.
|
"""Set parameter names.
|
||||||
|
@ -238,8 +239,7 @@ class Scenario(object):
|
||||||
:param names: `list` of `string` parameter names
|
:param names: `list` of `string` parameter names
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.params.update(keys)
|
self.example_params = [str(key) for key in keys]
|
||||||
self.example_params = keys
|
|
||||||
|
|
||||||
def add_example(self, values):
|
def add_example(self, values):
|
||||||
"""Add example.
|
"""Add example.
|
||||||
|
@ -253,6 +253,7 @@ class Scenario(object):
|
||||||
class Step(object):
|
class Step(object):
|
||||||
"""Step."""
|
"""Step."""
|
||||||
|
|
||||||
def __init__(self, name, type):
|
def __init__(self, name, type, params):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type = type
|
self.type = type
|
||||||
|
self.params = params
|
||||||
|
|
|
@ -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)
|
|
@ -11,15 +11,26 @@ test_publish_article = scenario(
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
|
import imp
|
||||||
|
|
||||||
|
import sys
|
||||||
import inspect # pragma: no cover
|
import inspect # pragma: no cover
|
||||||
from os import path as op # 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 import python
|
||||||
|
|
||||||
from pytest_bdd.feature import Feature, force_encode # pragma: no cover
|
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.steps import recreate_function, get_caller_module, get_caller_function
|
||||||
from pytest_bdd.types import GIVEN
|
from pytest_bdd.types import GIVEN
|
||||||
|
|
||||||
|
from pytest_bdd import plugin
|
||||||
|
|
||||||
|
|
||||||
class ScenarioValidationError(Exception):
|
class ScenarioValidationError(Exception):
|
||||||
"""Base class for scenario validation."""
|
"""Base class for scenario validation."""
|
||||||
|
@ -29,8 +40,8 @@ class ScenarioNotFound(ScenarioValidationError): # pragma: no cover
|
||||||
"""Scenario Not Found"""
|
"""Scenario Not Found"""
|
||||||
|
|
||||||
|
|
||||||
class NotEnoughScenarioParams(ScenarioValidationError): # pragma: no cover
|
class ScenarioExamplesNotValidError(ScenarioValidationError): # pragma: no cover
|
||||||
"""Scenario function doesn't take enough parameters in the arguments."""
|
"""Scenario steps argumets do not match declared scenario examples."""
|
||||||
|
|
||||||
|
|
||||||
class StepTypeError(ScenarioValidationError): # pragma: no cover
|
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
|
match = pattern.match(name) if pattern else None
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
|
converters = getattr(fixturedef.func, 'converters', {})
|
||||||
for arg, value in match.groupdict().items():
|
for arg, value in match.groupdict().items():
|
||||||
|
if arg in converters:
|
||||||
|
value = converters[arg](value)
|
||||||
_inject_fixture(request, arg, value)
|
_inject_fixture(request, arg, value)
|
||||||
return request.getfuncargvalue(pattern.pattern)
|
return request.getfuncargvalue(pattern.pattern)
|
||||||
raise
|
raise
|
||||||
|
@ -109,26 +123,58 @@ def _find_step_function(request, name, encoding):
|
||||||
|
|
||||||
def _validate_scenario(feature, scenario, request):
|
def _validate_scenario(feature, scenario, request):
|
||||||
"""Validate the scenario."""
|
"""Validate the scenario."""
|
||||||
resolved_params = scenario.params.intersection(request.fixturenames)
|
if scenario.params and scenario.example_params and scenario.params != set(scenario.example_params):
|
||||||
|
raise ScenarioExamplesNotValidError(
|
||||||
if scenario.params != resolved_params:
|
"""Scenario "{0}" in the feature "{1}" has not valid examples. """
|
||||||
raise NotEnoughScenarioParams(
|
"""Set of step parameters {2} should match set of example values {3}.""".format(
|
||||||
"""Scenario "{0}" in the feature "{1}" was not able to resolve all declared parameters."""
|
scenario.name, feature.filename, sorted(scenario.params), sorted(scenario.example_params),
|
||||||
"""Should resolve params: {2}, but resolved only: {3}.""".format(
|
|
||||||
scenario.name, feature.filename, sorted(scenario.params), sorted(resolved_params),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _execute_scenario_outline(feature, scenario, request, encoding):
|
def _execute_scenario_outline(feature, scenario, request, encoding, example_converters=None):
|
||||||
"""Execute the scenario outline."""
|
"""Execute the scenario outline."""
|
||||||
|
errors = []
|
||||||
|
# tricky part, basically here we clear pytest request cache
|
||||||
for example in scenario.examples:
|
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)
|
_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."""
|
"""Execute the scenario."""
|
||||||
|
|
||||||
_validate_scenario(feature, scenario, request)
|
_validate_scenario(feature, scenario, request)
|
||||||
|
@ -165,68 +211,77 @@ def _execute_scenario(feature, scenario, request, encoding):
|
||||||
exception=exception)
|
exception=exception)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
kwargs = {}
|
_execute_step_function(request, feature, step, step_func, example=example)
|
||||||
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 scenario(feature_name, scenario_name, encoding='utf-8'):
|
FakeRequest = collections.namedtuple('FakeRequest', ['module'])
|
||||||
"""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):
|
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):
|
if not module:
|
||||||
# Get the feature
|
module = caller_module
|
||||||
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)
|
|
||||||
|
|
||||||
# Get the scenario
|
if hasattr(module, fixture):
|
||||||
try:
|
return call_fixture(getattr(module, fixture))
|
||||||
scenario = feature.scenarios[scenario_name]
|
|
||||||
except KeyError:
|
|
||||||
raise ScenarioNotFound(
|
|
||||||
'Scenario "{0}" in feature "{1}" is not found.'.format(scenario_name, feature_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
if scenario.examples:
|
if path is None:
|
||||||
_execute_scenario_outline(feature, scenario, request, encoding)
|
path = os.path.dirname(module.__file__)
|
||||||
else:
|
if os.path.exists(os.path.join(path, '__init__.py')):
|
||||||
_execute_scenario(feature, scenario, request, encoding)
|
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):
|
def scenario(
|
||||||
# Called as a normal function.
|
feature_name, scenario_name, encoding='utf-8', example_converters=None,
|
||||||
_scenario = recreate_function(_scenario, module=caller_module)
|
caller_module=None, caller_function=None):
|
||||||
return _scenario(request)
|
"""Scenario."""
|
||||||
|
|
||||||
# Used as a decorator. Modify the returned function to add parameters from a decorated function.
|
caller_module = caller_module or get_caller_module()
|
||||||
func_args = inspect.getargspec(request).args
|
caller_function = caller_function or get_caller_function()
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -51,11 +51,13 @@ class StepError(Exception): # pragma: no cover
|
||||||
RE_TYPE = type(re.compile('')) # 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.
|
"""Given step decorator.
|
||||||
|
|
||||||
:param name: Given step name.
|
:param name: Given step name.
|
||||||
:param fixture: Optional name of the fixture to reuse.
|
: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.
|
:raises: StepError in case of wrong configuration.
|
||||||
:note: Can't be used as a decorator when the fixture is specified.
|
: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()
|
module = get_caller_module()
|
||||||
step_func = lambda request: request.getfuncargvalue(fixture)
|
step_func = lambda request: request.getfuncargvalue(fixture)
|
||||||
step_func.step_type = GIVEN
|
step_func.step_type = GIVEN
|
||||||
|
step_func.converters = converters
|
||||||
step_func.__name__ = name
|
step_func.__name__ = name
|
||||||
step_func.fixture = fixture
|
step_func.fixture = fixture
|
||||||
func = pytest.fixture(lambda: step_func)
|
func = pytest.fixture(lambda: step_func)
|
||||||
|
@ -73,29 +76,33 @@ def given(name, fixture=None):
|
||||||
contribute_to_module(module, remove_prefix(name), func)
|
contribute_to_module(module, remove_prefix(name), func)
|
||||||
return _not_a_fixture_decorator
|
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.
|
"""When step decorator.
|
||||||
|
|
||||||
:param name: Step name.
|
: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.
|
: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.
|
"""Then step decorator.
|
||||||
|
|
||||||
:param name: Step name.
|
: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.
|
: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):
|
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')
|
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.
|
"""Step decorator for the type and the name.
|
||||||
|
|
||||||
:param step_type: Step type (GIVEN, WHEN or THEN).
|
: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.__name__ = step_name
|
||||||
step_func.step_type = step_type
|
step_func.step_type = step_type
|
||||||
|
step_func.converters = converters
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def lazy_step_func():
|
def lazy_step_func():
|
||||||
|
@ -151,6 +159,8 @@ def _step_decorator(step_type, step_name):
|
||||||
|
|
||||||
if pattern:
|
if pattern:
|
||||||
lazy_step_func.pattern = pattern
|
lazy_step_func.pattern = pattern
|
||||||
|
if converters:
|
||||||
|
lazy_step_func.converters = converters
|
||||||
|
|
||||||
contribute_to_module(
|
contribute_to_module(
|
||||||
get_caller_module(),
|
get_caller_module(),
|
||||||
|
@ -162,7 +172,7 @@ def _step_decorator(step_type, step_name):
|
||||||
return decorator
|
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.
|
"""Recreate a function, replacing some info.
|
||||||
|
|
||||||
:param func: Function object.
|
:param func: Function object.
|
||||||
|
@ -188,6 +198,10 @@ def recreate_function(func, module=None, name=None, add_args=(), firstlineno=Non
|
||||||
if PY3:
|
if PY3:
|
||||||
argnames.insert(1, 'co_kwonlyargcount')
|
argnames.insert(1, 'co_kwonlyargcount')
|
||||||
|
|
||||||
|
for arg in inspect.getargspec(func).args:
|
||||||
|
if arg in add_args:
|
||||||
|
add_args.remove(arg)
|
||||||
|
|
||||||
args = []
|
args = []
|
||||||
code = get_code(func)
|
code = get_code(func)
|
||||||
for arg in argnames:
|
for arg in argnames:
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -6,7 +6,7 @@ from setuptools import setup
|
||||||
from setuptools.command.test import test as TestCommand
|
from setuptools.command.test import test as TestCommand
|
||||||
|
|
||||||
|
|
||||||
version = '1.0.0'
|
version = '2.0.0'
|
||||||
|
|
||||||
|
|
||||||
class Tox(TestCommand):
|
class Tox(TestCommand):
|
||||||
|
@ -54,6 +54,7 @@ setup(
|
||||||
cmdclass={'test': Tox},
|
cmdclass={'test': Tox},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'pytest',
|
'pytest',
|
||||||
|
'future'
|
||||||
],
|
],
|
||||||
# the following makes a plugin available to py.test
|
# the following makes a plugin available to py.test
|
||||||
entry_points={
|
entry_points={
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
Scenario: Every step takes a parameter with the same name
|
Scenario: Every step takes a parameter with the same name
|
||||||
Given I have 1 Euro
|
Given I have 1 Euro
|
||||||
When I pay 2 Euro
|
When I pay 2 Euro
|
||||||
And I pay 1 Euro
|
And I pay 1 Euro
|
||||||
Then I should have 0 Euro
|
Then I should have 0 Euro
|
||||||
And I should have 999999 Euro # In my dream...
|
And I should have 999999 Euro # In my dream...
|
||||||
|
|
||||||
|
|
||||||
Scenario: Using the same given fixture raises an error
|
Scenario: Using the same given fixture raises an error
|
||||||
Given I have 1 Euro
|
Given I have 1 Euro
|
||||||
And I have 2 Euro
|
And I have 2 Euro
|
||||||
|
|
|
@ -19,19 +19,24 @@ test_argument_in_when_step_2 = sc('Argument in when, step 2')
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def values():
|
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):
|
def i_have(euro, values):
|
||||||
assert euro == values.pop(0)
|
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):
|
def i_pay(euro, values, request):
|
||||||
assert euro == values.pop(0)
|
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')
|
@given('I have an argument')
|
||||||
def argument():
|
def argument():
|
||||||
"""I have an argument."""
|
"""I have an argument."""
|
||||||
|
@ -44,11 +49,6 @@ def get_argument(argument, arg):
|
||||||
argument['arg'] = 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+)'))
|
@then(re.compile('My argument should be (?P<arg>\d+)'))
|
||||||
def assert_that_my_argument_is_arg(argument, arg):
|
def assert_that_my_argument_is_arg(argument, arg):
|
||||||
"""Assert that arg from when equals arg."""
|
"""Assert that arg from when equals arg."""
|
||||||
|
|
|
@ -6,3 +6,24 @@ Scenario Outline: Outlined given, when, thens
|
||||||
Examples:
|
Examples:
|
||||||
| start | eat | left |
|
| start | eat | left |
|
||||||
| 12 | 5 | 7 |
|
| 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 |
|
||||||
|
|
|
@ -20,8 +20,6 @@ def pytestbdd_feature_base_dir():
|
||||||
|
|
||||||
def test_feature_path(request, scenario_name):
|
def test_feature_path(request, scenario_name):
|
||||||
"""Test feature base dir."""
|
"""Test feature base dir."""
|
||||||
sc = scenario('steps.feature', scenario_name)
|
|
||||||
with pytest.raises(IOError) as exc:
|
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)
|
assert os.path.join('/does/not/exist/', 'steps.feature') in str(exc.value)
|
||||||
|
|
|
@ -1,25 +1,55 @@
|
||||||
"""Scenario Outline tests."""
|
"""Scenario Outline tests."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from pytest_bdd import given, when, then, scenario
|
from pytest_bdd import given, when, then, scenario
|
||||||
|
from pytest_bdd import mark
|
||||||
|
from pytest_bdd.scenario import ScenarioExamplesNotValidError
|
||||||
|
|
||||||
|
|
||||||
test_outlined = scenario(
|
test_outlined = scenario(
|
||||||
'outline.feature',
|
'outline.feature',
|
||||||
'Outlined given, when, thens',
|
'Outlined given, when, thens',
|
||||||
|
example_converters=dict(start=int, eat=float, left=str)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@given('there are <start> cucumbers')
|
@given('there are <start> cucumbers')
|
||||||
def start_cucumbers(start):
|
def start_cucumbers(start):
|
||||||
return dict(start=int(start))
|
assert isinstance(start, int)
|
||||||
|
return dict(start=start)
|
||||||
|
|
||||||
|
|
||||||
@when('I eat <eat> cucumbers')
|
@when('I eat <eat> cucumbers')
|
||||||
def eat_cucumbers(start_cucumbers, start, eat):
|
def eat_cucumbers(start_cucumbers, eat):
|
||||||
start_cucumbers['eat'] = int(eat)
|
assert isinstance(eat, float)
|
||||||
|
start_cucumbers['eat'] = eat
|
||||||
|
|
||||||
|
|
||||||
@then('I should have <left> cucumbers')
|
@then('I should have <left> cucumbers')
|
||||||
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
||||||
assert int(start) - int(eat) == int(left)
|
assert isinstance(left, str)
|
||||||
assert start_cucumbers['start'] == int(start)
|
assert start - eat == int(left)
|
||||||
assert start_cucumbers['eat'] == int(eat)
|
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]
|
||||||
|
)
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pytest_bdd.scenario import NotEnoughScenarioParams
|
from pytest_bdd import given, when, then, mark
|
||||||
|
|
||||||
from pytest_bdd import given, when, then, scenario
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
['start', 'eat', 'left'],
|
['start', 'eat', 'left'],
|
||||||
[(12, 5, 7)])
|
[(12, 5, 7)])
|
||||||
@scenario(
|
@mark.scenario(
|
||||||
'parametrized.feature',
|
'parametrized.feature',
|
||||||
'Parametrized given, when, thens',
|
'Parametrized given, when, thens',
|
||||||
)
|
)
|
||||||
|
@ -18,37 +16,18 @@ def test_parametrized(request, start, eat, left):
|
||||||
|
|
||||||
@pytest.fixture(params=[1, 2])
|
@pytest.fixture(params=[1, 2])
|
||||||
def foo_bar(request):
|
def foo_bar(request):
|
||||||
return 'foo_bar' * request.param
|
return 'bar' * request.param
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
['start', 'eat', 'left'],
|
['start', 'eat', 'left'],
|
||||||
[(12, 5, 7)])
|
[(12, 5, 7)])
|
||||||
@scenario(
|
@mark.scenario(
|
||||||
'parametrized.feature',
|
'parametrized.feature',
|
||||||
'Parametrized given, when, thens',
|
'Parametrized given, when, thens',
|
||||||
)
|
)
|
||||||
def test_parametrized_with_other_fixtures(request, start, eat, left, foo_bar):
|
def test_parametrized_with_other_fixtures(request, start, eat, left, foo_bar):
|
||||||
"""Test parametrized scenario, but also with other fixtures."""
|
"""Test parametrized scenario, but also with other parametrized 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: []."""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@given('there are <start> cucumbers')
|
@given('there are <start> cucumbers')
|
||||||
|
|
|
@ -6,10 +6,9 @@ from pytest_bdd.scenario import ScenarioNotFound
|
||||||
|
|
||||||
def test_scenario_not_found(request):
|
def test_scenario_not_found(request):
|
||||||
"""Test the situation when scenario is not found."""
|
"""Test the situation when scenario is not found."""
|
||||||
test_not_found = scenario(
|
|
||||||
'not_found.feature',
|
|
||||||
'NOT FOUND'
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ScenarioNotFound):
|
with pytest.raises(ScenarioNotFound):
|
||||||
test_not_found(request)
|
scenario(
|
||||||
|
'not_found.feature',
|
||||||
|
'NOT FOUND'
|
||||||
|
)
|
||||||
|
|
|
@ -96,7 +96,7 @@ def test_step_hooks(testdir):
|
||||||
""")
|
""")
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
from pytest_bdd import given, when, scenario
|
from pytest_bdd import given, when, mark
|
||||||
|
|
||||||
@given('I have a bar')
|
@given('I have a bar')
|
||||||
def i_have_bar():
|
def i_have_bar():
|
||||||
|
@ -118,15 +118,15 @@ def test_step_hooks(testdir):
|
||||||
def when_dependency_fails(dependency):
|
def when_dependency_fails(dependency):
|
||||||
pass
|
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():
|
def test_when_dependency_fails():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@scenario('test.feature', 'When step has hook on failure')
|
@mark.scenario('test.feature', 'When step has hook on failure')
|
||||||
def test_when_fails():
|
def test_when_fails():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@scenario('test.feature', 'When step is not found')
|
@mark.scenario('test.feature', 'When step is not found')
|
||||||
def test_when_not_found():
|
def test_when_not_found():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ def test_step_hooks(testdir):
|
||||||
def foo():
|
def foo():
|
||||||
return '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():
|
def test_when_step_validation_error():
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
|
@ -185,7 +185,7 @@ def test_step_trace(testdir):
|
||||||
""")
|
""")
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
from pytest_bdd import given, when, scenario
|
from pytest_bdd import given, when, scenario, mark
|
||||||
|
|
||||||
@given('I have a bar')
|
@given('I have a bar')
|
||||||
def i_have_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')
|
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():
|
def test_when_fails_decorated():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,8 @@ def then_nevermind():
|
||||||
def test_wrong(request, feature, scenario_name):
|
def test_wrong(request, feature, scenario_name):
|
||||||
"""Test wrong feature scenarios."""
|
"""Test wrong feature scenarios."""
|
||||||
|
|
||||||
sc = scenario(feature, scenario_name)
|
|
||||||
with pytest.raises(FeatureError):
|
with pytest.raises(FeatureError):
|
||||||
sc(request)
|
scenario(feature, scenario_name)
|
||||||
# TODO: assert the exception args from parameters
|
# TODO: assert the exception args from parameters
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,9 +59,8 @@ def test_wrong_type_order(request, scenario_name):
|
||||||
|
|
||||||
def test_verbose_output(request):
|
def test_verbose_output(request):
|
||||||
"""Test verbose output of failed feature scenario"""
|
"""Test verbose output of failed feature scenario"""
|
||||||
sc = scenario('when_after_then.feature', 'When after then')
|
|
||||||
with pytest.raises(FeatureError) as excinfo:
|
with pytest.raises(FeatureError) as excinfo:
|
||||||
sc(request)
|
scenario('when_after_then.feature', 'When after then')
|
||||||
|
|
||||||
msg, line_number, line = excinfo.value.args
|
msg, line_number, line = excinfo.value.args
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue