code cleanup. vertical example table implemented

This commit is contained in:
Anatoly Bubenkov 2014-03-15 01:05:21 +01:00
parent c0ae33a241
commit 482482b059
12 changed files with 270 additions and 145 deletions

View File

@ -5,7 +5,9 @@ Changelog
----- -----
- Pure pytest parametrization for scenario outlines (bubenkoff) - 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 1.0.0

View File

@ -23,7 +23,7 @@ containing the side effects of the Gherkin imperative declarations.
Install pytest-bdd Install pytest-bdd
================== ------------------
:: ::
@ -31,7 +31,7 @@ Install pytest-bdd
Example Example
======= -------
publish\_article.feature: publish\_article.feature:
@ -54,7 +54,9 @@ test\_publish\_article.py:
from pytest_bdd import scenario, given, when, then 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') @given('I have an article')
@ -85,7 +87,7 @@ test\_publish\_article.py:
Step aliases Step aliases
============ ------------
Sometimes it is needed to declare the same fixtures or steps with the 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 different names for better readability. In order to use the same step
@ -118,7 +120,7 @@ default author.
Step arguments Step arguments
============== --------------
Often it's possible to reuse steps giving them a parameter(s). Often it's possible to reuse steps giving them a parameter(s).
This allows to have single implementation and multiple use, so less code. This allows to have single implementation and multiple use, so less code.
@ -145,7 +147,11 @@ The code will look like:
import re import re
from pytest_bdd import scenario, given, when, then 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<start>\d+) cucumbers'), converters=dict(start=int)) @given(re.compile('there are (?P<start>\d+) cucumbers'), converters=dict(start=int))
def start_cucumbers(start): def start_cucumbers(start):
@ -167,15 +173,15 @@ different than strings.
Scenario parameters 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. * `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. * `example_converters` - mapping to pass functions to convert example values provided in feature files.
Scenario outlines Scenario outlines
================= -----------------
Scenarios can be parametrized to cover few cases. In Gherkin the variable Scenarios can be parametrized to cover few cases. In Gherkin the variable
templates are written using corner braces as <somevalue>. templates are written using corner braces as <somevalue>.
@ -195,6 +201,24 @@ Example:
| start | eat | left | | start | eat | left |
| 12 | 5 | 7 | | 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 <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> 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: The code will look like:
@ -203,11 +227,13 @@ The code will look like:
from pytest_bdd import given, when, then, scenario from pytest_bdd import given, when, then, scenario
test_outlined = scenario( @scenario(
'outline.feature', 'outline.feature',
'Outlined given, when, thens', 'Outlined given, when, thens',
example_converters=dict(start=int, eat=float, left=str) example_converters=dict(start=int, eat=float, left=str)
) )
def test_outlined():
pass
@given('there are <start> cucumbers') @given('there are <start> cucumbers')
@ -242,6 +268,7 @@ The code will look like:
import pytest import pytest
from pytest_bdd import mark, 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'],
@ -276,7 +303,7 @@ The significant downside of this approach is inability to see the test table fro
Test setup Test setup
========== ----------
Test setup is implemented within the Given section. Even though these steps Test setup is implemented within the Given section. Even though these steps
are executed imperatively to apply possible side-effects, pytest-bdd is trying 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 Reusing fixtures
================ ----------------
Sometimes scenarios define new names for the fixture that can be Sometimes scenarios define new names for the fixture that can be
inherited. Fixtures can be reused with other names using given(): 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 Reusing steps
============= -------------
It is possible to define some common steps in the parent conftest.py and It is possible to define some common steps in the parent conftest.py and
simply expect them in the child test file. simply expect them in the child test file.
@ -410,14 +437,16 @@ test\_common.py:
.. code-block:: python .. 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 There are no definitions of the steps in the test file. They were
collected from the parent conftests. collected from the parent conftests.
Feature file paths Feature file paths
================== ------------------
But default, pytest-bdd will use current modules path as base path for But default, pytest-bdd will use current modules path as base path for
finding feature files, but this behaviour can be changed by having finding feature files, but this behaviour can be changed by having
@ -436,11 +465,14 @@ test\_publish\_article.py:
def pytestbdd_feature_base_dir(): def pytestbdd_feature_base_dir():
return '/home/user/projects/foo.bar/features' 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 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. 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. 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') 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 <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
===== -----
pytest-bdd exposes several pytest `hooks <http://pytest.org/latest/plugins.html#well-specified-hooks>`_ pytest-bdd exposes several pytest `hooks <http://pytest.org/latest/plugins.html#well-specified-hooks>`_
which might be helpful building useful reporting, visualization, etc on top of it: 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 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: Tools recommended to use for browser testing:
@ -496,7 +543,7 @@ Tools recommended to use for browser testing:
License License
======= -------
This software is licensed under the `MIT license <http://en.wikipedia.org/wiki/MIT_License>`_. This software is licensed under the `MIT license <http://en.wikipedia.org/wiki/MIT_License>`_.

25
pytest_bdd/exceptions.py Normal file
View File

@ -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."""

View File

@ -26,10 +26,8 @@ one line.
import re # pragma: no cover import re # pragma: no cover
import sys # pragma: no cover import sys # pragma: no cover
from pytest_bdd.types import ( from pytest_bdd import types # pragma: no cover
FEATURE, SCENARIO, SCENARIO_OUTLINE, EXAMPLES, EXAMPLES_HEADERS, EXAMPLE_LINE, GIVEN, WHEN, from pytest_bdd import exceptions # pragma: no cover
THEN # pragma: no cover
)
class FeatureError(Exception): # pragma: no cover class FeatureError(Exception): # pragma: no cover
@ -48,16 +46,17 @@ class FeatureError(Exception): # pragma: no cover
features = {} # pragma: no cover features = {} # pragma: no cover
STEP_PREFIXES = { # pragma: no cover STEP_PREFIXES = [ # pragma: no cover
'Feature: ': FEATURE, ('Feature: ', types.FEATURE),
'Scenario Outline: ': SCENARIO_OUTLINE, ('Scenario Outline: ', types.SCENARIO_OUTLINE),
'Examples:': EXAMPLES, ('Examples: Vertical', types.EXAMPLES_VERTICAL),
'Scenario: ': SCENARIO, ('Examples:', types.EXAMPLES),
'Given ': GIVEN, ('Scenario: ', types.SCENARIO),
'When ': WHEN, ('Given ', types.GIVEN),
'Then ': THEN, ('When ', types.WHEN),
'And ': None, # Unknown step type ('Then ', types.THEN),
} ('And ', None), # Unknown step type
]
COMMENT_SYMBOLS = '#' # pragma: no cover COMMENT_SYMBOLS = '#' # pragma: no cover
@ -70,9 +69,9 @@ def get_step_type(line):
:param line: Line of the Feature file :param line: Line of the Feature file
:return: SCENARIO, GIVEN, WHEN, THEN, or `None` if can't be detected. :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): if line.startswith(prefix):
return STEP_PREFIXES[prefix] return _type
def get_step_params(name): def get_step_params(name):
@ -104,7 +103,7 @@ def remove_prefix(line):
:return: Line without the prefix. :return: Line without the prefix.
""" """
for prefix in STEP_PREFIXES: for prefix, _ in STEP_PREFIXES:
if line.startswith(prefix): if line.startswith(prefix):
return line[len(prefix):].strip() return line[len(prefix):].strip()
return line return line
@ -156,20 +155,21 @@ class Feature(object):
continue continue
mode = get_step_type(line) or mode 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', raise FeatureError('Given steps must be the first in withing the Scenario',
line_number, line) 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', raise FeatureError('When steps must be the first or follow Given steps',
line_number, line) 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', raise FeatureError('Then steps must follow Given or When steps',
line_number, line) line_number, line)
if mode == FEATURE: if mode == types.FEATURE:
if prev_mode != FEATURE: if prev_mode != types.FEATURE:
self.name = remove_prefix(line) self.name = remove_prefix(line)
else: else:
description.append(line) description.append(line)
@ -178,16 +178,21 @@ class Feature(object):
# Remove Feature, Given, When, Then, And # Remove Feature, Given, When, Then, And
line = remove_prefix(line) line = remove_prefix(line)
if mode in [SCENARIO, SCENARIO_OUTLINE]: if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
self.scenarios[line] = scenario = Scenario(line) self.scenarios[line] = scenario = Scenario(self, line)
elif mode == EXAMPLES: elif mode == types.EXAMPLES:
mode = EXAMPLES_HEADERS mode = types.EXAMPLES_HEADERS
elif mode == 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()]) scenario.set_param_names([l.strip() for l in line.split('|') if l.strip()])
mode = EXAMPLE_LINE mode = types.EXAMPLE_LINE
elif mode == EXAMPLE_LINE: elif mode == types.EXAMPLE_LINE:
scenario.add_example([l.strip() for l in line.split('|') if l.strip()]) 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) scenario.add_step(step_name=line, step_type=mode)
self.description = u'\n'.join(description) self.description = u'\n'.join(description)
@ -215,12 +220,15 @@ class Feature(object):
class Scenario(object): class Scenario(object):
"""Scenario.""" """Scenario."""
def __init__(self, name): def __init__(self, feature, name, example_converters=None):
self.feature = feature
self.name = name self.name = name
self.params = set() self.params = set()
self.steps = [] self.steps = []
self.example_params = [] self.example_params = []
self.examples = [] self.examples = []
self.vertical_examples = []
self.example_converters = example_converters
def add_step(self, step_name, step_type): def add_step(self, step_name, step_type):
"""Add step to the scenario. """Add step to the scenario.
@ -249,6 +257,58 @@ class Scenario(object):
""" """
self.examples.append(values) 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): class Step(object):
"""Step.""" """Step."""

View File

@ -26,34 +26,11 @@ 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 execute, recreate_function, get_caller_module, get_caller_function from pytest_bdd.steps import execute, recreate_function, get_caller_module, get_caller_function
from pytest_bdd.types import GIVEN from pytest_bdd.types import GIVEN
from pytest_bdd import exceptions
from pytest_bdd import plugin 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): def _inject_fixture(request, arg, value):
"""Inject fixture into pytest fixture request. """Inject fixture into pytest fixture request.
@ -122,17 +99,6 @@ def _find_step_function(request, name, encoding):
raise 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): def _execute_step_function(request, feature, step, step_func, example=None):
"""Execute step function.""" """Execute step function."""
kwargs = {} kwargs = {}
@ -176,20 +142,20 @@ def _execute_scenario(feature, scenario, request, encoding, example=None):
try: try:
# Check the step types are called in the correct order # Check the step types are called in the correct order
if step_func.step_type != step.type: 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) '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 # Check if the fixture that implements given step has not been yet used by another given step
if step.type == GIVEN: if step.type == GIVEN:
if step_func.fixture in givens: if step_func.fixture in givens:
raise GivenAlreadyUsed( raise exceptions.GivenAlreadyUsed(
'Fixture "{0}" that implements this "{1}" given step has been already used.'.format( 'Fixture "{0}" that implements this "{1}" given step has been already used.'.format(
step_func.fixture, step.name, step_func.fixture, step.name,
) )
) )
givens.add(step_func.fixture) givens.add(step_func.fixture)
except ScenarioValidationError as exception: except exceptions.ScenarioValidationError as exception:
request.config.hook.pytest_bdd_step_validation_error( request.config.hook.pytest_bdd_step_validation_error(
request=request, feature=feature, scenario=scenario, step=step, step_func=step_func, request=request, feature=feature, scenario=scenario, step=step, step_func=step_func,
exception=exception) 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) return get_fixture(caller_module, fixture, path=os.path.dirname(path), module=module)
def scenario( def _get_scenario_decorator(
feature_name, scenario_name, encoding='utf-8', example_converters=None, feature, feature_name, scenario, scenario_name, caller_module, caller_function, encoding):
caller_module=None, caller_function=None): """Get scenario decorator."""
"""Scenario.""" g = locals()
g['_execute_scenario'] = _execute_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 decorator(_pytestbdd_function): def decorator(_pytestbdd_function):
if isinstance(_pytestbdd_function, python.FixtureRequest): if isinstance(_pytestbdd_function, python.FixtureRequest):
raise ScenarioIsDecoratorOnly( raise exceptions.ScenarioIsDecoratorOnly(
'scenario function can only be used as a decorator. Refer to the documentation.') 'scenario function can only be used as a decorator. Refer to the documentation.')
g.update(locals()) g.update(locals())
@ -293,6 +227,8 @@ def scenario(
_scenario = recreate_function( _scenario = recreate_function(
g[_pytestbdd_function.__name__], module=caller_module, firstlineno=caller_function.f_lineno) g[_pytestbdd_function.__name__], module=caller_module, firstlineno=caller_function.f_lineno)
params = scenario.get_params()
if params: if params:
_scenario = pytest.mark.parametrize(*params)(_scenario) _scenario = pytest.mark.parametrize(*params)(_scenario)
@ -300,6 +236,34 @@ def scenario(
feature_name=feature_name, scenario_name=scenario_name) feature_name=feature_name, scenario_name=scenario_name)
return _scenario 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)

View File

@ -3,8 +3,10 @@
FEATURE = 'feature' # pragma: no cover FEATURE = 'feature' # pragma: no cover
SCENARIO_OUTLINE = 'scenario outline' # pragma: no cover SCENARIO_OUTLINE = 'scenario outline' # pragma: no cover
EXAMPLES = 'examples' # pragma: no cover EXAMPLES = 'examples' # pragma: no cover
EXAMPLES_VERTICAL = 'examples vertical' # pragma: no cover
EXAMPLES_HEADERS = 'example headers' # pragma: no cover EXAMPLES_HEADERS = 'example headers' # pragma: no cover
EXAMPLE_LINE = 'example line' # pragma: no cover EXAMPLE_LINE = 'example line' # pragma: no cover
EXAMPLE_LINE_VERTICAL = 'example line vertical' # pragma: no cover
SCENARIO = 'scenario' # pragma: no cover SCENARIO = 'scenario' # pragma: no cover
GIVEN = 'given' # pragma: no cover GIVEN = 'given' # pragma: no cover
WHEN = 'when' # pragma: no cover WHEN = 'when' # pragma: no cover

View File

@ -4,7 +4,7 @@ import functools
import re import re
import pytest import pytest
from pytest_bdd import scenario, given, when, then from pytest_bdd import scenario, given, when, then
from pytest_bdd.scenario import GivenAlreadyUsed from pytest_bdd import exceptions
@scenario( @scenario(
@ -73,5 +73,5 @@ def test_multiple_given(request):
) )
def test(): def test():
pass pass
with pytest.raises(GivenAlreadyUsed): with pytest.raises(exceptions.GivenAlreadyUsed):
test(request) test(request)

View File

@ -28,3 +28,14 @@ Scenario Outline: Outlined with some examples failing
| start | eat | left | | start | eat | left |
| 0 | 5 | 5 | | 0 | 5 | 5 |
| 12 | 5 | 7 | | 12 | 5 | 7 |
Scenario Outline: Outlined with vertical example table
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Examples: Vertical
| start | 12 | 2 |
| eat | 5 | 1 |
| left | 7 | 1 |

View File

@ -4,7 +4,7 @@ import re
import pytest import pytest
from pytest_bdd import given, when, then, scenario from pytest_bdd import given, when, then, scenario
from pytest_bdd.scenario import ScenarioExamplesNotValidError from pytest_bdd import exceptions
@scenario( @scenario(
@ -13,7 +13,8 @@ from pytest_bdd.scenario import ScenarioExamplesNotValidError
example_converters=dict(start=int, eat=float, left=str) example_converters=dict(start=int, eat=float, left=str)
) )
def test_outlined(): 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 <start> cucumbers') @given('there are <start> cucumbers')
@ -39,7 +40,7 @@ def should_have_left_cucumbers(start_cucumbers, start, eat, left):
def test_wrongly_outlined(request): def test_wrongly_outlined(request):
"""Test parametrized scenario when the test function lacks parameters.""" """Test parametrized scenario when the test function lacks parameters."""
with pytest.raises(ScenarioExamplesNotValidError) as exc: with pytest.raises(exceptions.ScenarioExamplesNotValidError) as exc:
@scenario( @scenario(
'outline.feature', 'outline.feature',
'Outlined with wrong examples', 'Outlined with wrong examples',
@ -67,3 +68,14 @@ def other_fixture(request):
) )
def test_outlined_with_other_fixtures(other_fixture): def test_outlined_with_other_fixtures(other_fixture):
"""Test outlined scenario also using other parametrized 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']])

View File

@ -1,13 +1,14 @@
"""Test scenario decorator."""
import pytest import pytest
from pytest_bdd import scenario from pytest_bdd import scenario
from pytest_bdd.scenario import ScenarioNotFound from pytest_bdd import exceptions
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."""
with pytest.raises(ScenarioNotFound): with pytest.raises(exceptions.ScenarioNotFound):
scenario( scenario(
'not_found.feature', 'not_found.feature',
'NOT FOUND' 'NOT FOUND'

View File

@ -1,7 +1,7 @@
import pytest import pytest
from pytest_bdd import scenario, given, when, then 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') @scenario('steps.feature', 'Executed step by step')
@ -84,7 +84,7 @@ def test_multiple_given(request):
def test(): def test():
pass pass
with pytest.raises(GivenAlreadyUsed): with pytest.raises(exceptions.GivenAlreadyUsed):
test(request) test(request)

View File

@ -1,9 +1,11 @@
"""Test wrong feature syntax.""" """Test wrong feature syntax."""
import re
import pytest import pytest
from pytest_bdd import scenario, given, when, then from pytest_bdd import scenario, given, when, then
from pytest_bdd.feature import FeatureError from pytest_bdd.feature import FeatureError
from pytest_bdd.scenario import StepTypeError from pytest_bdd import exceptions
@given('something') @given('something')
@ -57,10 +59,9 @@ def test_wrong_type_order(request, scenario_name):
def test_wrong_type_order(request): def test_wrong_type_order(request):
pass pass
with pytest.raises(StepTypeError) as excinfo: with pytest.raises(exceptions.StepTypeError) as excinfo:
test_wrong_type_order(request) test_wrong_type_order(request)
assert re.match(r'Wrong step type \"(\w+)\" while \"(\w+)\" is expected\.', excinfo.value.args[0])
excinfo # TODO: assert the exception args from parameters
def test_verbose_output(request): def test_verbose_output(request):