如何在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
相关产品推荐
相关产品推荐

