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

为何Django测试用例检测实际数据库并抛出IntegrityError而非使用内存库?

Django测试在非空数据库下抛出IntegrityError的排查与解决

问题描述

当开发数据库(db.sqlite3)为空时,所有测试运行正常;但数据库存有数据时,几乎每个测试都会抛出IntegrityError,错误提示外键关联失效(如custom_fields_customfield.definition_id找不到对应custom_fields_customfielddefinition.id),但实际数据库中存在对应的关联对象。按预期Django测试应使用独立的内存数据库,却似乎受到了实际开发数据库状态的影响。

错误堆栈示例:

======================================================================
ERROR: test_api_get_detail (core.projects.tests.test_project_api.ProjectConfidentialPrivateAPITests)
Getting confidential object via API (GET)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/test/testcases.py", line 299, in _setup_and_call
    self._post_teardown()
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/test/testcases.py", line 1199, in _post_teardown
    self._fixture_teardown()
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/test/testcases.py", line 1461, in _fixture_teardown
    connections[db_name].check_constraints()
  File "/Users/ramonkcom/Desktop/management/management-backend/venv/lib/python3.10/site-packages/django/db/backends/sqlite3/base.py", line 383, in check_constraints
    raise IntegrityError(
django.db.utils.IntegrityError: The row in table 'custom_fields_customfield' with primary key '1' has an invalid foreign key: custom_fields_customfield.definition_id contains a value '1' that does not have a corresponding value in custom_fields_customfielddefinition.id.

关键触发条件

  • 使用Python 3.10.8 + Django 4.0.5
  • 多数测试继承django.test.TestCase,部分继承django.test.TransactionTestCase(用于动态创建虚拟模型)
  • 动态模型创建代码示例:
# 虚拟模型定义
@localized
class DummyClass(models.Model):
    """Represents a dummy model for testing purpose"""

    class Meta:
        app_label = 'apps.localizer'

    name = models.CharField(max_length=255, blank=True, null=True)

# TransactionTestCase的setup/teardown
class LocalizedModelsTests(TransactionTestCase):
    def setUp(self):
        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(DummyClass)

    def tearDown(self):
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(DummyClass)
  • 数据库配置为文件型SQLite:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

核心原因分析

  1. SQLite测试隔离机制差异

    • TestCase使用事务回滚实现测试隔离,但仅对当前事务内的数据有效;如果数据库文件已有外部数据,事务回滚后残留的外部数据会在check_constraints阶段触发约束冲突。
    • TransactionTestCase不使用事务隔离,而是在每个测试后截断表。但如果配置的是文件数据库,它会直接操作开发数据库文件,动态创建/删除模型的操作会修改数据库结构,与现有数据产生约束冲突。
  2. 动态模型的副作用
    在TransactionTestCase中直接通过schema_editor创建/删除模型,操作的是当前数据库连接对应的实例。如果未配置测试专用数据库,这个操作会直接修改开发数据库的表结构,导致后续测试的约束检查失效。

  3. 未启用测试专用内存数据库
    Django默认不会自动将文件型SQLite切换为内存数据库,只有显式配置NAME: ':memory:'时,测试才会使用完全隔离的内存实例,避免受开发数据库数据影响。

解决方案

1. 强制测试使用内存数据库

修改settings.py,在测试时自动切换到内存数据库:

import sys

# 仅在测试模式下使用内存数据库
if 'test' in sys.argv:
    DATABASES['default'] = {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': ':memory:',
    }

这是最彻底的解决方案,所有测试都会使用独立的内存实例,完全隔离开发数据库的数据和结构。

2. 优化TransactionTestCase的模型操作

  • 确保动态模型的创建/删除仅作用于测试数据库,避免污染开发环境。
  • 检查localized装饰器是否存在全局注册模型的逻辑,导致跨测试的模型残留,必要时在tearDown中额外清理模型注册信息。

3. 测试前清理开发数据库(备选)

如果必须使用文件数据库,在测试运行前强制清理开发数据库:

python manage.py flush --noinput

但这种方式无法彻底隔离测试与开发环境,仅作为临时方案。

内容的提问来源于stack exchange,提问作者Ramon K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 03:35:26