Django测试中出现'Duplicate entry'错误的原因排查
原本运行正常的Django单元测试,添加向MySQL发送原生查询的代码后,所有涉及认证的单元测试均抛出异常,即使移除这些代码问题仍存在。
错误信息
_mysql.connection.query(self, query)
django.db.utils.IntegrityError: (1062, "Duplicate entry 'myuser' for key 'auth_user.username'")
测试代码(添加查询前)
class AnnotationPage(TestCase): def setUp(self): self.factory = RequestFactory() self.user = User.objects.create_user( username='myuser', password='12345678') def test_labeling(self): ajaxData = {'testing': 'True', 'direction': 'forward'} request = self.factory.post('/annotation', ajaxData) request.user = self.user response = AnnotationView(request) assert response.content == b'{"image": "image.png", "id": 1}'
测试代码(添加查询后)
class AnnotationPage(TestCase): def setUp(self): self.factory = RequestFactory() self.user = User.objects.create_user( username='myuser', password='12345678') self.cursor = connection.cursor() query = "TRUNCATE TABLE homepage_imagelabel" self.cursor.execute(query) def test_labeling(self): self.cursor = connection.cursor() query = "INSERT INTO homepage_imagelabel (ImageFile) VALUES ('image.png')" self.cursor.execute(query) ajaxData = {'testing': 'True', 'direction': 'forward'} request = self.factory.post('/annotation', ajaxData) request.user = self.user response = AnnotationView(request) assert response.content == b'{"image": "image.png", "id": 1}'
核心问题是**TRUNCATE语句会隐式提交事务**,而Django的TestCase默认依赖事务隔离每个测试用例:每个测试开始时开启事务,测试结束后回滚事务,以此保证测试数据不会互相污染。
当你在setUp里执行TRUNCATE时,这个操作会强制提交当前事务,直接破坏了Django的事务隔离机制——之前测试创建的myuser没有被回滚,仍然留在数据库中。后续测试再执行User.objects.create_user创建同名用户时,就触发了唯一键冲突。
即使你移除了TRUNCATE代码,之前的测试已经污染了测试数据库的状态(比如auth_user表中已存在myuser),如果测试数据库没有被重新创建,问题会持续存在。
替换
TRUNCATE为Django ORM操作
不要用原生TRUNCATE,改用Django提供的删除方法,它会遵守事务规则:# 替换TRUNCATE语句 from homepage.models import ImageLabel ImageLabel.objects.all().delete()必须用原生SQL时,改用
DELETE
如果一定要用原生SQL,用DELETE FROM代替TRUNCATE,因为DELETE不会隐式提交事务:query = "DELETE FROM homepage_imagelabel" self.cursor.execute(query)重置测试数据库
运行测试时添加--noinput参数,强制重新创建测试数据库,清理残留数据:python manage.py test --noinput如果你之前用了
--keepdb参数保留测试库,这个操作会彻底重置数据库状态。避免在测试中使用会隐式提交的SQL语句
除了TRUNCATE,像ALTER TABLE、DROP TABLE这类DDL语句也会隐式提交事务,测试中尽量用Django ORM提供的方法操作数据。
内容的提问来源于stack exchange,提问作者Davood Dorostkar

