teamones / testing
PHPUnit 测试基础设施,适配 teamones framework(Workerman 常驻 + ThinkPHP 3.2 派生)
1.0.0
2026-10-22 15:16 UTC
Requires
- php: >=7.4
- phpunit/phpunit: ^9.5
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
适配 teamones framework(Workerman 常驻 + ThinkPHP 3.2 派生) 的 PHPUnit 测试基础设施。
解决三个问题:
- 框架环境引导:自动定位项目根目录、加载 autoload、定义
APP_PATH/CONF_PATH/DS等常量,测试里可以直接用框架类; - 不污染运行时:只做最小初始化,不启动 Workerman、不连数据库;
- 安全回归断言:
assertBlocked()/assertNoSqlInjection()等,专门表达“非法输入应被拦截”。
环境要求
- PHP >= 7.4
- PHPUnit ^9.5(PHP 10 需要 PHP 8.1+,本项目不适用)
安装(项目内 path 仓库)
在业务项目的 composer.json 中增加:
{
"repositories": [
{ "type": "path", "url": "packages/teamones-testing" }
],
"require-dev": {
"teamones/testing": "*"
}
}
然后:
composer update teamones/testing
若包发布到私有 Composer 仓库,直接 composer require --dev teamones/testing 即可。
基本用法
<?php namespace tests; use Teamones\Testing\TestCase; class ParseOrderTest extends TestCase { public function testNormalField(): void { $ref = new \ReflectionClass(\think\db\driver\Mysql::class); $db = $ref->newInstanceWithoutConstructor(); $m = $ref->getMethod('parseOrder'); $m->setAccessible(true); $this->assertSqlFragment( ' ORDER BY `id` DESC', fn() => $m->invoke($db, 'id DESC', false) ); } public function testInjectionBlocked(): void { $ref = new \ReflectionClass(\think\db\driver\Mysql::class); $db = $ref->newInstanceWithoutConstructor(); $m = $ref->getMethod('parseOrder'); $m->setAccessible(true); $this->assertBlocked(fn() => $m->invoke($db, 'id) OR 1=1 ASC', false)); } }
TestCase 已自动完成 Bootstrap::boot(),无需手动 require。
常用能力
| 方法 | 用途 |
|---|---|
Bootstrap::boot($root = null) |
引导框架环境(幂等) |
Bootstrap::detectRoot() |
向上查找 vendor/autoload.php |
$this->assertBlocked($fn) |
断言闭包被拦截(抛异常) |
$this->assertSqlFragment($expect, $fn) |
断言生成的 SQL 片段 |
$this->assertNoSqlInjection($sql) |
断言片段不含 union/sleep/outfile 等 |
$this->requireEnv('DB_HOST') |
缺少环境变量时跳过测试 |
$this->assertThrows($fn) |
断言抛异常并返回异常对象 |
在容器内运行
项目使用 Docker 部署时,直接在容器里跑,能保证 PHP 版本与生产一致:
# 包自身测试 docker exec -it teamones-saas sh -c "cd /app/packages/teamones-testing && composer install && ./vendor/bin/phpunit" # 业务项目测试 docker exec -it teamones-saas sh -c "cd /app && ./vendor/bin/phpunit --testsuite=Application"
真实服务 HTTP 测试
单元测试覆盖不到路由、中间件、鉴权等链路。参考 webman-tech/testing 的 laravel 风格 API,本包提供 InteractsWithHttp,但不在测试进程内拉起服务——teamones 服务由 Docker/常驻进程提供,测试只负责发请求与断言。
差异对比:
| 项 | webman-tech/testing | 本包 |
|---|---|---|
| PHP | ^8.2 | >= 7.4 |
| PHPUnit | ^10.5 | ^9.5 |
| HTTP 客户端 | Guzzle(PSR-18) | curl(无额外依赖) |
| 服务编排 | 测试内启动/停止真实 webman 进程 | 复用已运行服务(TEST_BASE_URL) |
| 鉴权 | withToken |
withToken + withUserInfo()(生成 x-userinfo) |
用法
use Teamones\Testing\TestCase; class HttpSmokeTest extends TestCase { protected function setUp(): void { $this->skipIfServiceUnavailable(); // 服务没起就跳过 } public function testRejected(): void { $this->get('/?s=/index/%5Cthink%5Capp/invokefunction') ->assertCode(-404); } public function testWithAuth(): void { $this->withUserInfo(['id' => 1, 'tenant_id' => 1, 'name' => 'tester']) ->postJson('/project/get_project_list', [ 'param' => ['filter' => ['id' => 1], 'fields' => 'id', 'order' => 'id DESC', 'page' => [1, 1]], ]) ->assertStatus(200); } }
可用方法:
| 方法 | 说明 |
|---|---|
withHeader/withHeaders/withToken/withUserInfo |
请求头与鉴权 |
get/getJson/post/postJson/putJson/deleteJson |
请求 |
assertStatus/assertCode/assertJsonPath/assertSee/assertHeader |
断言(assertCode 对应本框架的 code 业务码) |
waitFor($probe, $timeout) |
轮询等待副作用(借鉴 webmanWaitFor) |
skipIfServiceUnavailable() |
服务不可用时跳过 |
服务地址取环境变量 TEST_BASE_URL,默认 http://127.0.0.1:8080。
测试身份配置(X-Userinfo)
本框架的鉴权由 RequestCheck 读取 x-userinfo 头(base64 JSON)。为避免每个用例手写用户信息,包提供 UserInfoProvider:
$this->asUser('default')->postJson('/project/get_project_list', [...]); $this->asUser('admin')->getJson('/...'); $this->withUserInfo(['id' => 1, 'tenant_id' => 1]); // 直接指定,仍可用
配置位置(按优先级)
<项目根>/tests/config/users.php<项目根>/.teones/tests/users.php(已被.gitignore忽略,适合放本地真实账号)- 环境变量覆盖:
TEST_XUSERINFO_ID、TEST_XUSERINFO_TENANT_ID、TEST_XUSERINFO_PHONE、TEST_XUSERINFO_EMAIL、TEST_XUSERINFO_NAME
配置文件格式见 tests/config/users.php.example:
return [ 'default' => ['id' => 1, 'tenant_id' => 1, 'name' => 'tester'], 'users' => [ 'admin' => ['id' => 2, 'tenant_id' => 1, 'name' => 'admin'], ], ];
注意
id与tenant_id必填;缺失时asUser()会抛异常并给出配置路径提示,不会静默使用假身份;- 服务端会按
id同步用户(可能写库),请填测试环境的真实账号,不要编造; - CI 建议用环境变量注入,避免把账号写进仓库;
- 配置文件含手机号/邮箱等个人信息,不要提交到版本库。
注意事项
- 不要依赖 HTTP 请求对象:常驻框架的
request()依赖 Workerman 连接,单元测试里不可用;请把待测逻辑抽成纯方法或直接用反射调用; - 不要连业务库:涉及 DB 的测试请单独配置测试库,并用
requireEnv()做保护; - 全局状态:
C()配置、静态属性在同进程内会残留,测试间注意隔离(必要时在每个用例里重置); phpunit.xml中的convertErrorsToExceptions等旧属性在新版 PHPUnit 中会告警,按需升级。