You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何扩展/重写Django TestCase实现多权限测试代码复用?

针对你在Django权限测试中遇到的代码冗余问题,我给你几个实用的解决方案,都是实际项目里常用的:

解决方案一:自定义扩展TestCase的基类

这是最直观且扩展性强的方案,把重复的用户创建、登录、请求逻辑封装到自定义基类中,所有需要权限测试的类都继承这个基类即可。

from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User

class PermissionTestCase(TestCase):
    def setUp(self):
        # 统一创建三级权限用户,子类无需重复编写
        self.simple_user = User.objects.create_user(username='simple_user', password='xxx')
        self.manager = User.objects.create_user(username='manager', password='xx')
        self.admin = User.objects.create_superuser(
            username='admin', 
            password='x', 
            email='admin@example.com'
        )
        # 若需要给用户分配权限组/具体权限,可在这里统一处理

    def login_as(self, user_type):
        """根据用户类型完成登录,并返回对应用户对象"""
        user_map = {
            'simple_user': self.simple_user,
            'manager': self.manager,
            'admin': self.admin
        }
        user = user_map[user_type]
        # 这里假设密码和用户类型标识对应,可根据实际情况调整
        self.client.login(username=user.username, password=user_type)
        return user

    def get_with_user(self, user_type, url_name, **kwargs):
        """用指定权限用户登录后发送GET请求"""
        self.login_as(user_type)
        return self.client.get(reverse(url_name, kwargs=kwargs))

    def post_with_user(self, user_type, url_name, data=None, **kwargs):
        """用指定权限用户登录后发送POST请求"""
        self.login_as(user_type)
        return self.client.post(reverse(url_name, kwargs=kwargs), data=data)

你的测试类就可以简化成这样:

class DocumentViewTest(PermissionTestCase):
    def test_view_url_access_for_all_roles(self):
        # 测试管理员访问
        resp_admin = self.get_with_user('admin', 'view_upload')
        self.assertEqual(str(resp_admin.context['user']), 'admin')
        
        # 测试经理访问
        resp_manager = self.get_with_user('manager', 'view_upload')
        self.assertEqual(str(resp_manager.context['user']), 'manager')
        
        # 测试普通用户访问
        resp_user = self.get_with_user('simple_user', 'view_upload')
        self.assertEqual(str(resp_user.context['user']), 'simple_user')
解决方案二:使用参数化测试(或手动子测试)

如果只是单个测试方法需要遍历多用户,参数化测试会让代码更简洁。可以用第三方库parameterized,或者Django自带的subTest手动实现。

方式1:使用parameterized库(需先安装pip install parameterized)

from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from parameterized import parameterized

class DocumentViewTest(TestCase):
    def setUp(self):
        self.simple_user = User.objects.create_user(username='simple_user', password='xxx')
        self.manager = User.objects.create_user(username='manager', password='xx')
        self.admin = User.objects.create_superuser(
            username='admin', 
            password='x', 
            email='admin@example.com'
        )
        # 存储用户信息映射
        self.user_info = {
            'simple_user': (self.simple_user, 'xxx'),
            'manager': (self.manager, 'xx'),
            'admin': (self.admin, 'x'),
        }

    @parameterized.expand([
        ('simple_user', 'simple_user'),
        ('manager', 'manager'),
        ('admin', 'admin'),
    ])
    def test_view_url_access(self, user_type, expected_username):
        user, password = self.user_info[user_type]
        self.client.login(username=user.username, password=password)
        resp = self.client.get(reverse('view_upload'))
        self.assertEqual(str(resp.context['user']), expected_username)

方式2:手动使用subTest(无需额外依赖)

from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User

class DocumentViewTest(TestCase):
    def setUp(self):
        # 定义用户列表,包含用户名和密码
        self.test_users = [
            ('simple_user', 'xxx'),
            ('manager', 'xx'),
            ('admin', 'x'),
        ]
        # 批量创建用户
        for username, password in self.test_users:
            if username == 'admin':
                User.objects.create_superuser(
                    username=username, 
                    password=password, 
                    email='admin@example.com'
                )
            else:
                User.objects.create_user(username=username, password=password)

    def test_view_url_access_for_all_roles(self):
        # 遍历用户,用subTest保证每个子测试独立执行
        for username, password in self.test_users:
            with self.subTest(username=username):
                self.client.login(username=username, password=password)
                resp = self.client.get(reverse('view_upload'))
                self.assertEqual(str(resp.context['user']), username)
解决方案三:用装饰器/工具类封装多用户测试流程

如果只是少数测试方法需要复用多用户逻辑,可以写一个装饰器来包裹测试函数:

def test_with_all_users(test_func):
    """装饰器:让测试函数自动遍历所有权限用户"""
    def wrapper(self, *args, **kwargs):
        # 这里可以把用户信息抽离到配置或基类中,避免硬编码
        test_users = [
            ('simple_user', 'xxx'),
            ('manager', 'xx'),
            ('admin', 'x'),
        ]
        for username, password in test_users:
            with self.subTest(username=username):
                self.client.login(username=username, password=password)
                # 把当前用户名传给测试函数
                test_func(self, username, *args, **kwargs)
    return wrapper

class DocumentViewTest(TestCase):
    def setUp(self):
        # 创建用户逻辑...

    @test_with_all_users
    def test_view_url_access(self, username):
        resp = self.client.get(reverse('view_upload'))
        self.assertEqual(str(resp.context['user']), username)
方案对比与推荐
  • 自定义基类:最推荐,适合有大量权限测试的场景,把重复逻辑完全封装,扩展性强(后续新增权限只需修改基类),代码可读性高。
  • 参数化/子测试:适合单个测试方法需要遍历多用户的场景,代码简洁,无需额外封装基类。
  • 装饰器:适合零散的测试方法,灵活性高,但复用性不如基类。

另外,你之前设想的直接重写Client的login/get/post方法不太推荐——Django的Client是核心测试工具,重写它可能破坏原有功能,且维护成本高。不如在基类中封装上层的业务方法更安全、可控。

内容的提问来源于stack exchange,提问作者Laurynas Tamulevičius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:38:19