forked from test_framework/pytest-bdd
Merge pull request #196 from thedrow/ISSUE-139-gherkin-terminal-reporter
Merged version of the gherkin reporter
This commit is contained in:
commit
a26ce26724
|
@ -1,10 +1,15 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
2.18.0
|
||||
------
|
||||
|
||||
- Add gherkin terminal reporter (spinus + thedrow)
|
||||
|
||||
2.17.2
|
||||
------
|
||||
|
||||
- Fix scenairo lines containing an ``@`` being parsed as a tag. (The-Compiler)
|
||||
- Fix scenario lines containing an ``@`` being parsed as a tag. (The-Compiler)
|
||||
|
||||
2.17.1
|
||||
------
|
||||
|
|
|
@ -1126,6 +1126,13 @@ To have an output in json format:
|
|||
py.test --cucumberjson=<path to json report>
|
||||
|
||||
|
||||
To enable gherkin-formatted output on terminal, use
|
||||
|
||||
::
|
||||
|
||||
py.test --gherkin-terminal-reporter
|
||||
|
||||
|
||||
Test code generation helpers
|
||||
----------------------------
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
from pytest_bdd.steps import given, when, then
|
||||
from pytest_bdd.scenario import scenario, scenarios
|
||||
|
||||
__version__ = '2.17.2'
|
||||
__version__ = '2.18.0'
|
||||
|
||||
__all__ = [given.__name__, when.__name__, then.__name__, scenario.__name__, scenarios.__name__]
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from _pytest.terminal import TerminalReporter
|
||||
|
||||
|
||||
def add_options(parser):
|
||||
group = parser.getgroup("terminal reporting", "reporting", after="general")
|
||||
group._addoption(
|
||||
'--gherkin-terminal-reporter',
|
||||
action="store_true",
|
||||
dest="gherkin_terminal_reporter",
|
||||
default=False,
|
||||
help=(
|
||||
"enable gherkin output"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def configure(config):
|
||||
if config.option.gherkin_terminal_reporter:
|
||||
# Get the standard terminal reporter plugin and replace it with our
|
||||
current_reporter = config.pluginmanager.getplugin('terminalreporter')
|
||||
if current_reporter.__class__ != TerminalReporter:
|
||||
raise Exception("gherkin-terminal-reporter is not compatibile with any other terminal reporter."
|
||||
"You can use only one terminal reporter."
|
||||
"Currently '{0}' is used."
|
||||
"Please decide to use one by deactivating {0} or gherkin-terminal-reporter."
|
||||
.format(current_reporter.__class__))
|
||||
gherkin_reporter = GherkinTerminalReporter(config)
|
||||
config.pluginmanager.unregister(current_reporter)
|
||||
config.pluginmanager.register(gherkin_reporter, 'terminalreporter')
|
||||
if config.pluginmanager.getplugin("dsession"):
|
||||
raise Exception("gherkin-terminal-reporter is not compatible with 'xdist' plugin.")
|
||||
|
||||
|
||||
class GherkinTerminalReporter(TerminalReporter):
|
||||
|
||||
def __init__(self, config):
|
||||
TerminalReporter.__init__(self, config)
|
||||
|
||||
def pytest_runtest_logstart(self, nodeid, location):
|
||||
# Prevent locationline from being printed since we already
|
||||
# show the module_name & in verbose mode the test name.
|
||||
pass
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
rep = report
|
||||
res = self.config.hook.pytest_report_teststatus(report=rep)
|
||||
cat, letter, word = res
|
||||
|
||||
if not letter and not word:
|
||||
# probably passed setup/teardown
|
||||
return
|
||||
|
||||
if isinstance(word, tuple):
|
||||
word, word_markup = word
|
||||
else:
|
||||
if rep.passed:
|
||||
word_markup = {'green': True}
|
||||
elif rep.failed:
|
||||
word_markup = {'red': True}
|
||||
elif rep.skipped:
|
||||
word_markup = {'yellow': True}
|
||||
feature_markup = {'blue': True}
|
||||
scenario_markup = word_markup
|
||||
|
||||
if self.verbosity <= 0:
|
||||
return TerminalReporter.pytest_runtest_logreport(self, rep)
|
||||
elif self.verbosity == 1:
|
||||
if hasattr(report, 'scenario'):
|
||||
self.ensure_newline()
|
||||
self._tw.write('Feature: ', **feature_markup)
|
||||
self._tw.write(report.scenario['feature']['name'], **feature_markup)
|
||||
self._tw.write('\n')
|
||||
self._tw.write(' Scenario: ', **scenario_markup)
|
||||
self._tw.write(report.scenario['name'], **scenario_markup)
|
||||
self._tw.write(' ')
|
||||
self._tw.write(word, **word_markup)
|
||||
self._tw.write('\n')
|
||||
else:
|
||||
return TerminalReporter.pytest_runtest_logreport(self, rep)
|
||||
elif self.verbosity > 1:
|
||||
if hasattr(report, 'scenario'):
|
||||
self.ensure_newline()
|
||||
self._tw.write('Feature: ', **feature_markup)
|
||||
self._tw.write(report.scenario['feature']['name'], **feature_markup)
|
||||
self._tw.write('\n')
|
||||
self._tw.write(' Scenario: ', **scenario_markup)
|
||||
self._tw.write(report.scenario['name'], **scenario_markup)
|
||||
self._tw.write('\n')
|
||||
for step in report.scenario['steps']:
|
||||
self._tw.write(' {} {}\n'.format(step['keyword'],
|
||||
step['name']), **scenario_markup)
|
||||
self._tw.write(' ' + word, **word_markup)
|
||||
self._tw.write('\n\n')
|
||||
else:
|
||||
return TerminalReporter.pytest_runtest_logreport(self, rep)
|
||||
self.stats.setdefault(cat, []).append(rep)
|
|
@ -6,6 +6,7 @@ from . import given, when, then
|
|||
from . import cucumber_json
|
||||
from . import generation
|
||||
from . import reporting
|
||||
from . import gherkin_terminal_reporter
|
||||
|
||||
from .fixtures import *
|
||||
|
||||
|
@ -33,12 +34,14 @@ def pytest_addoption(parser):
|
|||
"""Add pytest-bdd options."""
|
||||
cucumber_json.add_options(parser)
|
||||
generation.add_options(parser)
|
||||
gherkin_terminal_reporter.add_options(parser)
|
||||
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_configure(config):
|
||||
"""Configure all subplugins."""
|
||||
cucumber_json.configure(config)
|
||||
gherkin_terminal_reporter.configure(config)
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
Feature: Gherkin terminal reporter
|
||||
|
||||
Scenario: Should default output be the same as regular terminal reporter
|
||||
Given there is gherkin scenario implemented
|
||||
When tests are run
|
||||
Then output must be formatted the same way as regular one
|
||||
|
||||
Scenario: Should verbose mode enable displaying feature and scenario names rather than test names in a single line
|
||||
Given there is gherkin scenario implemented
|
||||
When tests are run with verbose mode
|
||||
Then output should contain single line feature description
|
||||
And output should contain single line scenario description
|
||||
|
||||
Scenario: Should verbose mode preserve displaying of regular tests as usual
|
||||
Given there is non-gherkin scenario implemented
|
||||
When tests are run with verbose mode
|
||||
Then output must be formatted the same way as regular one
|
||||
|
||||
Scenario: Should double verbose mode enable displaying of full gherkin scenario description
|
||||
Given there is gherkin scenario implemented
|
||||
When tests are run with very verbose mode
|
||||
Then output must contain full gherkin scenario description
|
||||
|
||||
Scenario: Should error message be displayed when no scenario is found
|
||||
Given there is gherkin scenario without implementation
|
||||
When tests are run with any verbosity mode
|
||||
Then output contains error about missing scenario implementation
|
||||
|
||||
Scenario: Should error message be displayed when no step is found
|
||||
Given there is gherkin scenario partially implemented
|
||||
When tests are run with any verbosity mode
|
||||
Then output contains error about missing step implementation
|
||||
|
||||
Scenario: Should error message be displayed when error occurs during test execution
|
||||
Given there is gherkin scenario with broken implementation
|
||||
When tests are run with any verbosity mode
|
||||
Then output contains error about missing scenario implementation
|
||||
|
||||
Scenario: Should local variables be displayed when --showlocals option is used
|
||||
Given there is gherkin scenario with broken implementation
|
||||
When tests are run with --showlocals
|
||||
Then error traceback contains local variable descriptions
|
|
@ -0,0 +1,278 @@
|
|||
import re
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from pytest_bdd import scenario, given, when, then
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should default output be the same as regular terminal reporter')
|
||||
def test_Should_default_output_be_the_same_as_regular_terminal_reporter():
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should verbose mode enable displaying feature and scenario names rather than test names in a single line')
|
||||
def test_Should_verbose_mode_enable_displaying_feature_and_scenario_names_rather_than_test_names_in_a_single_line():
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should verbose mode preserve displaying of regular tests as usual')
|
||||
def test_Should_verbose_mode_preserve_displaying_of_regular_tests_as_usual():
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should double verbose mode enable displaying of full gherkin scenario description')
|
||||
def test_Should_double_verbose_mode_enable_displaying_of_full_gherkin_scenario_description():
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should error message be displayed when no scenario is found')
|
||||
def test_Should_error_message_be_displayed_when_no_scenario_is_found(verbosity_mode):
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should error message be displayed when no step is found')
|
||||
def test_Should_error_message_be_displayed_when_no_step_is_found(verbosity_mode):
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should error message be displayed when error occurs during test execution')
|
||||
def test_Should_error_message_be_displayed_when_error_occurs_during_test_execution(verbosity_mode):
|
||||
pass
|
||||
|
||||
|
||||
@scenario('gherkin_terminal_reporter.feature',
|
||||
'Should local variables be displayed when --showlocals option is used')
|
||||
def test_Should_local_variables_be_displayed_when___showlocals_option_is_used():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(params=[0, 1, 2],
|
||||
ids=['compact mode', 'line per test', 'verbose'])
|
||||
def verbosity_mode(request):
|
||||
return request.param, '-' + 'v' * request.param if request.param else ''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_execution():
|
||||
return {}
|
||||
|
||||
|
||||
@given("there is non-gherkin scenario implemented")
|
||||
def non_gherkin_test(testdir):
|
||||
testdir.makepyfile(test_regular="""
|
||||
def test_1():
|
||||
pass
|
||||
""")
|
||||
|
||||
|
||||
@given("there is gherkin scenario implemented")
|
||||
def gherkin_scenario(testdir):
|
||||
testdir.makefile('.feature', test="""
|
||||
Feature: Gherkin terminal output feature
|
||||
Scenario: Scenario example 1
|
||||
Given there is a bar
|
||||
When the bar is accessed
|
||||
Then world explodes
|
||||
""")
|
||||
testdir.makepyfile(test_gherkin="""
|
||||
import pytest
|
||||
from pytest_bdd import given, when, scenario, then
|
||||
|
||||
@given('there is a bar')
|
||||
def a_bar():
|
||||
return 'bar'
|
||||
|
||||
@when('the bar is accessed')
|
||||
def the_bar_is_accessed():
|
||||
pass
|
||||
|
||||
@then('world explodes')
|
||||
def world_explodes():
|
||||
pass
|
||||
|
||||
@scenario('test.feature', 'Scenario example 1')
|
||||
def test_scenario_1():
|
||||
pass
|
||||
""")
|
||||
|
||||
|
||||
@when("tests are run")
|
||||
def tests_are_run(testdir, test_execution):
|
||||
test_execution['regular'] = testdir.runpytest()
|
||||
test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter')
|
||||
|
||||
|
||||
@then("output must be formatted the same way as regular one")
|
||||
def output_must_be_the_same_as_regular_reporter(test_execution):
|
||||
reg = test_execution['regular']
|
||||
ghe = test_execution['gherkin']
|
||||
assert reg.ret == 0
|
||||
assert ghe.ret == 0
|
||||
# last line can be different because of test execution time is printed
|
||||
reg_lines = reg.stdout.lines if reg.stdout.lines[-1] else reg.stdout.lines[:-2]
|
||||
reg_lines[-1] = re.sub(r' \d+\.\d+ ', ' X ', reg_lines[-1])
|
||||
ghe_lines = ghe.stdout.lines if ghe.stdout.lines[-1] else ghe.stdout.lines[:-2]
|
||||
ghe_lines[-1] = re.sub(r' \d+\.\d+ ', ' X ', ghe_lines[-1])
|
||||
for l1, l2 in zip(reg_lines, ghe_lines):
|
||||
assert l1 == l2
|
||||
|
||||
|
||||
@when("tests are run with verbose mode")
|
||||
def tests_are_run_with_verbose_mode(testdir, test_execution):
|
||||
test_execution['regular'] = testdir.runpytest('-v')
|
||||
test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter', '-v')
|
||||
|
||||
|
||||
@when("tests are run with very verbose mode")
|
||||
def tests_are_run_with_very_verbose_mode(testdir, test_execution):
|
||||
test_execution['regular'] = testdir.runpytest('-vv')
|
||||
test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter', '-vv')
|
||||
|
||||
|
||||
@then("output should contain single line feature description")
|
||||
def output_should_contain_single_line_feature_description(test_execution):
|
||||
ghe = test_execution['gherkin']
|
||||
assert ghe.ret == 0
|
||||
ghe.stdout.fnmatch_lines('Feature: Gherkin terminal output feature')
|
||||
|
||||
|
||||
@then("output should contain single line scenario description")
|
||||
def output_should_contain_single_line_scenario_description(test_execution):
|
||||
ghe = test_execution['gherkin']
|
||||
assert ghe.ret == 0
|
||||
ghe.stdout.fnmatch_lines('*Scenario: Scenario example 1 PASSED')
|
||||
|
||||
|
||||
@then("output must contain full gherkin scenario description")
|
||||
def output_should_contain_full_gherkin_scenario_description(test_execution):
|
||||
ghe = test_execution['gherkin']
|
||||
assert ghe.ret == 0
|
||||
ghe.stdout.fnmatch_lines('*Scenario: Scenario example 1')
|
||||
ghe.stdout.fnmatch_lines('*Given there is a bar')
|
||||
ghe.stdout.fnmatch_lines('*When the bar is accessed')
|
||||
ghe.stdout.fnmatch_lines('*Then world explodes')
|
||||
ghe.stdout.fnmatch_lines('*PASSED')
|
||||
|
||||
|
||||
@given('there is gherkin scenario without implementation')
|
||||
def gherkin_scenario_without_implementation(testdir):
|
||||
testdir.makefile('.feature', test="""
|
||||
Feature: Gherkin terminal output feature
|
||||
Scenario: Scenario example 1
|
||||
Given there is a bar
|
||||
When the bar is accessed
|
||||
Then world explodes
|
||||
""")
|
||||
testdir.makepyfile(test_gherkin="""
|
||||
import pytest
|
||||
from pytest_bdd import scenarios
|
||||
|
||||
scenarios('.')
|
||||
|
||||
""")
|
||||
|
||||
|
||||
@when('tests are run with any verbosity mode')
|
||||
def tests_are_run_with_any_verbosity_mode(
|
||||
test_execution, verbosity_mode, testdir,
|
||||
gherkin_scenario_without_implementation):
|
||||
# test_execution['gherkin'] = testdir.runpytest(
|
||||
# '--gherkin-terminal-reporter', '-vv')
|
||||
if verbosity_mode[1]:
|
||||
test_execution['gherkin'] = testdir.runpytest(
|
||||
'--gherkin-terminal-reporter', verbosity_mode[1])
|
||||
else:
|
||||
test_execution['gherkin'] = testdir.runpytest(
|
||||
'--gherkin-terminal-reporter')
|
||||
|
||||
|
||||
@then('output contains error about missing scenario implementation')
|
||||
def output_contains_error_about_missing_scenario_implementation(test_execution):
|
||||
ghe = test_execution['gherkin']
|
||||
assert ghe.ret
|
||||
ghe.stdout.fnmatch_lines('''*StepDefinitionNotFoundError: Step definition is not found: Given "there is a bar". '''
|
||||
'''Line 3 in scenario "Scenario example 1"*''')
|
||||
|
||||
|
||||
@given('there is gherkin scenario partially implemented')
|
||||
def partially_implemented_gherkin_scenario(testdir):
|
||||
testdir.makefile('.feature', test="""
|
||||
Feature: Gherkin terminal output feature
|
||||
Scenario: Scenario example 1
|
||||
Given there is a bar
|
||||
When the bar is accessed
|
||||
Then world explodes
|
||||
""")
|
||||
testdir.makepyfile(test_gherkin="""
|
||||
import pytest
|
||||
from pytest_bdd import given, when, scenario, then
|
||||
|
||||
@given('there is a bar')
|
||||
def a_bar():
|
||||
return 'bar'
|
||||
|
||||
@when('the bar is accessed')
|
||||
def the_bar_is_accessed():
|
||||
pass
|
||||
|
||||
@scenario('test.feature', 'Scenario example 1')
|
||||
def test_scenario_1():
|
||||
pass
|
||||
""")
|
||||
|
||||
|
||||
@then('output contains error about missing step implementation')
|
||||
def output_contains_error_about_missing_step_implementation(test_execution):
|
||||
ghe = test_execution['gherkin']
|
||||
assert ghe.ret
|
||||
ghe.stdout.fnmatch_lines('''*StepDefinitionNotFoundError: Step definition is not found: Given "there is a bar". '''
|
||||
'''Line 3 in scenario "Scenario example 1"*''')
|
||||
|
||||
|
||||
@given('there is gherkin scenario with broken implementation')
|
||||
def there_is_gherkin_scenario_with_broken_implementation(testdir):
|
||||
testdir.makefile('.feature', test="""
|
||||
Feature: Gherkin terminal output feature
|
||||
Scenario: Scenario example 1
|
||||
Given there is a bar
|
||||
When the bar is accessed
|
||||
Then world explodes
|
||||
""")
|
||||
testdir.makepyfile(test_gherkin="""
|
||||
import pytest
|
||||
from pytest_bdd import given, when, scenario, then
|
||||
|
||||
@given('there is a bar')
|
||||
def a_bar(request):
|
||||
return 'bar'
|
||||
|
||||
@when('the bar is accessed')
|
||||
def the_bar_is_accessed(request):
|
||||
local_var = 'value2'
|
||||
raise Exception("ERROR")
|
||||
|
||||
@scenario('test.feature', 'Scenario example 1')
|
||||
def test_scenario_1():
|
||||
pass
|
||||
""")
|
||||
|
||||
|
||||
@when('tests are run with --showlocals')
|
||||
def tests_are_run_with___showlocals(test_execution, testdir):
|
||||
test_execution['gherkin'] = testdir.runpytest('--gherkin-terminal-reporter', '--showlocals')
|
||||
|
||||
|
||||
@then('error traceback contains local variable descriptions')
|
||||
def error_traceback_contains_local_variable_descriptions(test_execution):
|
||||
ghe = test_execution['gherkin']
|
||||
assert ghe.ret
|
||||
ghe.stdout.fnmatch_lines('''request*=*<FixtureRequest for *''')
|
||||
ghe.stdout.fnmatch_lines('''local_var*=*''')
|
Loading…
Reference in New Issue