You need to enable JavaScript to run this app. 如何在Flask应用中使用unittest测试数据库外键关联逻辑
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flask应用中使用unittest测试数据库外键关联逻辑

适配Flask 1.1.2 + Flask-SQLAlchemy 2.4.4 + MySQL的外键约束单元测试实现方案

当前测试用例的核心短板

现有测试仅覆盖正常关联写入流程,完全没有验证外键约束的实际校验效果:

  • 没有模拟「写入不存在关联主键的非法外键值」场景,无法验证数据库外键关联约束是否生效
  • 没有模拟「空值写入外键字段」场景,无法验证外键字段的非空约束是否生效
  • 没有覆盖提交异常后的会话回滚逻辑,很容易因测试会话状态异常导致用例偶发失效

前置校验:确认外键约束的生效前提

很多时候测试跑不出预期效果,不是测试代码的问题,是底层配置根本没让外键约束生效:

  • 确认MySQL测试库的表引擎为InnoDB,MyISAM引擎不支持外键约束,哪怕模型定义了ForeignKey也不会触发校验
  • 确认测试环境的数据库连接指向真实MySQL实例,不要默认套用开发环境的SQLite配置——SQLite默认关闭外键校验,会导致测试结果完全失真
  • 确认db.create_all()生成的表结构和当前模型定义完全一致,没有残留旧版本生成的无约束表结构
  • 确认ContactClient模型的client_id字段已正确标记db.ForeignKey('clients.code')和nullable=False,和表结构定义对齐

可直接落地的测试代码实现

基于你现有的unittest测试框架,补充核心校验场景,完整代码如下:

import unittest
from app import create_app, db
from app.models.clients import Client, ContactClient
from sqlalchemy.exc import IntegrityError

class ClientModelTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()
        # 每次初始化前清空残留表结构,避免旧结构影响测试结果
        db.drop_all()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()

    def test_normal_foreign_key_write(self):
        # 先创建主表关联记录
        test_client = Client(code=1, name_fantasy='empresa_test')
        db.session.add(test_client)
        db.session.commit()
        # 写入带合法外键的从表记录
        test_contact = ContactClient(
            business_mail='empresa_test@example.com',
            ddi=55,
            ddd=11,
            phone=12345678,
            client_id=1
        )
        db.session.add(test_contact)
        db.session.commit()
        # 验证关联关系可正常查询
        saved_contact = ContactClient.query.filter_by(business_mail='empresa_test@example.com').first()
        self.assertIsNotNone(saved_contact)
        self.assertEqual(saved_contact.client.code, 1)

    def test_invalid_foreign_key_throw_error(self):
        # 不提前创建code=999对应的Client记录,直接写入带该外键的从表数据
        invalid_contact = ContactClient(
            business_mail='invalid@example.com',
            client_id=999
        )
        db.session.add(invalid_contact)
        # 断言提交时会抛出数据库完整性约束异常
        with self.assertRaises(IntegrityError):
            db.session.commit()
        # 异常后必须回滚会话,避免会话状态污染后续用例
        db.session.rollback()

    def test_null_foreign_key_throw_error(self):
        # 不传入client_id值,触发非空约束校验
        null_contact = ContactClient(
            business_mail='null@example.com',
            client_id=None
        )
        db.session.add(null_contact)
        with self.assertRaises(IntegrityError):
            db.session.commit()
        db.session.rollback()

测试执行的关键注意点

  • 遇到提交抛出的完整性异常后,必须执行db.session.rollback(),否则SQLAlchemy的会话会进入永久失效状态,同测试类下后续的所有用例都会执行失败
  • 所有校验要走真实的SQL提交动作,不要依赖ORM在应用层做的前置拦截,要确认约束是由数据库本身触发的——这也是之前你遇到「未设置nullable=False时不合法数据可正常写入」的核心原因:ORM层不会替你兜底字段校验,只要值传到数据库端,没有对应约束就会直接放行
  • 不要在测试用例中混用不同测试场景的会话数据,每个用例的前置和后置要严格隔离,避免互相干扰

内容的提问来源于stack exchange,提问作者Brunoazzireluto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.04 07:24:06