phpunit仍是php自动化测试的事实标准,2026年未被取代;关键在于正确实践:用sqlite内存库替代pdo mock以真实验证sql与约束,teardown中显式回滚事务,用数组工厂函数管理fixture,guzzle测试需覆盖非2xx响应并禁用http_errors。

PHPUnit 仍是 PHP 自动化测试的事实标准,2026 年没有新框架取代它;关键不是换工具,而是用对方式——尤其在数据库、API 和并发场景下,错用连接、忽略事务隔离、硬编码 fixture,会让测试变脆且不可靠。
为什么 SQLite 内存库比 mock PDO 更值得信赖
mock PDO 或 PDOStatement 只能验证“你写了什么 SQL”,但无法验证“SQL 是否真能执行”“约束是否生效”“JOIN 结果是否符合预期”。而 sqlite::memory: 支持外键、事务、UNIQUE 约束、触发器,行为与 MySQL/PostgreSQL 在绝大多数场景下一致。
- 每次
setUp()创建新连接,天然隔离:不会因上一个 test 残留数据导致失败 - 建表语句可复用生产 migration 的 SQL 片段,避免 schema 不一致
- 故意写错字段名或违反 NOT NULL,
PDOException真实抛出——mock 做不到这点 - 不支持 FULLTEXT 或 JSON 函数?那就别在测试里用;这反而是提醒你:那些功能不该进核心路径
测试数据库操作时,tearDown() 必须显式回滚事务
默认 PDO 是 auto-commit 模式。如果你在 test 中手动调用了 $pdo->beginTransaction(),但没在 tearDown() 里 $pdo->rollback(),下一个 test 就会继承未提交的脏状态,导致随机失败。
- 不要依赖“脚本结束自动释放连接”——连接释放 ≠ 事务回滚
- 哪怕只读查询也建议包一层事务:
$pdo->beginTransaction()+$pdo->rollback(),确保无隐式写入(如 SELECT FOR UPDATE)污染 - 使用
TRUNCATE TABLE清空表前,先确认引擎支持(SQLite 支持,但会重置自增计数器;MySQL InnoDB 下需注意外键约束) - 若用 Docker 启 MySQL 实例,
tearDown()应执行DROP DATABASE IF EXISTS test_XXXX,而非只清空表
用数组定义 fixture,别手写 INSERT SQL 字符串
写 "INSERT INTO users (name, email) VALUES ('test', 't@e.com')" 是反模式:字段增减、类型变更、NULL 处理全得改字符串,极易漏。
- 定义
createUser(['name' => 'Alice', 'email' => 'a@b.com']),返回关联数组,DAO 层统一用execute($data)绑定参数 - 工厂函数内可自动补
created_at、updated_at时间戳,避免测试里到处date('Y-m-d H:i:s') - 主从关系用嵌套结构表达:
createOrder(['user' => createUser(...), 'items' => [...]]),封装层自动处理插入顺序和外键赋值 - 测试中修改字段只需改数组键名,不用翻找所有 SQL 字符串
Guzzle 测试 API 时,必须捕获非 2xx 响应体
很多测试只断言 $response->getStatusCode() === 200,却忽略 400/422/500 响应里的错误详情。这导致接口改了错误格式,测试仍绿,但线上报错没人知道。
- 用
$response->getBody()->getContents()获取原始响应体,再json_decode(..., true)断言错误码和 message 字段 - 对必填字段缺失、类型错误等常见 422 场景,单独写 test 方法,不合并到“成功流程”里
- 禁用
http_errors => false全局配置——它会让 Guzzle 把 4xx 当正常响应,掩盖真实问题 - CI 中跑 API 测试前,确保目标服务已就绪;可用
curl -f http://api:8000/health做前置检查,避免超时失败误判
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











