Scenario parametrization implemented. Coveralls integration. Coverage improvement
This commit is contained in:
parent
a2ec61b622
commit
f05045c15d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-------------------------------
|
||||
|
||||
|
|
46
README.md
46
README.md
|
@ -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
|
||||
================
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
mock
|
||||
pytest-pep8
|
||||
pytest-cov
|
10
setup.py
10
setup.py
|
@ -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'],
|
||||
)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Scenario: Some scenario
|
||||
Given 1
|
||||
When 2
|
||||
Then 3
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
Loading…
Reference in New Issue