Django DRF 单元测试通过未执行的元类添加了动态混合

Django DRF unit tests added with dynamic mixins via metaclass not being executed

我正在尝试测试 DRF 端点,并尝试将混合动态添加到测试中,以便再次执行测试端点中允许的每种方法(get、post、put、patch、delete)

所以,我的想法是做一个基础测试 class,如果允许的话,它会自动添加一些混合到测试端点。我可以创建将从该基础继承的实际测试 class。

代码:

from rest_framework.test import APITestCase

class GetTestMixin:
    def test_get_all(self):
        response = self.client.get(self.uri)
        self.assertEqual(response.status_code,status.HTTP_200_OK)


class AutoMixinMeta(type):
    def __call__(cls, *args, **kwargs):
        allowed_methods = ['get', 'post']
        # cls would be the Test class, for example TestExample
        # cls.__bases__ is a tuple with the classes inherited in the Test class, for example:
        # (<class 'unit_tests.endpoints.base_test.RESTTestCase'>, <class 'rest_framework.test.APITestCase'>)
        bases = cls.__bases__
        for method in allowed_methods:
            bases += (cls.method_mixins[method.lower()],)
        # Create a new instance with all the mixins
        cls = type(cls.__name__, bases, dict(cls.__dict__))
        return type.__call__(cls, *args, **kwargs)

class RESTTestCase(metaclass=AutoMixinMeta):
    uri = None
    method_mixins = {
        'post': PostTestMixin,
        'get': GetTestMixin,
    }

class TestExample(RESTTestCase, APITestCase):
    uri = reverse('somemodel-list')

我期待 test_get_all 被执行,但它没有被执行。

混合到位。我在 TestExample 中创建了一个虚拟方法并放置了一个调试器,然后检查它,如下所示:

(Pdb) self.__class__
<class 'TestExample'>
(Pdb) self.__class__.__bases__
(<class 'RESTTestCase'>, <class 'rest_framework.test.APITestCase'>, <class 'GetTestMixin'>)

问题是收集要测试的 classes 的代码永远不会“看到”class 作为测试 classes 的一个实例,或者作为a subclass of: 从测试用例继承的 class 只有在创建实例时才存在。

实现此功能的唯一方法是在导入时创建派生的 classes,并将所需的动态 classes 绑定为模块上的顶级名称。

为此,您可以取消元class,只需将语句放在模块主体中,将新的 class 或 classes 分配给名称使用globals()。或者,如果您只需要 subclasses,而不是模块顶层,则可以将代码放在 __init_subclass__ 方法中。此方法在创建 class 时调用,而不是在实例化时调用,它应该可以工作。

from rest_framework.test import APITestCase

class GetTestMixin:
    def test_get_all(self):
        response = self.client.get(self.uri)
        self.assertEqual(response.status_code,status.HTTP_200_OK)




class RESTTestCase():
    uri = None
    method_mixins = {
        'post': PostTestMixin,
        'get': GetTestMixin,
    }
    
    def __init_subclass__(cls, *args, **kw):
        super.__init_subclass__(*args, **kw)
        if "Dynamic" in cls.__name__:
            return
        allowed_methods = ['get', 'post']
        
        bases = list(cls.__bases__)
        for method in allowed_methods:
            bases.append(cls.method_mixins[method.lower()])
        # Create a new instance with all the mixins
        new_cls = type(cls.__name__ + "Dynamic", bases, dict(cls.__dict__))
        globals()[new_cls.__name__] = new_cls


class TestExample(RESTTestCase, APITestCase):
    uri = reverse('somemodel-list')

# class TestExampleDynamic is created automatically when the `class` statement above resolves