测试 setUpClass 中创建的对象无法通过数据库访问问题求助
解决Django测试中setUpClass创建对象无法被数据库访问的问题
哦,这个问题我太熟悉了!这其实是Django测试框架的事务隔离机制在搞鬼,我来给你拆解清楚:
问题根源
Django的TestCase默认会为每个测试方法启动一个独立的数据库事务,测试结束后自动回滚,保证测试之间互不干扰。但setUpClass是在所有测试方法执行之前运行的,它创建的对象是在这些测试事务之外的——也就是说,测试方法里的数据库连接看不到setUpClass中创建的数据,因为事务是隔离的。
另外,我还注意到你代码里的一个小坑:你手动设置了cls.report_id = 99,但cls.report = Report.objects.create()生成的实际ID肯定不是99(除非你特意配置了自增起始值),这也会导致你在测试方法里用这个ID找对象时出问题。
解决方案
方案1:改用setUp代替setUpClass(最推荐)
把初始化逻辑移到setUp方法里,它会在每个测试方法执行前运行,并且处于测试方法的事务内部,这样创建的对象就能被测试方法正常访问:
class TestReportModel(TestCase): def setUp(self): self.factory = RequestFactory() self.user = User.objects.create(username="user1", password="password") self.report = Report.objects.create() # 用实际生成的report id,不要硬编码 self.report_id = self.report.id def test_report_employee_with_access(self): request = self.factory.get('/reports/{}'.format(self.report_id)) request.user = self.user # 这里直接用self.report就行,不用重新赋值 response = self.report(request, self.report_id) # 后续的断言逻辑...
方案2:如果必须用setUpClass,使用TransactionTestCase
如果你一定要在所有测试前只初始化一次数据,可以改用TransactionTestCase——它不会用事务隔离测试,而是在每个测试后清空数据库表。注意这种方式下测试之间会共享数据,需要自己处理隔离:
from django.test import TransactionTestCase class TestReportModel(TransactionTestCase): @classmethod def setUpClass(cls): super().setUpClass() cls.factory = RequestFactory() cls.user = User.objects.create(username="user1", password="password") cls.report = Report.objects.create() cls.report_id = cls.report.id @classmethod def tearDownClass(cls): # 手动清理数据,或者让框架自动truncate cls.user.delete() cls.report.delete() super().tearDownClass() def test_report_employee_with_access(self): request = self.factory.get('/reports/{}'.format(self.report_id)) request.user = self.user response = self.report(request, self.report_id) # 断言逻辑...
方案3:手动控制事务(进阶)
如果你坚持用TestCase+setUpClass,可以在setUpClass里用事务提交,然后在测试方法里手动刷新对象:
from django.db import transaction class TestReportModel(TestCase): @classmethod def setUpClass(cls): super().setUpClass() # 在原子块里创建并提交 with transaction.atomic(): cls.factory = RequestFactory() cls.user = User.objects.create(username="user1", password="password") cls.report = Report.objects.create() cls.report_id = cls.report.id # 提交后,测试方法里需要刷新对象才能看到 cls.user.refresh_from_db() cls.report.refresh_from_db() def test_report_employee_with_access(self): # 测试前再刷新一次确保能访问 self.user.refresh_from_db() self.report.refresh_from_db() request = self.factory.get('/reports/{}'.format(self.report_id)) request.user = self.user response = self.report(request, self.report_id) # 断言逻辑...
额外提醒
- 不要硬编码
report_id,一定要用创建后对象的实际id属性,不然请求的URL对应的对象根本不存在。 - 测试方法里的
report.user = request.user这行其实没必要,因为你后面已经把request.user设为self.user了,直接用request.user就好。
内容的提问来源于stack exchange,提问作者Айдар Мулюков
相关产品推荐
相关产品推荐

