* add pytest_nose plugin

* have unittest functions always receive a fresh instance

--HG--
branch : 1.0.x
This commit is contained in:
holger krekel 2009-08-10 11:27:13 +02:00
parent a01e4769cc
commit b552f6eb46
9 changed files with 286 additions and 15 deletions

View File

@ -1,6 +1,10 @@
Changes between 1.0.0 and 1.0.1
=====================================
* added a 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* unicode fixes: capturing and unicode writes to sys.stdout
(through e.g a print statement) now work within tests,
they are encoded as "utf8" by default, also terminalwriting

64
doc/test/plugin/nose.txt Normal file
View File

@ -0,0 +1,64 @@
pytest_nose plugin
==================
nose-compatibility plugin: allow to run nose test suites natively.
.. contents::
:local:
This is an experimental plugin for allowing to run tests written
in the 'nosetests' style with py.test.
nosetests is a popular clone
of py.test and thus shares some philosophy. This plugin is an
attempt to understand and neutralize differences. It allows to
run nosetests' own test suite and a number of other test suites
without problems.
Usage
-------------
If you type::
py.test -p nose
where you would type ``nosetests``, you can run your nose style tests.
You might also try to run without the nose plugin to see where your test
suite is incompatible to the default py.test.
To avoid the need for specifying a command line option you can set an environment
variable::
PYTEST_PLUGINS=nose
or create a ``conftest.py`` file in your test directory or below::
# conftest.py
pytest_plugins = "nose",
If you find issues or have suggestions you may run::
py.test -p nose --pastebin=all
to create a URL of a test run session and send it with comments to the issue
tracker or mailing list.
Known issues
------------------
- nose-style doctests are not collected and executed correctly,
also fixtures don't work.
Start improving this plugin in 30 seconds
=========================================
Do you find the above documentation or the plugin itself lacking?
1. Download `pytest_nose.py`_ plugin source code
2. put it somewhere as ``pytest_nose.py`` into your import path
3. a subsequent ``py.test`` run will use your local version
Further information: extend_ documentation, other plugins_ or contact_.
.. include:: links.txt

View File

@ -7,7 +7,7 @@ plugins = [
('Plugins related to Python test functions and programs',
'xfail figleaf monkeypatch capture recwarn',),
('Plugins for other testing styles and languages',
'unittest doctest oejskit restdoc'),
'oejskit unittest nose doctest restdoc'),
('Plugins for generic reporting and failure logging',
'pastebin resultlog terminal',),
('internal plugins / core functionality',

View File

@ -396,6 +396,11 @@ class Directory(FSCollector):
def _ignore(self, path):
ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path)
return ignore_paths and path in ignore_paths
# XXX more refined would be:
if ignore_paths:
for p in ignore_paths:
if path == p or path.relto(p):
return True
def consider(self, path):
if self._ignore(path):

View File

@ -15,10 +15,15 @@ def pytest_funcarg__testdir(request):
# testdir.plugins.append(obj.testplugin)
# break
#else:
basename = request.module.__name__.split(".")[-1]
if basename.startswith("pytest_"):
modname = request.module.__name__.split(".")[-1]
if modname.startswith("pytest_"):
testdir.plugins.append(vars(request.module))
testdir.plugins.append(basename)
testdir.plugins.append(modname)
#elif modname.startswith("test_pytest"):
# pname = modname[5:]
# assert pname not in testdir.plugins
# testdir.plugins.append(pname)
# #testdir.plugins.append(vars(request.module))
else:
pass # raise ValueError("need better support code")
return testdir

View File

@ -0,0 +1,97 @@
"""nose-compatibility plugin: allow to run nose test suites natively.
This is an experimental plugin for allowing to run tests written
in the 'nosetests' style with py.test.
nosetests is a popular clone
of py.test and thus shares some philosophy. This plugin is an
attempt to understand and neutralize differences. It allows to
run nosetests' own test suite and a number of other test suites
without problems.
Usage
-------------
If you type::
py.test -p nose
where you would type ``nosetests``, you can run your nose style tests.
You might also try to run without the nose plugin to see where your test
suite is incompatible to the default py.test.
To avoid the need for specifying a command line option you can set an environment
variable::
PYTEST_PLUGINS=nose
or create a ``conftest.py`` file in your test directory or below::
# conftest.py
pytest_plugins = "nose",
If you find issues or have suggestions you may run::
py.test -p nose --pastebin=all
to create a URL of a test run session and send it with comments to the issue
tracker or mailing list.
Known issues
------------------
- nose-style doctests are not collected and executed correctly,
also fixtures don't work.
"""
import py
import inspect
import sys
def pytest_runtest_makereport(__call__, item, call):
SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
if SkipTest:
if call.excinfo and call.excinfo.errisinstance(SkipTest):
# let's substitute the excinfo with a py.test.skip one
call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when)
call.excinfo = call2.excinfo
def pytest_report_iteminfo(item):
# nose 0.11.1 uses decorators for "raises" and other helpers.
# for reporting progress by filename we fish for the filename
if isinstance(item, py.test.collect.Function):
obj = item.obj
if hasattr(obj, 'compat_co_firstlineno'):
fn = sys.modules[obj.__module__].__file__
if fn.endswith(".pyc"):
fn = fn[:-1]
#assert 0
#fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
lineno = obj.compat_co_firstlineno
return py.path.local(fn), lineno, obj.__module__
def pytest_runtest_setup(item):
if isinstance(item, (py.test.collect.Function)):
if isinstance(item.parent, py.test.collect.Generator):
gen = item.parent
if not hasattr(gen, '_nosegensetup'):
call_optional(gen.obj, 'setup')
if isinstance(gen.parent, py.test.collect.Instance):
call_optional(gen.parent.obj, 'setup')
gen._nosegensetup = True
call_optional(item.obj, 'setup')
def pytest_runtest_teardown(item):
if isinstance(item, py.test.collect.Function):
call_optional(item.obj, 'teardown')
#if hasattr(item.parent, '_nosegensetup'):
# #call_optional(item._nosegensetup, 'teardown')
# del item.parent._nosegensetup
def pytest_make_collect_report(collector):
if isinstance(collector, py.test.collect.Generator):
call_optional(collector.obj, 'setup')
def call_optional(obj, name):
method = getattr(obj, name, None)
if method:
method()

View File

@ -241,15 +241,10 @@ class TerminalReporter:
if self.config.option.traceconfig:
plugins = []
for plugin in self.config.pluginmanager.comregistry:
name = plugin.__class__.__name__
if name.endswith("Plugin"):
name = name[:-6]
#if name == "Conftest":
# XXX get filename
plugins.append(name)
else:
plugins.append(str(plugin))
name = getattr(plugin, '__name__', None)
if name is None:
name = plugin.__class__.__name__
plugins.append(name)
plugins = ", ".join(plugins)
self.write_line("active plugins: %s" %(plugins,))
for i, testarg in py.builtin.enumerate(self.config.args):

View File

@ -55,6 +55,9 @@ class UnitTestFunction(py.test.collect.Function):
if obj is not _dummy:
self._obj = obj
self._sort_value = sort_value
if hasattr(self.parent, 'newinstance'):
self.parent.newinstance()
self.obj = self._getobj()
def runtest(self):
target = self.obj
@ -87,7 +90,6 @@ def test_simple_unittest(testdir):
def test_setup(testdir):
testpath = testdir.makepyfile(test_two="""
import unittest
pytest_plugins = "pytest_unittest" # XXX
class MyTestCase(unittest.TestCase):
def setUp(self):
self.foo = 1
@ -98,6 +100,18 @@ def test_setup(testdir):
rep = reprec.matchreport("test_setUp")
assert rep.passed
def test_new_instances(testdir):
testpath = testdir.makepyfile("""
import unittest
class MyTestCase(unittest.TestCase):
def test_func1(self):
self.x = 2
def test_func2(self):
assert not hasattr(self, 'x')
""")
reprec = testdir.inline_run(testpath)
reprec.assertoutcome(passed=2)
def test_teardown(testdir):
testpath = testdir.makepyfile(test_three="""
import unittest

View File

@ -0,0 +1,87 @@
import py
py.test.importorskip("nose")
def test_nose_setup(testdir):
p = testdir.makepyfile("""
l = []
def test_hello():
assert l == [1]
def test_world():
assert l == [1,2]
test_hello.setup = lambda: l.append(1)
test_hello.teardown = lambda: l.append(2)
""")
result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([
"*2 passed*"
])
def test_nose_test_generator_fixtures(testdir):
p = testdir.makepyfile("""
# taken from nose-0.11.1 unit_tests/test_generator_fixtures.py
from nose.tools import eq_
called = []
def outer_setup():
called.append('outer_setup')
def outer_teardown():
called.append('outer_teardown')
def inner_setup():
called.append('inner_setup')
def inner_teardown():
called.append('inner_teardown')
def test_gen():
called[:] = []
for i in range(0, 5):
yield check, i
def check(i):
expect = ['outer_setup']
for x in range(0, i):
expect.append('inner_setup')
expect.append('inner_teardown')
expect.append('inner_setup')
eq_(called, expect)
test_gen.setup = outer_setup
test_gen.teardown = outer_teardown
check.setup = inner_setup
check.teardown = inner_teardown
class TestClass(object):
def setup(self):
print "setup called in", self
self.called = ['setup']
def teardown(self):
print "teardown called in", self
eq_(self.called, ['setup'])
self.called.append('teardown')
def test(self):
print "test called in", self
for i in range(0, 5):
yield self.check, i
def check(self, i):
print "check called in", self
expect = ['setup']
#for x in range(0, i):
# expect.append('setup')
# expect.append('teardown')
#expect.append('setup')
eq_(self.called, expect)
""")
result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([
"*10 passed*"
])