diff --git a/_pytest/__init__.py b/_pytest/__init__.py index f23606567..50aeaf59e 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev12' +__version__ = '2.3.0.dev13' diff --git a/_pytest/python.py b/_pytest/python.py index 569edb731..8543d6e77 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -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 diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 903dc87a3..10b5c60a2 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -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'): diff --git a/doc/en/unittest.txt b/doc/en/unittest.txt index cdfce8c0f..7c3f8a14b 100644 --- a/doc/en/unittest.txt +++ b/doc/en/unittest.txt @@ -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 diff --git a/setup.py b/setup.py index 9d55119a2..82989480b 100644 --- a/setup.py +++ b/setup.py @@ -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'], diff --git a/testing/test_python.py b/testing/test_python.py index 8ce5e8b50..9a55565a5 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -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 diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 2ea227d35..63e9cd89a 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -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*")