Skip to content

Commit

Permalink
Merge pull request #187 from cdent/fix-pytest-yield
Browse files Browse the repository at this point in the history
Fix pytest yield
  • Loading branch information
cdent committed Nov 30, 2016
2 parents 60ccdd0 + d858e83 commit 2fe6fd0
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 40 deletions.
43 changes: 34 additions & 9 deletions docs/source/loader.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,35 @@ pytest
.. _pytest_loader:

Since pytest does not support the ``load_tests`` system, a different
way of generating tests is required. A test file must be created
way of generating tests is required. Two techniques are supported.

The original method (described below) used yield statements to
generate tests which pytest would collect. This style of tests is
deprecated as of ``pytest>=3.0`` so a new style using pytest
fixtures has been developed.

pytest >= 3.0
-------------

In the newer technique, a test file is created that uses the
``pytest_generate_tests`` hook. Special care must be taken to always
import the ``test_pytest`` method which is the base test that the
pytest hook parametrizes to generate the tests from the YAML files.
Without the method, the hook will not be called and no tests generated.

Here is a simple example file:

.. literalinclude:: pytest3.0-example.py
:language: python

This can then be run with the usual pytest commands. For example::

py.test -svx pytest3.0-example.py

pytest < 3.0
------------

When using the older technique, test file must be created
that calls :meth:`~gabbi.driver.py_test_generator` and yields the
generated tests. That will look a bit like this:

Expand All @@ -78,14 +106,11 @@ This can then be run with the usual pytest commands. For example::

py.test -svx pytest-example.py

.. warning:: In ``pytest>=3.0`` yield tests are deprecated and using
them will cause pytest to produce a warning. If you
wish to ignore and hide these warnings add the
``--disable-pytest-warnings`` parameter to the
invocation of ``py.test`` or use a version of pytest
earlier than version ``3.0``. A new way of creating gabbi
tests that works more effectively with modern pytest is
being developed.
The older technique will continue to work with all versions of
``pytest<4.0`` but ``>=3.0`` will produce warnings. If you want to
use the older technique but not see the warnings add
``--disable-pytest-warnings`` parameter to the invocation of
``py.test``.

.. _source distribution: https://github.com/cdent/gabbi
.. _the tutorial repo: https://github.com/cdent/gabbi-demo
Expand Down
30 changes: 30 additions & 0 deletions docs/source/pytest3.0-example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""A sample pytest module for pytest >= 3.0."""

# For pathname munging
import os

# The module that py_test_generator comes from.
from gabbi import driver

# We need test_pytest so that pytest test collection works properly.
# Without this, the pytest_generate_tests method below will not be
# called.
from gabbi.driver import test_pytest # noqa

# We need access to the WSGI application that hosts our service
from myapp import wsgiapp

# We're using fixtures in the YAML files, we need to know where to
# load them from.
from myapp.test import fixtures

# By convention the YAML files are put in a directory named
# "gabbits" that is in the same directory as the Python test file.
TESTS_DIR = 'gabbits'


def pytest_generate_tests(metafunc):
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
driver.py_test_generator(
test_dir, intercept=wsgiapp.app,
fixture_module=fixtures, metafunc=metafunc)
39 changes: 34 additions & 5 deletions gabbi/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ def build_tests(path, loader, host=None, port=8001, intercept=None,
def py_test_generator(test_dir, host=None, port=8001, intercept=None,
prefix=None, test_loader_name=None,
fixture_module=None, response_handlers=None,
content_handlers=None, require_ssl=False, url=None):
content_handlers=None, require_ssl=False, url=None,
metafunc=None):
"""Generate tests cases for py.test
This uses build_tests to create TestCases and then yields them in
Expand All @@ -151,15 +152,43 @@ def py_test_generator(test_dir, host=None, port=8001, intercept=None,
prefix=prefix, require_ssl=require_ssl,
url=url)

test_list = []
for test in tests:
if hasattr(test, '_tests'):
# Establish fixtures as if they were tests. These will
# be cleaned up by the pytester plugin.
yield 'start_%s' % test._tests[0].__class__.__name__, \
test.start, result
test_list.append(('start_%s' % test._tests[0].__class__.__name__,
test.start, result))
for subtest in test:
yield '%s' % subtest.__class__.__name__, subtest, result
yield 'stop_%s' % test._tests[0].__class__.__name__, test.stop
test_list.append(('%s' % subtest.__class__.__name__,
subtest, result))
test_list.append(('stop_%s' % test._tests[0].__class__.__name__,
test.stop))

if metafunc:
if metafunc.function == test_pytest:
ids = []
args = []
for test in test_list:
if len(test) >= 3:
name, method, arg = test
else:
name, method = test
arg = None
ids.append(name)
args.append((method, arg))

metafunc.parametrize("test, result", argvalues=args, ids=ids)
else:
# preserve backwards compatibility with old calling style
return test_list


def test_pytest(test, result):
if result:
test(result)
else:
test()


def test_suite_from_yaml(loader, test_base_name, test_yaml, test_directory,
Expand Down
30 changes: 13 additions & 17 deletions gabbi/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@

def get_cleanname(item):
"""Extract a test name from a pytest Function item."""
cleanname = item.name[2:]
cleanname = cleanname[:-2]
return cleanname
if '[' in item.name:
cleanname = item.name.split('[', 1)[1]
cleanname = cleanname.split(']', 1)[0]
return cleanname
return item.name


def get_suitename(name):
Expand Down Expand Up @@ -86,10 +88,13 @@ def a_pytest_collection_modifyitems(items, config):
continue
suitename = get_suitename(cleanname)
if cleanname.startswith('start_'):
STARTS[suitename] = item
test = item.callspec.params['test']
result = item.callspec.params['result']
STARTS[suitename] = (test, result)
deselected.append(item)
elif cleanname.startswith('stop_'):
STOPS[suitename] = item
test = item.callspec.params['test']
STOPS[suitename] = test
deselected.append(item)
else:
remaining.append(item)
Expand Down Expand Up @@ -118,20 +123,11 @@ def pytest_runtest_setup(item):
run its priors after running this.
"""
if hasattr(item, 'starter'):
try:
# Python 2
item.starter.function(item.starter.obj.__self__, item._args[0])
except TypeError:
# Python 3
item.starter.function(item._args[0])
test, result = item.starter
test(result)


def pytest_runtest_teardown(item, nextitem):
"""Run a stopper if a test has one."""
if hasattr(item, 'stopper'):
try:
# Python 2
item.stopper.function(item.stopper.obj.__self__)
except TypeError:
# Python 3
item.stopper.function()
item.stopper()
15 changes: 6 additions & 9 deletions gabbi/tests/test_gabbits_pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,19 @@
import os

from gabbi import driver
# TODO(cdent): this test_* needs to be imported bare or things do not work
from gabbi.driver import test_pytest # noqa
from gabbi.tests import simple_wsgi
from gabbi.tests import test_intercept

TESTS_DIR = 'gabbits_intercept'


def test_from_build():

def pytest_generate_tests(metafunc):
os.environ['GABBI_TEST_URL'] = 'takingnames'
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
test_generator = driver.py_test_generator(
driver.py_test_generator(
test_dir, intercept=simple_wsgi.SimpleWsgi,
fixture_module=test_intercept,
response_handlers=[test_intercept.TestResponseHandler])

# TODO(cdent): Where is our Python3!
# yield from test_generator
for test in test_generator:
yield test
response_handlers=[test_intercept.TestResponseHandler],
metafunc=metafunc)

0 comments on commit 2fe6fd0

Please sign in to comment.