Class methods can now be discovered as tests (#10552)

Fix #10525
This commit is contained in:
Marko Pacak 2022-12-02 16:53:04 +01:00 committed by GitHub
parent eca93db05b
commit 9fbd67dd4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 24 additions and 8 deletions

View File

@ -225,6 +225,7 @@ Marcin Bachry
Marco Gorelli Marco Gorelli
Mark Abramowitz Mark Abramowitz
Mark Dickinson Mark Dickinson
Marko Pacak
Markus Unterwaditzer Markus Unterwaditzer
Martijn Faassen Martijn Faassen
Martin Altmayer Martin Altmayer

View File

@ -0,0 +1 @@
Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods.

View File

@ -50,8 +50,8 @@ Conventions for Python test discovery
* In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_. * In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
* From those files, collect test items: * From those files, collect test items:
* ``test`` prefixed test functions or methods outside of class * ``test`` prefixed test functions or methods outside of class.
* ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method) * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method). Methods decorated with ``@staticmethod`` and ``@classmethods`` are also considered.
For examples of how to customize your test discovery :doc:`/example/pythoncollection`. For examples of how to customize your test discovery :doc:`/example/pythoncollection`.

View File

@ -403,8 +403,8 @@ class PyCollector(PyobjMixin, nodes.Collector):
def istestfunction(self, obj: object, name: str) -> bool: def istestfunction(self, obj: object, name: str) -> bool:
if self.funcnamefilter(name) or self.isnosetest(obj): if self.funcnamefilter(name) or self.isnosetest(obj):
if isinstance(obj, staticmethod): if isinstance(obj, (staticmethod, classmethod)):
# staticmethods need to be unwrapped. # staticmethods and classmethods need to be unwrapped.
obj = safe_getattr(obj, "__func__", False) obj = safe_getattr(obj, "__func__", False)
return callable(obj) and fixtures.getfixturemarker(obj) is None return callable(obj) and fixtures.getfixturemarker(obj) is None
else: else:

View File

@ -416,7 +416,7 @@ def test_function_instance(pytester: Pytester) -> None:
def test_static(): pass def test_static(): pass
""" """
) )
assert len(items) == 3 assert len(items) == 4
assert isinstance(items[0], Function) assert isinstance(items[0], Function)
assert items[0].name == "test_func" assert items[0].name == "test_func"
assert items[0].instance is None assert items[0].instance is None
@ -424,6 +424,6 @@ def test_function_instance(pytester: Pytester) -> None:
assert items[1].name == "test_method" assert items[1].name == "test_method"
assert items[1].instance is not None assert items[1].instance is not None
assert items[1].instance.__class__.__name__ == "TestIt" assert items[1].instance.__class__.__name__ == "TestIt"
assert isinstance(items[2], Function) assert isinstance(items[3], Function)
assert items[2].name == "test_static" assert items[3].name == "test_static"
assert items[2].instance is None assert items[3].instance is None

View File

@ -735,6 +735,20 @@ class Test_genitems:
assert s.endswith("test_example_items1.testone") assert s.endswith("test_example_items1.testone")
print(s) print(s)
def test_classmethod_is_discovered(self, pytester: Pytester) -> None:
"""Test that classmethods are discovered"""
p = pytester.makepyfile(
"""
class TestCase:
@classmethod
def test_classmethod(cls) -> None:
pass
"""
)
items, reprec = pytester.inline_genitems(p)
ids = [x.getmodpath() for x in items] # type: ignore[attr-defined]
assert ids == ["TestCase.test_classmethod"]
def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None: def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None:
"""Test that Python_classes and Python_functions config options work """Test that Python_classes and Python_functions config options work
as prefixes and glob-like patterns (#600).""" as prefixes and glob-like patterns (#600)."""