15
15
from typing import Union
16
16
17
17
import _pytest ._code
18
- from _pytest .compat import getimfunc
19
18
from _pytest .compat import is_async_function
20
19
from _pytest .config import hookimpl
21
20
from _pytest .fixtures import FixtureRequest
@@ -63,6 +62,13 @@ class UnitTestCase(Class):
63
62
# to declare that our children do not support funcargs.
64
63
nofuncargs = True
65
64
65
+ def newinstance (self , method_name : str = "runTest" ):
66
+ # TestCase __init__ takes the method (test) name, so TestCaseFunction
67
+ # needs to pass it.
68
+ # runTest is a special no-op name for unittest, can be used when a dummy
69
+ # instance is needed.
70
+ return self .obj (method_name )
71
+
66
72
def collect (self ) -> Iterable [Union [Item , Collector ]]:
67
73
from unittest import TestLoader
68
74
@@ -76,15 +82,15 @@ def collect(self) -> Iterable[Union[Item, Collector]]:
76
82
self ._register_unittest_setup_class_fixture (cls )
77
83
self ._register_setup_class_fixture ()
78
84
79
- self .session ._fixturemanager .parsefactories (self , unittest = True )
85
+ self .session ._fixturemanager .parsefactories (self .newinstance (), self .nodeid )
86
+
80
87
loader = TestLoader ()
81
88
foundsomething = False
82
89
for name in loader .getTestCaseNames (self .obj ):
83
90
x = getattr (self .obj , name )
84
91
if not getattr (x , "__test__" , True ):
85
92
continue
86
- funcobj = getimfunc (x )
87
- yield TestCaseFunction .from_parent (self , name = name , callobj = funcobj )
93
+ yield TestCaseFunction .from_parent (self , name = name )
88
94
foundsomething = True
89
95
90
96
if not foundsomething :
@@ -169,31 +175,28 @@ def unittest_setup_method_fixture(
169
175
class TestCaseFunction (Function ):
170
176
nofuncargs = True
171
177
_excinfo : Optional [List [_pytest ._code .ExceptionInfo [BaseException ]]] = None
172
- _testcase : Optional ["unittest.TestCase" ] = None
173
178
174
179
def _getobj (self ):
175
- assert self .parent is not None
176
- # Unlike a regular Function in a Class, where `item.obj` returns
177
- # a *bound* method (attached to an instance), TestCaseFunction's
178
- # `obj` returns an *unbound* method (not attached to an instance).
179
- # This inconsistency is probably not desirable, but needs some
180
- # consideration before changing.
181
- return getattr (self .parent .obj , self .originalname ) # type: ignore[attr-defined]
180
+ assert isinstance (self .parent , UnitTestCase )
181
+ testcase = self .parent .obj (self .name )
182
+ return getattr (testcase , self .name )
183
+
184
+ # Backward compat for pytest-django; can be removed after pytest-django
185
+ # updates + some slack.
186
+ @property
187
+ def _testcase (self ):
188
+ return self ._obj .__self__
182
189
183
190
def setup (self ) -> None :
184
191
# A bound method to be called during teardown() if set (see 'runtest()').
185
192
self ._explicit_tearDown : Optional [Callable [[], None ]] = None
186
- assert self .parent is not None
187
- self ._testcase = self .parent .obj (self .name ) # type: ignore[attr-defined]
188
- self ._obj = getattr (self ._testcase , self .name )
189
193
super ().setup ()
190
194
191
195
def teardown (self ) -> None :
192
196
super ().teardown ()
193
197
if self ._explicit_tearDown is not None :
194
198
self ._explicit_tearDown ()
195
199
self ._explicit_tearDown = None
196
- self ._testcase = None
197
200
self ._obj = None
198
201
199
202
def startTest (self , testcase : "unittest.TestCase" ) -> None :
@@ -292,14 +295,14 @@ def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None:
292
295
def runtest (self ) -> None :
293
296
from _pytest .debugging import maybe_wrap_pytest_function_for_tracing
294
297
295
- assert self ._testcase is not None
298
+ testcase = self .obj . __self__
296
299
297
300
maybe_wrap_pytest_function_for_tracing (self )
298
301
299
302
# Let the unittest framework handle async functions.
300
303
if is_async_function (self .obj ):
301
304
# Type ignored because self acts as the TestResult, but is not actually one.
302
- self . _testcase (result = self ) # type: ignore[arg-type]
305
+ testcase (result = self ) # type: ignore[arg-type]
303
306
else :
304
307
# When --pdb is given, we want to postpone calling tearDown() otherwise
305
308
# when entering the pdb prompt, tearDown() would have probably cleaned up
@@ -311,16 +314,16 @@ def runtest(self) -> None:
311
314
assert isinstance (self .parent , UnitTestCase )
312
315
skipped = _is_skipped (self .obj ) or _is_skipped (self .parent .obj )
313
316
if self .config .getoption ("usepdb" ) and not skipped :
314
- self ._explicit_tearDown = self . _testcase .tearDown
315
- setattr (self . _testcase , "tearDown" , lambda * args : None )
317
+ self ._explicit_tearDown = testcase .tearDown
318
+ setattr (testcase , "tearDown" , lambda * args : None )
316
319
317
320
# We need to update the actual bound method with self.obj, because
318
321
# wrap_pytest_function_for_tracing replaces self.obj by a wrapper.
319
- setattr (self . _testcase , self .name , self .obj )
322
+ setattr (testcase , self .name , self .obj )
320
323
try :
321
- self . _testcase (result = self ) # type: ignore[arg-type]
324
+ testcase (result = self ) # type: ignore[arg-type]
322
325
finally :
323
- delattr (self . _testcase , self .name )
326
+ delattr (testcase , self .name )
324
327
325
328
def _traceback_filter (
326
329
self , excinfo : _pytest ._code .ExceptionInfo [BaseException ]
0 commit comments