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

问题描述

我正在尝试测试 DRF 端点,并尝试将混合动态添加到测试中,以便再次执行端点中允许的每种方法获取、发布、放置、修补、删除

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

代码

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,**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'>)

解决方法

问题在于收集待测试类的代码永远不会将该类“视为”Test 类的实例,或作为以下类的子类:从测试用例继承的类仅在实例存在时才存在已创建。

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

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

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