diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 7f85bcdc7..3100aa15e 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -41,6 +41,8 @@ CONFIGURATION_ENV = 'DJANGO_CONFIGURATION' INVALID_TEMPLATE_VARS_ENV = 'FAIL_INVALID_TEMPLATE_VARS' +PY2 = sys.version_info[0] == 2 + # ############### pytest hooks ################ @@ -258,7 +260,7 @@ def pytest_configure(): _setup_django() -def _method_is_defined_at_leaf(cls, method_name): +def _classmethod_is_defined_at_leaf(cls, method_name): super_method = None for base_cls in cls.__bases__: @@ -268,7 +270,15 @@ def _method_is_defined_at_leaf(cls, method_name): assert super_method is not None, ( '%s could not be found in base class' % method_name) - return getattr(cls, method_name).__func__ is not super_method.__func__ + method = getattr(cls, method_name) + + try: + f = method.__func__ + except AttributeError: + pytest.fail('%s.%s should be a classmethod' % (cls, method_name)) + if PY2 and not (inspect.ismethod(method) and method.__self__ is cls): + pytest.fail('%s.%s should be a classmethod' % (cls, method_name)) + return f is not super_method.__func__ _disabled_classmethods = {} @@ -280,9 +290,9 @@ def _disable_class_methods(cls): _disabled_classmethods[cls] = ( cls.setUpClass, - _method_is_defined_at_leaf(cls, 'setUpClass'), + _classmethod_is_defined_at_leaf(cls, 'setUpClass'), cls.tearDownClass, - _method_is_defined_at_leaf(cls, 'tearDownClass'), + _classmethod_is_defined_at_leaf(cls, 'tearDownClass'), ) cls.setUpClass = types.MethodType(lambda cls: None, cls) diff --git a/tests/test_unittest.py b/tests/test_unittest.py index 954ff6d51..7b93b3771 100644 --- a/tests/test_unittest.py +++ b/tests/test_unittest.py @@ -122,6 +122,25 @@ def test_pass(self): ]) assert result.ret == 0 + def test_setUpClass_not_being_a_classmethod(self, django_testdir): + django_testdir.create_test_module(''' + from django.test import TestCase + + class TestFoo(TestCase): + def setUpClass(self): + pass + + def test_pass(self): + pass + ''') + + result = django_testdir.runpytest_subprocess('-v', '-s') + result.stdout.fnmatch_lines([ + "* ERROR at setup of TestFoo.test_pass *", + "E *Failed: .setUpClass should be a classmethod", # noqa:E501 + ]) + assert result.ret == 1 + def test_multi_inheritance_setUpClass(self, django_testdir): django_testdir.create_test_module(''' from django.test import TestCase