python: fix instance handling in static and class method tests

and also fixes a regression in pytest 8.0.0 where `setup_method` crashes
if the class has static or class method tests.

It is allowed to have a test class with static/class methods which
request non-static/class method fixtures (including `setup_method`
xunit-fixture). I take it as a given that we need to support this
somewhat odd scenario (stdlib unittest also supports it).

This raises a question -- when a staticmethod test requests a bound
fixture, what is that fixture's `self`?

stdlib unittest says - a fresh instance for the test.

Previously, pytest said - some instance that is shared by all
static/class methods. This is definitely broken since it breaks test
isolation.

Change pytest to behave like stdlib unittest here.

In practice, this means stopping to rely on `self.obj.__self__` to get
to the instance from the test function's binding. This doesn't work
because staticmethods are not bound to anything.

Instead, keep the instance explicitly and use that.

BTW, I think this will allow us to change `Class`'s fixture collection
(`parsefactories`) to happen on the class itself instead of a class
instance, allowing us to avoid one class instantiation. But needs more
work.

Fixes #12065.
This commit is contained in:
Ran Benita
2024-03-09 09:08:44 +02:00
parent 774f0c44e6
commit 0dc0360351
6 changed files with 100 additions and 15 deletions

View File

@@ -410,22 +410,37 @@ def test_function_instance(pytester: Pytester) -> None:
items = pytester.getitems(
"""
def test_func(): pass
class TestIt:
def test_method(self): pass
@classmethod
def test_class(cls): pass
@staticmethod
def test_static(): pass
"""
)
assert len(items) == 4
assert isinstance(items[0], Function)
assert items[0].name == "test_func"
assert items[0].instance is None
assert isinstance(items[1], Function)
assert items[1].name == "test_method"
assert items[1].instance is not None
assert items[1].instance.__class__.__name__ == "TestIt"
# Even class and static methods get an instance!
# This is the instance used for bound fixture methods, which
# class/staticmethod tests are perfectly able to request.
assert isinstance(items[2], Function)
assert items[2].name == "test_class"
assert items[2].instance is not None
assert isinstance(items[3], Function)
assert items[3].name == "test_static"
assert items[3].instance is None
assert items[3].instance is not None
assert items[1].instance is not items[2].instance is not items[3].instance