应使用 Seeder + migrate:refresh 组合而非 Db::name('user')->insert(),因其可重跑、可清理、可验证;需用 replaceInto() 避免主键冲突,单元测试优先用 Model::make()->save() 触发完整生命周期。

Db::name('user')->insert() 是最直接的插入方式,但真正在测试中用它填充数据,容易踩坑、难维护、不可重复执行。别一上来就写 insert(),先想清楚:你要的是「一次性的脏数据」,还是「可重跑、可清理、可验证的测试上下文」?
为什么不能直接用 insert() 填测试数据
直接调用 Db::name('user')->insert($data) 看似简单,但在实际测试或开发环境中会暴露几个硬伤:
- 主键冲突:自增 ID 表反复运行会报
Duplicate entry '1' for key 'PRIMARY',除非你每次手动TRUNCATE - 外键/关联断裂:插入 A 表后没插 B 表,后续查关联数据直接返回空,但错误不明显
- 时间字段失效:比如
create_time是自动写入的 timestamp,但你传了固定值,可能被框架过滤或覆盖 - 无法回滚:没配合迁移(migration),清库只能靠人工或脚本,CI 流水线里极易失败
用 Seeder + migrate:refresh 组合才是正解
ThinkPHP 本身不内置 Seeder,但通过命令行 + 自定义类能实现和 Laravel 类似的填充逻辑。核心是让「结构重建」和「数据填充」解耦又联动:
- 先跑
php think migrate:refresh—— 它会先DROP再CREATE所有已注册的迁移表,确保干净起点 - 再跑
php think seed:run—— 这个命令需你自己实现(放在command/目录下),它按顺序调用各Seeder类的run()方法 - 每个 Seeder 类里,用
Db::name('user')->replaceInto($data)替代insert(),避免主键冲突;或先where()->delete()再插入 - 固定 ID 场景(如权限角色映射),必须用
replaceInto()或显式TRUNCATE,不能依赖insert()的“存在即跳过”逻辑
测试中临时插数据,优先用 Model::make() + save() 而非 Db::insert()
单元测试里若只需构造一条数据用于验证逻辑(比如测一个用户状态变更方法),别碰原生 Db:
- 用
$user = User::make(['name' => 'test', 'status' => 1])创建模型实例,它会走字段过滤、自动时间戳、类型转换等流程 - 再调
$user->save(),确保触发initialize()、事件、验证器等生命周期钩子 - 千万别用
new User()后直接save()——$this->db为 null,报Call to a member function query() on null - 如果只是构造数据不入库(比如测 DTO 转换),用
User::make()就够了;要入库才save()



















