为何第二次测试结果会因是否提前执行其他测试而变化?
核心原因:drop_all破坏了测试隔离的正常流程
你的问题根源出在SQLBaseModel.metadata.drop_all(bind=engine)的执行时机和它对数据库状态的影响上,具体分这几个情况:
Fixture执行顺序与数据库状态冲突
因为app_with_db是function级别fixture,每个测试都会完整走一遍「初始化数据库→添加fake_users→执行测试→清理」的流程。当你在测试结束的teardown阶段执行drop_all时,第一个测试跑完后会直接删除所有表。而第二个测试启动时,虽然fixture会重新执行create_all和添加fake_users,但如果数据库连接池存在残留连接,或者drop_all的异步操作没完全收尾,可能导致create_all或fake_users插入逻辑静默失败——结果就是第二个测试运行时,数据库里根本没有预设的用户,自然能“成功创建”,不符合你的测试预期。事务隔离机制被打破
如果你的测试或fixture原本用了事务回滚来做数据隔离(比如每个测试包裹在事务里,结束后回滚),drop_all属于直接操作数据库元数据的DDL语句,它会强制提交事务并修改表结构,完全绕过了事务隔离的逻辑。第一个测试的drop_all删掉表后,第二个测试的事务环境里可能触发了自动建表,但预设的fake_users却没被正确插入,导致测试异常。连接状态异常导致数据插入失败
drop_all执行后,数据库连接的状态可能变得异常(比如连接仍然指向已被删除的表对象)。此时第二个测试中插入fake_users的代码可能没有抛出异常,但实际上数据根本没写入数据库,最终测试时目标用户不存在,创建操作就“成功”了。
解决办法
- 用事务回滚替代
drop_all做测试隔离:
把fixture的清理逻辑从drop_all改成事务回滚。比如在fixture启动时开启一个数据库事务,测试结束后直接回滚这个事务——这样既保证每个测试的数据互不干扰,又不会破坏数据库表结构,是测试数据库场景的标准做法。 - 调整
drop_all的执行时机:
如果一定要用create_all/drop_all,只在fixture的setup阶段执行drop_all+create_all,teardown阶段不要再执行drop_all。毕竟function级fixture每个测试都会重新初始化,没必要在每个测试结束后删表。 - 验证fixture的数据初始化结果:
在fixture里添加断言,确认fake_users确实被成功插入数据库后,再执行测试。比如插入后查询数据库,验证用户数量或特定用户存在,避免初始化失败却没被发现。
内容的提问来源于stack exchange,提问作者Seba Rossi

