implement full @pytest.setup function unittest.TestCase interaction

This commit is contained in:
holger krekel 2012-09-18 10:54:12 +02:00
parent d9c24552fc
commit a7c6688bd6
7 changed files with 129 additions and 16 deletions

View File

@ -1,2 +1,2 @@
#
__version__ = '2.3.0.dev12'
__version__ = '2.3.0.dev13'

View File

@ -1001,10 +1001,14 @@ class FuncargRequest:
if clscol:
return clscol.obj
@scopeproperty()
@property
def instance(self):
""" instance (can be None) on which test function was collected. """
return py.builtin._getimself(self.function)
# unittest support hack, see _pytest.unittest.TestCaseFunction
try:
return self._pyfuncitem._testcase
except AttributeError:
return py.builtin._getimself(self.function)
@scopeproperty()
def module(self):
@ -1341,7 +1345,7 @@ class FuncargManager:
for fin in l:
fin()
def _parsefactories(self, holderobj, nodeid):
def _parsefactories(self, holderobj, nodeid, unittest=False):
if holderobj in self._holderobjseen:
return
#print "parsefactories", holderobj
@ -1372,7 +1376,7 @@ class FuncargManager:
setup = getattr(obj, "_pytestsetup", None)
if setup is not None:
scope = setup.scope
sf = SetupCall(self, nodeid, obj, scope)
sf = SetupCall(self, nodeid, obj, scope, unittest)
self.setuplist.append(sf)
continue
faclist = self.arg2facspec.setdefault(argname, [])
@ -1428,7 +1432,6 @@ class FuncargManager:
request._factorystack.append(setupcall)
mp = monkeypatch()
try:
#mp.setattr(request, "_setupcall", setupcall, raising=False)
mp.setattr(request, "scope", setupcall.scope)
kwargs = {}
for name in setupcall.funcargnames:
@ -1438,7 +1441,12 @@ class FuncargManager:
self.session._setupstate.addfinalizer(setupcall.finish, scol)
for argname in setupcall.funcargnames: # XXX all deps?
self.addargfinalizer(setupcall.finish, argname)
setupcall.execute(kwargs)
# for unittest-setup methods we need to provide
# the correct instance
posargs = ()
if setupcall.unittest:
posargs = (request.instance,)
setupcall.execute(posargs, kwargs)
finally:
mp.undo()
request._factorystack.remove(setupcall)
@ -1457,20 +1465,21 @@ class FuncargManager:
class SetupCall:
""" a container/helper for managing calls to setup functions. """
def __init__(self, funcargmanager, baseid, func, scope):
def __init__(self, funcargmanager, baseid, func, scope, unittest):
self.funcargmanager = funcargmanager
self.baseid = baseid
self.func = func
self.funcargnames = getfuncargnames(func)
self.funcargnames = getfuncargnames(func, startindex=int(unittest))
self.scope = scope
self.scopenum = scopes.index(scope)
self.active = False
self.unittest= unittest
self._finalizer = []
def execute(self, kwargs):
def execute(self, posargs, kwargs):
assert not self.active
self.active = True
self.func(**kwargs)
self.func(*posargs, **kwargs)
def addfinalizer(self, finalizer):
assert self.active

View File

@ -21,6 +21,8 @@ def pytest_pycollect_makeitem(collector, name, obj):
class UnitTestCase(pytest.Class):
def collect(self):
self.session.funcargmanager._parsefactories(self.obj, self.nodeid,
unittest=True)
loader = py.std.unittest.TestLoader()
module = self.getparent(pytest.Module).obj
cls = self.obj
@ -56,6 +58,8 @@ class TestCaseFunction(pytest.Function):
pytest.skip(self._obj.skip)
if hasattr(self._testcase, 'setup_method'):
self._testcase.setup_method(self._obj)
if hasattr(self, "_request"):
self._request._callsetup()
def teardown(self):
if hasattr(self._testcase, 'teardown_method'):

View File

@ -24,8 +24,8 @@ Running it yields::
$ py.test test_unittest.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
plugins: xdist, bugzilla, cache, oejskit, pep8, cov
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev12
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
collecting ... collected 1 items
test_unittest.py F
@ -47,3 +47,64 @@ Running it yields::
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
Moreover, you can use the new :ref:`@pytest.setup functions <@pytest.setup>`
functions and make use of pytest's unique :ref:`funcarg mechanism` in your
test suite::
# content of test_unittest_funcargs.py
import pytest
import unittest
class MyTest(unittest.TestCase):
@pytest.setup()
def chdir(self, tmpdir):
tmpdir.chdir() # change to pytest-provided temporary directory
tmpdir.join("samplefile.ini").write("# testdata")
def test_method(self):
s = open("samplefile.ini").read()
assert "testdata" in s
Running this file should give us one passed test because the setup
function took care to prepare a directory with some test data
which the unittest-testcase method can now use::
$ py.test -q test_unittest_funcargs.py
collecting ... collected 1 items
.
1 passed in 0.28 seconds
If you want to make a database attribute available on unittest.TestCases
instances, based on a marker, you can do it using :ref:`pytest.mark`` and
:ref:`setup functions`::
# content of test_unittest_marked_db.py
import pytest
import unittest
@pytest.factory()
def db():
class DummyDB:
x = 1
return DummyDB()
@pytest.setup()
def stick_db_to_self(request, db):
if hasattr(request.node.markers, "needsdb"):
request.instance.db = db
class MyTest(unittest.TestCase):
def test_method(self):
assert not hasattr(self, "db")
@pytest.mark.needsdb
def test_method2(self):
assert self.db.x == 1
Running it passes both tests, one of which will see a ``db`` attribute
because of the according ``needsdb`` marker::
$ py.test -q test_unittest_marked_db.py
collecting ... collected 2 items
..
2 passed in 0.03 seconds

View File

@ -24,7 +24,7 @@ def main():
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
version='2.3.0.dev12',
version='2.3.0.dev13',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -1792,8 +1792,7 @@ class TestFuncargManager:
reprec.assertoutcome(passed=1)
class TestSetupDiscovery:
def pytest_funcarg__testdir(self, request):
testdir = request.getfuncargvalue("testdir")
def pytest_funcarg__testdir(self, testdir):
testdir.makeconftest("""
import pytest
@pytest.setup()
@ -1832,6 +1831,21 @@ class TestSetupDiscovery:
reprec = testdir.inline_run("-s")
reprec.assertoutcome(passed=1)
def test_setup_at_classlevel(self, testdir):
testdir.makepyfile("""
import pytest
class TestClass:
@pytest.setup()
def permethod(self, request):
request.instance.funcname = request.function.__name__
def test_method1(self):
assert self.funcname == "test_method1"
def test_method2(self):
assert self.funcname == "test_method2"
""")
reprec = testdir.inline_run("-s")
reprec.assertoutcome(passed=2)
def test_callables_nocode(self, testdir):
"""
a imported mock.call would break setup/factory discovery

View File

@ -480,3 +480,28 @@ def test_unittest_unexpected_failure(testdir):
])
def test_unittest_setup_interaction(testdir):
testdir.makepyfile("""
import unittest
import pytest
class MyTestCase(unittest.TestCase):
@pytest.setup(scope="class")
def perclass(self, request):
request.cls.hello = "world"
@pytest.setup(scope="function")
def perfunction(self, request):
request.instance.funcname = request.function.__name__
def test_method1(self):
assert self.funcname == "test_method1"
assert self.hello == "world"
def test_method2(self):
assert self.funcname == "test_method2"
def test_classattr(self):
assert self.__class__.hello == "world"
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("*3 passed*")