DatabaseTransactions 不干净,因它依赖数据库驱动且仅限单连接,无法保护跨上下文操作;RefreshDatabase 才是官方推荐的真清洁方案,通过迁移重建确保状态可控可复现。

别用 DatabaseTransactions 试图“保持数据库清洁”——它根本做不到你想要的效果,反而会掩盖事务边界问题、导致断言失效或测试间污染。
为什么 DatabaseTransactions 在多数场景下不干净
这个 trait 的行为高度依赖底层数据库驱动和连接配置:MySQL 默认开启 autocommit,DB::beginTransaction() 若未显式调用或被框架/ORM 隐式提交(比如某些 Eloquent 批量操作、触发器、存储过程),事务就提前结束了;SQLite 内存模式下看似可靠,但一换到 MySQL 就暴露问题;更关键的是,它只对当前连接生效,队列任务、HTTP 子请求、Redis Pub/Sub 回调等跨上下文操作完全不受保护。
- 常见错误现象:
testUserCreatedThenDeleted断言用户不存在,却偶尔失败——因为 delete 操作实际已提交,rollback 没拦住 - 使用场景受限:仅适用于纯同步、单连接、无外部副作用的简单 CRUD 测试
- 性能影响小但可靠性差:比
RefreshDatabase快,但换来的是不可预测的残留数据
RefreshDatabase 才是 Laravel 官方推荐的“真清洁”方案
它不依赖事务,而是每次测试前用迁移重建表结构 + 种子填充,结束后直接删库或回滚迁移。虽然比事务慢,但状态绝对可控、可复现。
- 默认启用事务回滚(仅限支持的驱动),但一旦检测到不兼容环境(如
--without-tty或非内存 SQLite),自动 fallback 到迁移重建 - 确保每个测试从完全一致的起点开始,避免“上一个测试漏删某条记录,导致当前测试断言失败”这类幽灵问题
- 若想加速,可配合
artisan migrate:fresh --seed预生成快照,或为不同测试组指定最小粒度的迁移文件
需要保留测试后数据?别 hack DatabaseTransactions,改用断言验证
所谓“想看数据库里最终长什么样”,本质是把验证逻辑外包给了人工。这在 CI、团队协作、长期维护中必然崩坏。
- 正确做法:用
$this->assertDatabaseMissing()、$this->assertDatabaseHas()或User::where(...)->count()等直接断言状态 - 调试时临时查看:可在测试末尾加
dump(User::pluck('id', 'email')->toArray());,而非依赖 phpMyAdmin - 如果业务逻辑复杂到无法用断言覆盖(比如多步异步更新+缓存穿透),说明测试设计本身需重构——拆成可验证的原子步骤
真正难的不是让数据库“看起来干净”,而是让每个测试的输入、行为、输出都清晰可描述。事务回滚只是表象,契约明确才是清洁的根源。


















