Implement simple code generation command. Closes #32
This commit is contained in:
parent
c21dda176c
commit
57431dc218
|
@ -5,6 +5,7 @@ Unreleased
|
|||
----------
|
||||
|
||||
- Better reporting of a not found scenario (bubenkoff)
|
||||
- Simple test code generation implemented (bubenkoff)
|
||||
|
||||
|
||||
2.4.0
|
||||
|
|
|
@ -3,3 +3,4 @@ include README.rst
|
|||
include requirements-testing.txt
|
||||
include setup.py
|
||||
include LICENSE
|
||||
include pytest_bdd/templates/*.mak
|
||||
|
|
26
README.rst
26
README.rst
|
@ -731,6 +731,25 @@ To have an output in json format:
|
|||
py.test --cucumberjson=<path to json report>
|
||||
|
||||
|
||||
Test code generation helper
|
||||
---------------------------
|
||||
|
||||
For newcomers it's sometimes hard to write all needed test code without being frustrated.
|
||||
To simplify their life, simple code generator was implemented. It allows to create fully functional
|
||||
but of course empty tests and step definitions for given a feature file.
|
||||
It's done as a separate console script provided by pytest-bdd package:
|
||||
|
||||
::
|
||||
|
||||
pytest-bdd generate <feature file name> .. <feature file nameN>
|
||||
|
||||
It will print the generated code to the standard output so you can easily redirect it to the file:
|
||||
|
||||
::
|
||||
|
||||
pytest-bdd generate features/some.feature > tests/functional/test_some.py
|
||||
|
||||
|
||||
Migration of your tests from versions 0.x.x-1.x.x
|
||||
-------------------------------------------------
|
||||
|
||||
|
@ -743,15 +762,12 @@ decorator. Reasons for that:
|
|||
decorator more or not, so to support it along with functional approach there needed to be special parameter
|
||||
for that, which is also a backwards-incompartible change.
|
||||
|
||||
To help users migrate to newer version, there's migration console script provided with **migrate** extra:
|
||||
To help users migrate to newer version, there's migration subcommand of the `pytest-bdd` console script:
|
||||
|
||||
::
|
||||
|
||||
# install extra for migration
|
||||
pip install pytest-bdd[migrate]
|
||||
|
||||
# run migration script
|
||||
pytestbdd_migrate_tests <your test folder>
|
||||
pytest-bdd migrate <your test folder>
|
||||
|
||||
Under the hood the script does the replacement from this:
|
||||
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
"""pytest-bdd scripts."""
|
||||
|
||||
import glob2
|
||||
import argparse
|
||||
import itertools
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
|
||||
import glob2
|
||||
from mako.lookup import TemplateLookup
|
||||
|
||||
import pytest_bdd
|
||||
from pytest_bdd.feature import Feature
|
||||
|
||||
template_lookup = TemplateLookup(directories=[os.path.join(os.path.dirname(pytest_bdd.__file__), 'templates')])
|
||||
|
||||
MIGRATE_REGEX = re.compile(r'\s?(\w+)\s\=\sscenario\((.+)\)', flags=re.MULTILINE)
|
||||
|
||||
PYTHON_REPLACE_REGEX = re.compile('\W')
|
||||
|
||||
def migrate_tests():
|
||||
ALPHA_REGEX = re.compile('^\d+_*')
|
||||
|
||||
|
||||
def make_python_name(string):
|
||||
"""Make python attribute name out of a given string."""
|
||||
string = re.sub(PYTHON_REPLACE_REGEX, '', string.replace(' ', '_'))
|
||||
return re.sub(ALPHA_REGEX, '', string)
|
||||
|
||||
|
||||
def migrate_tests(args):
|
||||
"""Migrate outdated tests to the most recent form."""
|
||||
if len(sys.argv) != 2:
|
||||
print('Usage: pytestbdd_migrate_tests <path>')
|
||||
sys.exit(1)
|
||||
path = sys.argv[1]
|
||||
path = args.path
|
||||
for file_path in glob2.iglob(os.path.join(os.path.abspath(path), '**', '*.py')):
|
||||
migrate_tests_in_file(file_path)
|
||||
|
||||
|
@ -33,3 +46,61 @@ def migrate_tests_in_file(file_path):
|
|||
print('skipped: {0}'.format(file_path))
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
def check_existense(file_name):
|
||||
"""Check filename for existense."""
|
||||
if not os.path.isfile(file_name):
|
||||
raise argparse.ArgumentTypeError('{0} is an invalid file name'.format(file_name))
|
||||
return file_name
|
||||
|
||||
|
||||
def generate_code(args):
|
||||
"""Generate test code for the given filename."""
|
||||
features = []
|
||||
scenarios = []
|
||||
seen_names = set()
|
||||
for file_name in args.files:
|
||||
if file_name in seen_names:
|
||||
continue
|
||||
seen_names.add(file_name)
|
||||
base, name = os.path.split(file_name)
|
||||
feature = Feature.get_feature(base, name)
|
||||
features.append(feature)
|
||||
scenarios.extend(feature.scenarios.values())
|
||||
|
||||
steps = itertools.chain.from_iterable(
|
||||
scenario.steps for scenario in scenarios)
|
||||
steps = sorted(steps, key=lambda step: step.type)
|
||||
seen_steps = set()
|
||||
grouped_steps = []
|
||||
for step in (itertools.chain.from_iterable(
|
||||
sorted(group, key=lambda step: step.name)
|
||||
for _, group in itertools.groupby(steps, lambda step: step.type))):
|
||||
if step.name not in seen_steps:
|
||||
grouped_steps.append(step)
|
||||
seen_steps.add(step.name)
|
||||
|
||||
print(template_lookup.get_template('test.py.mak').render(
|
||||
feature=features[0], scenarios=scenarios, steps=grouped_steps, make_python_name=make_python_name))
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(prog='pytest-bdd')
|
||||
subparsers = parser.add_subparsers(help='sub-command help')
|
||||
|
||||
parser_generate = subparsers.add_parser('generate', help='generate help')
|
||||
parser_generate.add_argument(
|
||||
'files', metavar='FEATURE_FILE', type=check_existense, nargs='+',
|
||||
help='Feature files to generate test code with')
|
||||
parser_generate.set_defaults(func=generate_code)
|
||||
|
||||
parser_migrate = subparsers.add_parser('migrate', help='migrate help')
|
||||
parser_migrate.add_argument(
|
||||
'path', metavar='PATH',
|
||||
help='Migrate outdated tests to the most recent form')
|
||||
parser_migrate.set_defaults(func=migrate_tests)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
"""${ feature.name or feature.rel_filename } feature tests."""
|
||||
from functools import partial
|
||||
|
||||
from pytest_bdd import (given, when, then, scenario)
|
||||
|
||||
scenario = partial(scenario, feature.filename)
|
||||
|
||||
|
||||
% for scenario in sorted(scenarios, key=lambda scenario: scenario.name):
|
||||
@scenario('${scenario.name}')
|
||||
def test_${ make_python_name(scenario.name)}():
|
||||
"""${scenario.name}."""
|
||||
|
||||
|
||||
% endfor
|
||||
% for step in steps:
|
||||
@${step.type}('${step.name}')
|
||||
def ${ make_python_name(step.name)}():
|
||||
"""${step.name}."""
|
||||
% if not loop.last:
|
||||
|
||||
|
||||
% endif
|
||||
% endfor
|
8
setup.py
8
setup.py
|
@ -57,6 +57,8 @@ setup(
|
|||
cmdclass={'test': Tox},
|
||||
install_requires=[
|
||||
'pytest>=2.6.0',
|
||||
'glob2',
|
||||
'Mako',
|
||||
],
|
||||
# the following makes a plugin available to py.test
|
||||
entry_points={
|
||||
|
@ -65,12 +67,10 @@ setup(
|
|||
'pytest-bdd-cucumber-json = pytest_bdd.cucumber_json',
|
||||
],
|
||||
'console_scripts': [
|
||||
'pytestbdd_migrate_tests = pytest_bdd.scripts:migrate_tests [migrate]'
|
||||
'pytest-bdd = pytest_bdd.scripts:main'
|
||||
]
|
||||
},
|
||||
tests_require=['detox'],
|
||||
extras_require={
|
||||
'migrate': ['glob2']
|
||||
},
|
||||
packages=['pytest_bdd'],
|
||||
include_package_data=True,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
Feature: Code generation
|
||||
|
||||
Scenario: Given and when using the same fixture should not evaluate it twice
|
||||
Given I have an empty list
|
||||
And 1 have a fixture (appends 1 to a list) in reuse syntax
|
||||
|
||||
When I use this fixture
|
||||
|
||||
Then my list should be [1]
|
|
@ -0,0 +1,49 @@
|
|||
"""Test code generation command."""
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from pytest_bdd.scripts import main
|
||||
|
||||
PATH = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def test_generate(monkeypatch, capsys):
|
||||
"""Test if the code is generated by a given feature."""
|
||||
monkeypatch.setattr(sys, 'argv', ['', 'generate', os.path.join(PATH, 'generate.feature')])
|
||||
main()
|
||||
out, err = capsys.readouterr()
|
||||
assert out == textwrap.dedent('''
|
||||
"""Code generation feature tests."""
|
||||
from functools import partial
|
||||
|
||||
from pytest_bdd import (given, when, then, scenario)
|
||||
|
||||
scenario = partial(scenario, feature.filename)
|
||||
|
||||
|
||||
@scenario('Given and when using the same fixture should not evaluate it twice')
|
||||
def test_Given_and_when_using_the_same_fixture_should_not_evaluate_it_twice():
|
||||
"""Given and when using the same fixture should not evaluate it twice."""
|
||||
|
||||
|
||||
@given('1 have a fixture (appends 1 to a list) in reuse syntax')
|
||||
def have_a_fixture_appends_1_to_a_list_in_reuse_syntax():
|
||||
"""1 have a fixture (appends 1 to a list) in reuse syntax."""
|
||||
|
||||
|
||||
@given('I have an empty list')
|
||||
def I_have_an_empty_list():
|
||||
"""I have an empty list."""
|
||||
|
||||
|
||||
@then('my list should be [1]')
|
||||
def my_list_should_be_1():
|
||||
"""my list should be [1]."""
|
||||
|
||||
|
||||
@when('I use this fixture')
|
||||
def I_use_this_fixture():
|
||||
"""I use this fixture."""
|
||||
|
||||
'''[1:].replace(u"'", u"'"))
|
Loading…
Reference in New Issue