最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
TP5.1 如何编写单元测试:PHPUnit 模拟数据库与 Mock 对象 测试
时间:2026-07-28 07:05:47 编辑:袖梨 来源:一聚教程网
ThinkPHP5.1单元测试需解耦静态调用,将Db/Model操作封装为可注入的Repository/Service类,用Mockery部分mock或PHPUnit mock自定义DAO层,禁用Db::/Model::静态调用,避免直接mock Db类,并慎用SQLite内存库。
thinkphp5.1 项目做单元测试,不能直接用 phpunit 跑原生 TP 代码——因为 TP5.1 的自动加载、数据库连接、配置加载等机制和标准 PHPUnit 测试环境不兼容。必须手动桥接或重构依赖注入方式,否则 Db::table()、Model::get() 这类静态调用会直接连真实库,测试不可控、不可重复。
TP5.1 单元测试前必须解耦数据库静态调用
TP5.1 默认大量使用 Db 和 Model 静态方法,这导致无法被 PHPUnit Mock。你不能对 Db::name('user')->where(...)->select() 做 mock,因为它是静态调用,PHPUnit 的 $this->createMock() 只能 mock 实例对象。
- 把所有数据库操作封装进 Repository 或 Service 类,并通过构造函数/Setter 注入依赖(例如传入
Db实例或自定义的QueryInterface) - 禁用
Db::和Model::静态调用,改用$this->db或$this->userModel这样的实例属性 - 确保业务逻辑类不 new 任何 TP 核心类,全部走依赖注入(哪怕只是传一个
PDO或Connection对象)
用 createMock() 替换 DAO 层,而非模拟 Db
别试图 mock Db 类本身——它不是普通类,很多方法是静态的、final 的,mock 失败率高。正确做法是 mock 你自己的数据访问层。
- 假设你写了
UserRepository,它内部用$this->db执行查询;测试时用$mockRepo = $this->createMock(UserRepository::class) - 用
expects()->method('findByName')->willReturn(['id' => 1, 'name' => 'Alice'])控制返回值 - 若需验证调用参数,加
with($this->equalTo('admin')),避免因字符串空格或类型隐式转换导致断言失败 - 注意:mock 对象默认不继承原类方法签名,如果原方法有 type hint(如
array $where),mock 返回值也得匹配,否则运行时报 Fatal error
TP5.1 中 SQLite 内存库不推荐用于单元测试
sqlite::memory: 看起来干净,但在 TP5.1 里实际踩坑多:配置复杂、事务行为和 MySQL 不一致、外键默认关闭、Db 类对内存库的适配不完整(比如某些聚合函数报错)。
立即学习“PHP免费学习笔记(深入)”;
- TP5.1 的
database.php不支持直接设'dsn' => 'sqlite::memory:',需额外 patchthinkdbconnectorSqlite - 即使跑通,
Db::table('user')->insert()后再select可能查不到数据——因为 TP5.1 的连接复用机制在内存库下容易丢上下文 - 真正需要 SQL 行为验证(比如 join、group by)时,才考虑用
RefreshDatabase+ 真实 SQLite 文件(非内存),并配合setUp()清库
Mockery 比 PHPUnit 原生 Mock 更适合 TP5.1 场景
TP5.1 的类常含复杂构造逻辑(如自动读配置、初始化连接),PHPUnit 的 createMock() 创建空对象后,很多方法调用直接 fatal。Mockery 支持更灵活的“伪造”(fake)和部分 mock。
- 安装:
composer require --dev mockery/mockery - 写法示例:
$mockDb = Mockery::mock('thinkdbQuery')->makePartial(),然后shouldReceive('select')->andReturn([['id'=>1]]) - 关键点:用
makePartial()保留原方法骨架,只 mock 想替换的部分;否则select()调用时可能因未 mockparseWhere()等内部方法而崩溃 - 测试结束必须调用
Mockery::close()(通常放tearDown()),否则后续测试可能受残留 mock 影响
TP5.1 的单元测试难点不在工具链,而在 如何让框架代码“可测” ——静态调用、全局状态、隐式单例这三座山,不推倒就永远卡在“跑不起来”或“测了等于没测”。mock 对象不是银弹,它只对松耦合代码生效。