不能直接运行 vendor/bin/phpunit,必须用 php think unit——否则跳过框架环境初始化,导致 Loader、Config、路由及 HTTP 模拟层失效,测试必然报错;ThinkPHP5 的测试依赖 base.php 引导、命名空间注册、配置加载和 InteractsWithPages 等封装能力,这些均由 php think unit 集成提供。

直接用 php think unit 运行,别碰 vendor/bin/phpunit —— 否则会跳过 ThinkPHP 的环境初始化,Loader、Config、路由等全失效,测试必然报错。
为什么不能直接运行 phpunit
ThinkPHP5 的测试不是纯 PHPUnit 环境:它依赖框架引导(base.php)、应用命名空间注册(Loader::addNamespace)、配置加载(Config::load)以及测试专用的 HTTP 模拟层(InteractsWithPages)。这些都在 php think unit 命令里封装好了。
-
vendor/bin/phpunit只读phpunit.xml和bootstrap,但默认bootstrap="vendor/autoload.php"不加载 ThinkPHP 应用上下文 - 手动改
phpunit.xml的bootstrap指向自定义文件?可行,但得同步维护APP_PATH、RUNTIME_PATH、APP_DEBUG等定义,稍有遗漏就Class not found或Undefined index: SERVER_NAME -
think-testing扩展里的TestCase依赖think\Console初始化,绕开php think就失去请求模拟能力(比如$this->visit()、$this->see())
testBasicExample 失败时怎么定位
错误信息里出现 Failed asserting that 'Hello,world' matches PCRE pattern "/ThinkPHP/i",说明页面实际输出和断言不一致。这不是测试写错了,而是被测目标变了。
- 检查
application/index/controller/Index.php的index()方法是否还返回含ThinkPHP的 HTML;如果改成echo 'Hello,world'; die;,断言自然失败 -
$this->visit('/')是走完整 HTTP 生命周期:路由 → 控制器 → 视图渲染 → 输出缓冲捕获。确保没有中间件拦截、模板被覆盖、或output被提前清空 - 不要在控制器里调用
exit或die—— 测试框架靠捕获ob_get_contents()获取响应,提前终止会导致空响应,断言直接报"" does not match - 若需验证 JSON 接口,改用
$this->json('GET', '/api/user')+$this->seeJson(['state' => 1100]),而不是visit+see
自定义测试类必须继承 TestCase
所有测试类必须继承 tests\TestCase(不是 PHPUnit\Framework\TestCase),否则无法使用 visit、see、click 等 ThinkPHP 封装的断言方法。
立即学习“PHP免费学习笔记(深入)”;
- 新建测试文件如
tests/UserControllerTest.php,类名建议用UserControllerTest,文件名后缀为.php(不是Test.php),因为php think unit默认扫描tests/**/*.php - 命名空间可省略,但显式写上更安全:
namespace tests;,避免和app\controller冲突 - 若要测试某个控制器实例方法(非 HTTP 请求),可 new 出来直接调用,但此时应使用原生 PHPUnit 断言(
$this->assertEquals()),且注意该控制器可能依赖Request、Session等对象,需手动 mock 或注入 - 别在测试里写
require_once APP_PATH . '/controller/User.php'——Loader已自动注册命名空间,直接new \app\controller\User()即可
真正容易被忽略的是:每次修改控制器逻辑后,必须同步更新对应测试里的断言预期值。测试不是摆设,它是接口契约的快照 —— 响应结构一变,测试就得立刻红,否则就失去了预警价值。



















