forked from test_framework/pytest-bdd
multiline steps implemented
This commit is contained in:
parent
e935d6eee9
commit
60591ced61
|
@ -1,6 +1,12 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
- Implemented multiline steps (bubenkoff)
|
||||
|
||||
|
||||
2.0.1
|
||||
-----
|
||||
|
||||
|
|
57
README.rst
57
README.rst
|
@ -38,7 +38,7 @@ Example
|
|||
publish_article.feature:
|
||||
|
||||
.. code-block:: gherkin
|
||||
|
||||
|
||||
Feature: Blog
|
||||
A site where you can publish your articles.
|
||||
|
||||
|
@ -174,6 +174,61 @@ Example code also shows possibility to pass argument converters which may be use
|
|||
different than strings.
|
||||
|
||||
|
||||
Multiline steps
|
||||
---------------
|
||||
|
||||
As Gherkin, pytest-bdd supports multiline steps (aka `PyStrings <http://docs.behat.org/guides/1.gherkin.html#pystrings>`_).
|
||||
But in much cleaner and powerful way:
|
||||
|
||||
.. code-block:: gherkin
|
||||
|
||||
Scenario: Multiline step using sub indentation
|
||||
Given I have a step with:
|
||||
Some
|
||||
Extra
|
||||
Lines
|
||||
Then the text should be parsed with correct indentation
|
||||
|
||||
Step is considered as multiline one, if the **next** line(s) after it's first line, is indented relatively
|
||||
to the first line. The step name is then simply extended by adding futher lines with newlines.
|
||||
In the example above, the Given step name will be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""I have a step with:\nSome\nExtra\nLines"""
|
||||
|
||||
You can of course register step using full name (including the newlines), but it seems more practical to use
|
||||
step arguments and capture lines after first line (or some subset of them) into the argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import re
|
||||
|
||||
from pytest_bdd import given, then, scenario
|
||||
|
||||
|
||||
@scenario(
|
||||
'multiline.feature',
|
||||
'Multiline step using sub indentation',
|
||||
)
|
||||
def test_multiline():
|
||||
pass
|
||||
|
||||
|
||||
@given(re.compile(r'I have a step with:\n(?P<text>.+)', re.DOTALL))
|
||||
def i_have_text(text):
|
||||
return text
|
||||
|
||||
|
||||
@then('the text should be parsed with correct indentation')
|
||||
def eat_cucumbers(i_have_text, text):
|
||||
assert i_have_text == text == """Some
|
||||
Extra
|
||||
Lines"""
|
||||
|
||||
Pay attention to the re.DOTALL option used for step registration. When used, .+ will also capture newlines.
|
||||
|
||||
|
||||
Scenario parameters
|
||||
-------------------
|
||||
Scenario decorator can accept such optional keyword arguments:
|
||||
|
|
|
@ -146,52 +146,62 @@ class Feature(object):
|
|||
|
||||
with _open_file(filename, encoding) as f:
|
||||
content = force_unicode(f.read(), encoding)
|
||||
step = None
|
||||
for line_number, line in enumerate(content.splitlines()):
|
||||
raw_line = line.strip()
|
||||
line = strip_comments(line)
|
||||
if not line:
|
||||
unindented_line = line.lstrip()
|
||||
line_indent = len(line) - len(unindented_line)
|
||||
if step and step.indent < line_indent:
|
||||
# multiline step, so just add line and continue
|
||||
step.add_line(line.lstrip())
|
||||
continue
|
||||
mode = get_step_type(line) or mode
|
||||
stripped_line = line.strip()
|
||||
clean_line = strip_comments(line)
|
||||
if not clean_line:
|
||||
continue
|
||||
mode = get_step_type(clean_line) or mode
|
||||
|
||||
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',
|
||||
line_number, line)
|
||||
line_number, clean_line)
|
||||
|
||||
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',
|
||||
line_number, line)
|
||||
line_number, clean_line)
|
||||
|
||||
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',
|
||||
line_number, line)
|
||||
line_number, clean_line)
|
||||
|
||||
if mode == types.FEATURE:
|
||||
if prev_mode != types.FEATURE:
|
||||
self.name = remove_prefix(line)
|
||||
self.name = remove_prefix(clean_line)
|
||||
else:
|
||||
description.append(line)
|
||||
description.append(clean_line)
|
||||
|
||||
prev_mode = mode
|
||||
|
||||
# Remove Feature, Given, When, Then, And
|
||||
line = remove_prefix(line)
|
||||
clean_line = remove_prefix(clean_line)
|
||||
if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
|
||||
self.scenarios[line] = scenario = Scenario(self, line)
|
||||
self.scenarios[clean_line] = scenario = Scenario(self, clean_line)
|
||||
step = None
|
||||
elif mode == types.EXAMPLES:
|
||||
step = None
|
||||
mode = types.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('|')[1:-1] if l.strip()])
|
||||
scenario.set_param_names([l.strip() for l in clean_line.split('|')[1:-1] if l.strip()])
|
||||
mode = types.EXAMPLE_LINE
|
||||
elif mode == types.EXAMPLE_LINE:
|
||||
scenario.add_example([l.strip() for l in raw_line.split('|')[1:-1]])
|
||||
scenario.add_example([l.strip() for l in stripped_line.split('|')[1:-1]])
|
||||
elif mode == types.EXAMPLE_LINE_VERTICAL:
|
||||
line = [l.strip() for l in raw_line.split('|')[1:-1]]
|
||||
scenario.add_example_row(line[0], line[1:])
|
||||
step = None
|
||||
clean_line = [l.strip() for l in stripped_line.split('|')[1:-1]]
|
||||
scenario.add_example_row(clean_line[0], clean_line[1:])
|
||||
elif mode and mode != types.FEATURE:
|
||||
scenario.add_step(step_name=line, step_type=mode)
|
||||
step = scenario.add_step(step_name=clean_line, step_type=mode, indent=line_indent)
|
||||
|
||||
self.description = u'\n'.join(description)
|
||||
|
||||
|
@ -228,7 +238,7 @@ class Scenario(object):
|
|||
self.vertical_examples = []
|
||||
self.example_converters = example_converters
|
||||
|
||||
def add_step(self, step_name, step_type):
|
||||
def add_step(self, step_name, step_type, indent):
|
||||
"""Add step to the scenario.
|
||||
|
||||
:param step_name: Step name.
|
||||
|
@ -237,7 +247,9 @@ class Scenario(object):
|
|||
"""
|
||||
params = get_step_params(step_name)
|
||||
self.params.update(params)
|
||||
self.steps.append(Step(name=step_name, type=step_type, params=params))
|
||||
step = Step(name=step_name, type=step_type, params=params, scenario=self, indent=indent)
|
||||
self.steps.append(step)
|
||||
return step
|
||||
|
||||
def set_param_names(self, keys):
|
||||
"""Set parameter names.
|
||||
|
@ -311,7 +323,13 @@ class Scenario(object):
|
|||
class Step(object):
|
||||
"""Step."""
|
||||
|
||||
def __init__(self, name, type, params):
|
||||
def __init__(self, name, type, params, scenario, indent):
|
||||
self.name = name
|
||||
self.indent = indent
|
||||
self.type = type
|
||||
self.params = params
|
||||
self.scenario = scenario
|
||||
|
||||
def add_line(self, line):
|
||||
"""Add line to the multiple step."""
|
||||
self.name += '\n' + line
|
||||
|
|
2
setup.py
2
setup.py
|
@ -6,7 +6,7 @@ from setuptools import setup
|
|||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
|
||||
version = '2.0.1'
|
||||
version = '2.1.0'
|
||||
|
||||
|
||||
class Tox(TestCommand):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Feature: Multiline step using sub indentation
|
||||
Given I have a step with:
|
||||
Some
|
||||
Extra
|
||||
Lines
|
||||
Then text should be parsed with correct indentation
|
||||
Scenario: Multiline step using sub indentation
|
||||
Given I have a step with:
|
||||
Some
|
||||
Extra
|
||||
Lines
|
||||
Then the text should be parsed with correct indentation
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
"""Multiline steps tests."""
|
||||
import re
|
||||
|
||||
from pytest_bdd import given, then, scenario
|
||||
|
||||
|
||||
@scenario(
|
||||
'multiline.feature',
|
||||
'Multiline step using sub indentation',
|
||||
)
|
||||
def test_multiline():
|
||||
pass
|
||||
|
||||
|
||||
@given(re.compile(r'I have a step with:\n(?P<text>.+)', re.DOTALL))
|
||||
def i_have_text(text):
|
||||
return text
|
||||
|
||||
|
||||
@then('the text should be parsed with correct indentation')
|
||||
def eat_cucumbers(i_have_text, text):
|
||||
assert i_have_text == text == """Some
|
||||
Extra
|
||||
Lines"""
|
Loading…
Reference in New Issue