php中sqlite3内存数据库通过传入':memory:'创建独立、自动销毁的进程内实例,语法与磁盘库一致,但不支持wal模式、attach及跨进程共享。

用 SQLite 内存数据库替代真实 DB
PHPUnit 默认不连接 MySQL 或 PostgreSQL,直接换用 sqlite:memory: 是最轻量、最可控的方式。它每次运行测试都从空库开始,无需清理表或事务回滚——因为整个库生命周期只存在于当前测试进程里。
常见错误现象:测试跑完发现数据还在、不同测试间状态污染、CI 环境连不上 MySQL 服务导致全挂。
- 在
phpunit.xml中配置环境变量:DB_CONNECTION=sqlite和DB_DATABASE=:memory: - Laravel 用户可直接在
.env.testing里写死该配置;ThinkPHP 则需在config/database.php的testing环境下覆盖连接参数 - 确保所有迁移命令(如
php artisan migrate)在测试启动前执行一次,否则表不存在会报SQLSTATE[HY000]: General error: 1 no such table
用 RefreshDatabase 重置每次测试状态
内存数据库虽快,但如果你依赖框架的迁移机制(比如 Laravel 的 migrate:refresh),就得靠 RefreshDatabase trait 来保证每个测试方法前后都是干净状态。
使用场景:需要测试带外键、索引、种子数据的真实建表逻辑,又不想手动写 DROP TABLE。
- 在测试类顶部加
use RefreshDatabase;(Laravel)或use think\testing\RefreshDatabase;(ThinkPHP) - 它会在每个
testXXX()方法执行前清空并重跑迁移,比DatabaseTransactions更彻底——后者只回滚事务,对 DDL 无效 - 注意性能:100 个测试 = 100 次 migrate,若迁移文件多,建议把非核心迁移移到
setUpBeforeClass()里一次性执行
Mock 数据库查询构造器或 ORM 实例
当你要测的是业务逻辑而非 SQL 正确性时,mock DB::table() 或 User::query() 能跳过所有数据库层,速度更快、隔离性更强。
容易踩的坑:mock 过度导致测试失去意义;或 mock 返回结构和真实 ORM 不一致,断言通过但线上出错。
- 用
$mock = $this->createMock(Builder::class);模拟查询构造器,然后链式设置willReturn()返回假数据 - 对 Eloquent 模型,更推荐 mock 静态方法:
User::shouldReceive('where')->andReturnSelf(); User::shouldReceive('get')->andReturn(collect([...]))(需配合 Mockery) - 避免 mock
DB::connection()全局实例——它牵扯太多底层,容易触发ConnectionException或未预期的 lazy 初始化
不推荐直接 mock PDO 或底层驱动
有人试图用 Mockery::mock(PDO::class) 拦截所有 SQL 执行,这在技术上可行,但实际维护成本极高。
为什么这样做很危险:PDO 接口方法极多(prepare、beginTransaction、getAttribute…),漏 mock 任一方法就会导致 BadMethodCallException;且不同数据库驱动行为差异大(SQLite vs MySQL 的 lastInsertId 处理),mock 无法覆盖这些细节。
真正要验证 SQL 本身是否正确?应该用功能测试 + SQLite 文件数据库做 end-to-end 校验,而不是在单元测试里补全一个虚拟 PDO。
复杂点在于:内存数据库和迁移重置看似简单,但一旦项目用了视图、存储过程或 JSON 字段函数,SQLite 就不再兼容——这时候 mock 查询构造器反而是更务实的选择。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











