Scenario parametrization implemented. Coveralls integration. Coverage improvement

This commit is contained in:
Anatoly Bubenkov 2013-08-10 03:32:25 +02:00
parent a2ec61b622
commit f05045c15d
15 changed files with 176 additions and 29 deletions

View File

@ -4,9 +4,11 @@ python:
- "2.7"
- "3.3"
# command to install dependencies
install: "python setup.py develop"
install: pip install -e . python-coveralls
# # command to run tests
script: python setup.py test
after_success:
- coveralls
notifications:
email:
- bubenkoff@gmail.com

View File

@ -1,3 +1,11 @@
Changes between 0.4.7 and 0.5.0
-------------------------------
- Added parametrization to scenarios
- Coveralls.io integration
- Test coverage improvement/fixes
Changes between 0.4.6 and 0.4.7
-------------------------------

View File

@ -87,6 +87,52 @@ author of the article, but article should have some default author.
And there is an article
Step parameters
===============
Sometimes it is hard to write good scenarios without duplicating most of contents of existing scenario.
For example if you create some object with static param value, you might want to create another test with
different param value.
By Gherkin specification it's possible to have parameters in steps: http://docs.behat.org/guides/1.gherkin.html
Example:
Scenario: Parametrized given, when, thens
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
As you can see we don't use Scenario Outline, but use just Scenario, just because it's simple to implement for pytest.
The code will look like:
test_reuse = scenario(
'parametrized.feature',
'Parametrized given, when, thens',
# here we tell scenario about the parameters, it's not possible to get them automatically, as
# feature files are parsed on test runtime, not import time
params=['start', 'eat', 'left']
)
# here we use pytest power to parametrize test
test_reuse = pytest.mark.parametrize(['start', 'eat', 'left'], [(12, 5, 7)])(test_reuse)
@given('there are <start> cucumbers')
def start_cucumbers(start):
return dict(start=start)
@when('I eat <eat> cucumbers')
def eat_cucumbers(start_cucumbers, start, eat):
assert start_cucumbers['start'] == start
@then('I should have <left> cucumbers')
def should_have_left_cucumbers(start, eat, left):
assert start - eat == left
Reuse fixtures
================

View File

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

View File

@ -19,8 +19,9 @@ Syntax example:
:note: There're no multiline steps, the description of the step must fit in
one line.
"""
import re
from pytest_bdd.types import SCENARIO, GIVEN, WHEN, THEN
from pytest_bdd.types import SCENARIO, GIVEN, WHEN, THEN # pragma: no cover
class FeatureError(Exception):
@ -42,6 +43,8 @@ STEP_PREFIXES = {
COMMENT_SYMBOLS = '#'
STEP_PARAM_RE = re.compile('\<(.+)\>')
def get_step_type(line):
"""Detect step type by the beginning of the line.
@ -54,6 +57,14 @@ def get_step_type(line):
return STEP_PREFIXES[prefix]
def get_step_params(name):
"""Return step parameters."""
params = STEP_PARAM_RE.search(name)
if params:
return params.groups()
return ()
def strip(line):
"""Remove leading and trailing whitespaces and comments.
@ -135,8 +146,10 @@ class Scenario(object):
def __init__(self, name):
self.name = name
self.params = set()
self.steps = []
def add_step(self, step):
"""Add step."""
self.params.update(get_step_params(step))
self.steps.append(step)

View File

@ -1,10 +1,10 @@
"""Pytest plugin entry point. Used for any fixtures needed."""
import os.path
import os.path # pragma: no cover
import pytest
import pytest # pragma: no cover
@pytest.fixture
@pytest.fixture # pragma: no cover
def pytestbdd_feature_base_dir(request):
return os.path.dirname(request.module.__file__)

View File

@ -10,17 +10,18 @@ test_publish_article = scenario(
scenario_name='Publishing the article',
)
"""
import inspect
from os import path as op
import inspect # pragma: no cover
from os import path as op # pragma: no cover
from pytest_bdd.feature import Feature
from pytest_bdd.feature import Feature # pragma: no cover
from pytest_bdd.steps import recreate_function # pragma: no cover
class ScenarioNotFound(Exception):
class ScenarioNotFound(Exception): # pragma: no cover
"""Scenario Not Found"""
def scenario(feature_name, scenario_name):
def scenario(feature_name, scenario_name, params=()):
"""Scenario."""
def _scenario(request):
@ -41,4 +42,8 @@ def scenario(feature_name, scenario_name):
kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in inspect.getargspec(func).args)
func(**kwargs)
if params:
# add test parameters to function
_scenario = recreate_function(_scenario, add_args=params)
return _scenario

View File

@ -58,6 +58,7 @@ def given(name, fixture=None):
:note: Can't be used as a decorator when the fixture is specified.
"""
name = remove_prefix(name)
if fixture is not None:
module = get_caller_module()
func = lambda: lambda request: request.getfuncargvalue(fixture)
@ -94,10 +95,11 @@ 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, params=None):
"""Step decorator for the type and the name.
:param step_type: Step type (GIVEN, WHEN or THEN).
:param step_name: Step name as in the feature file.
:param params: Step params.
:return: Decorator function for the step.
@ -120,18 +122,17 @@ def _step_decorator(step_type, step_name):
step_name,
pytest.fixture(lambda: step_func),
)
return func
return decorator
def contribute_to_module(module, name, func):
"""Contribute a function to a module.
:param module: Module to contribute to.
:param name: Attribute name.
def recreate_function(func, module=None, add_args=()):
"""Recreate a function, replacing some info.
:param func: Function object.
"""
:param module: Module to contribute to.
:param add_args: Additional arguments to add to function."""
def get_code(func):
return func.__code__ if PY3 else func.func_code
@ -152,12 +153,28 @@ def contribute_to_module(module, name, func):
args = []
code = get_code(func)
for arg in argnames:
if arg == 'co_filename':
if module is not None and arg == 'co_filename':
args.append(module.__file__)
elif arg == 'co_argcount':
args.append(getattr(code, arg) + len(add_args))
elif arg == 'co_varnames':
args.append(tuple(add_args) + getattr(code, arg))
else:
args.append(getattr(code, arg))
set_code(func, CodeType(*args))
return func
def contribute_to_module(module, name, func):
"""Contribute a function to a module.
:param module: Module to contribute to.
:param name: Attribute name.
:param func: Function object.
"""
func = recreate_function(func, module=module)
setattr(module, name, func)

View File

@ -1,6 +1,6 @@
"""Common type definitions."""
SCENARIO = 'scenario'
GIVEN = 'given'
WHEN = 'when'
THEN = 'then'
SCENARIO = 'scenario' # pragma: no cover
GIVEN = 'given' # pragma: no cover
WHEN = 'when' # pragma: no cover
THEN = 'then' # pragma: no cover

3
requirements-testing.txt Normal file
View File

@ -0,0 +1,3 @@
mock
pytest-pep8
pytest-cov

View File

@ -73,6 +73,7 @@ Links
* `documentation <https://pytest-bdd.readthedocs.org/en/latest/>`_
"""
import os
import sys
from setuptools import setup
@ -83,15 +84,16 @@ class PyTest(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_args = ['tests', '-pep8', '--cov', 'pytest_bdd', '--cov-report', 'term-missing']
self.test_suite = True
def run_tests(self):
#import here, cause outside the eggs aren't loaded
# The import is here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(self.test_args)
sys.exit(errno)
tests_require = open(os.path.join(os.path.dirname(__file__), 'requirements-testing.txt')).read().split()
setup(
name='pytest-bdd',
@ -101,7 +103,7 @@ setup(
license='MIT license',
author_email='oleg.podsadny@gmail.com',
url='https://github.com/olegpidsadnyi/pytest-bdd',
version='0.4.7',
version='0.5.0',
classifiers=[
'Development Status :: 6 - Mature',
'Intended Audience :: Developers',
@ -125,6 +127,6 @@ setup(
'pytest-bdd = pytest_bdd.plugin',
]
},
tests_require=['mock'],
tests_require=tests_require,
packages=['pytest_bdd'],
)

View File

@ -0,0 +1,4 @@
Scenario: Some scenario
Given 1
When 2
Then 3

View File

@ -0,0 +1,4 @@
Scenario: Parametrized given, when, thens
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers

View File

@ -0,0 +1,28 @@
import pytest
from pytest_bdd.steps import when
from pytest_bdd import given, then, scenario
test_reuse = scenario(
'parametrized.feature',
'Parametrized given, when, thens',
params=['start', 'eat', 'left']
)
test_reuse = pytest.mark.parametrize(['start', 'eat', 'left'], [(12, 5, 7)])(test_reuse)
@given('there are <start> cucumbers')
def start_cucumbers(start):
return dict(start=start)
@when('I eat <eat> cucumbers')
def eat_cucumbers(start_cucumbers, start, eat):
assert start_cucumbers['start'] == start
@then('I should have <left> cucumbers')
def should_have_left_cucumbers(start, eat, left):
assert start - eat == left

View File

@ -0,0 +1,15 @@
import pytest
from pytest_bdd import scenario
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)