如何扩展/重写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
相关产品推荐
相关产品推荐

