- 测试环境配置
- PHPUnit 配置
- 运行测试
- 运行所有测试
- 快速测试(无覆盖率)
- 运行特定测试文件
- 运行特定测试方法
- 编写测试
- 基本测试类
- 使用 TestCase 基类
- HTTP 测试
- HTTP 测试 Trait
- HTTP 方法
- 请求头
- 响应断言
- 数据库测试
- 数据库测试 Trait
- 数据库事务
- 使用测试数据库
- API 契约测试
- 什么是 API 契约测试?
- 编写 API 契约测试
- 运行 API 契约测试
- 测试覆盖率
- 生成覆盖率报告
- 生成 HTML 覆盖率报告
- 报告位置:coverage/html/index.html
- 检查覆盖率
- 检查覆盖率是否达标
- 分析覆盖率
- 覆盖率配置
- 测试辅助方法
- 创建测试数据
- Mock 对象
- 最佳实践
- 1. 测试命名
- 2. 测试组织
- 3. 测试隔离
- 4. 使用数据提供者
- 5. 测试异常
- 常见问题
- Q: 如何运行特定测试?
- Q: 如何跳过某些测试?
- Q: 测试数据库如何配置?
- Q: 如何模拟外部服务?
- 相关文档
测试指南
最后更新: 2026-01-27 11:02:47测试指南
Unicode Framework 提供了完整的测试支持,使用 PHPUnit 作为测试框架。
测试环境配置
PHPUnit 配置
框架已配置好 PHPUnit,配置文件为 phpunit.xml:
<phpunit bootstrap="tests/bootstrap.php">
<testsuites>
<testsuite name="Framework Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
运行测试
<h1 id="运行所有测试">运行所有测试</h1>
composer test
<h1 id="快速测试无覆盖率">快速测试(无覆盖率)</h1>
composer test:quick
<h1 id="运行特定测试文件">运行特定测试文件</h1>
vendor/bin/phpunit tests/App/AppTest.php
<h1 id="运行特定测试方法">运行特定测试方法</h1>
vendor/bin/phpunit tests/App/AppTest.php --filter testMethodName
编写测试
基本测试类
<?php
namespace Tests\App;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
public function testBasicAssertion(): void
{
$this->assertTrue(true);
$this->assertEquals(2, 1 + 1);
}
}
使用 TestCase 基类
框架提供了 Tests\TestCase 基类,包含常用功能:
<?php
namespace Tests\App;
use Tests\TestCase;
class UserTest extends TestCase
{
public function testUserCreation(): void
{
// 测试代码
}
}
HTTP 测试
HTTP 测试 Trait
使用 HttpTestTrait 进行 HTTP 测试:
<?php
namespace Tests\App;
use PHPUnit\Framework\TestCase;
use Unicode\Framework\Testing\HttpTestTrait;
class UserControllerTest extends TestCase
{
use HttpTestTrait;
public function testUserIndex(): void
{
$response = $this->get('/users');
$response->assertStatus(200);
$response->assertJson(['users' => []]);
}
public function testUserCreate(): void
{
$response = $this->post('/users', [
'name' => 'John Doe',
'email' => 'john@example.com',
]);
$response->assertStatus(201);
$response->assertJsonStructure([
'user' => ['id', 'name', 'email'],
]);
}
}
HTTP 方法
// GET 请求
$response = $this->get('/users');
$response = $this->get('/users', ['status' => 'active']);
// POST 请求
$response = $this->post('/users', ['name' => 'John']);
// PUT 请求
$response = $this->put('/users/1', ['name' => 'Jane']);
// DELETE 请求
$response = $this->delete('/users/1');
// 自定义请求
$response = $this->call('PATCH', '/users/1', ['name' => 'Jane']);
请求头
$response = $this->withHeaders([
'Authorization' => 'Bearer token',
'Content-Type' => 'application/json',
])->get('/users');
响应断言
// 状态码
$response->assertStatus(200);
$response->assertStatus(404);
// JSON 响应
$response->assertJson(['status' => 'ok']);
$response->assertJsonStructure(['user' => ['id', 'name']]);
// 响应头
$response->assertHeader('Content-Type', 'application/json');
// 响应内容
$response->assertSee('Hello');
$response->assertDontSee('Error');
数据库测试
数据库测试 Trait
使用 DatabaseTestTrait 进行数据库测试:
<?php
namespace Tests\App;
use PHPUnit\Framework\TestCase;
use Unicode\Framework\Testing\DatabaseTestTrait;
class UserModelTest extends TestCase
{
use DatabaseTestTrait;
public function testUserCreation(): void
{
$user = \App\Index\Model\User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
]);
$this->assertNotNull($user->id);
$this->assertEquals('John Doe', $user->name);
}
public function testUserQuery(): void
{
\App\Index\Model\User::create([
'name' => 'John',
'email' => 'john@example.com',
]);
$user = \App\Index\Model\User::where('email', 'john@example.com')->first();
$this->assertNotNull($user);
$this->assertEquals('John', $user->name);
}
}
数据库事务
测试会自动在事务中运行,测试结束后自动回滚:
public function testDatabaseOperation(): void
{
// 这些操作会在事务中执行
db('users')->insert(['name' => 'Test']);
// 测试结束后自动回滚,不影响其他测试
}
使用测试数据库
在 phpunit.xml 中配置测试数据库:
<php>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
API 契约测试
什么是 API 契约测试?
API 契约测试确保 API 的签名和行为保持一致,防止意外的破坏性变更。
编写 API 契约测试
<?php
namespace Tests\ApiContract;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use ReflectionMethod;
class UserControllerContractTest extends TestCase
{
public function testIndexMethodSignature(): void
{
$reflection = new ReflectionClass(\App\Index\Controller\UserController::class);
$method = $reflection->getMethod('index');
// 检查参数
$params = $method->getParameters();
$this->assertCount(1, $params);
$this->assertEquals('request', $params[0]->getName());
// 检查返回类型
$returnType = $method->getReturnType();
$this->assertEquals('array', $returnType->getName());
}
}
运行 API 契约测试
composer test:api-contract
测试覆盖率
生成覆盖率报告
<h1 id="生成-html-覆盖率报告">生成 HTML 覆盖率报告</h1>
composer test:coverage
<h1 id="报告位置coveragehtmlindexhtml">报告位置:coverage/html/index.html</h1>
检查覆盖率
<h1 id="检查覆盖率是否达标">检查覆盖率是否达标</h1>
composer coverage:check
<h1 id="分析覆盖率">分析覆盖率</h1>
composer coverage:analyze
覆盖率配置
在 phpunit.xml 中配置:
<source>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>vendor</directory>
<directory>tests</directory>
</exclude>
</source>
测试辅助方法
创建测试数据
use function Tests\createUser;
$user = createUser([
'name' => 'John',
'email' => 'john@example.com',
]);
Mock 对象
use PHPUnit\Framework\TestCase;
class ServiceTest extends TestCase
{
public function testService(): void
{
$mock = $this->createMock(SomeService::class);
$mock->method('doSomething')
->willReturn('result');
$result = $mock->doSomething();
$this->assertEquals('result', $result);
}
}
最佳实践
1. 测试命名
// ✅ 正确:描述性命名
public function testUserCanBeCreated(): void { }
public function testUserEmailMustBeUnique(): void { }
// ❌ 错误:不清晰的命名
public function test1(): void { }
public function testUser(): void { }
2. 测试组织
// 按功能组织测试
class UserControllerTest extends TestCase
{
// 创建相关
public function testUserCanBeCreated(): void { }
public function testUserCreationRequiresEmail(): void { }
// 查询相关
public function testUserCanBeRetrieved(): void { }
public function testUserListCanBeFiltered(): void { }
// 更新相关
public function testUserCanBeUpdated(): void { }
// 删除相关
public function testUserCanBeDeleted(): void { }
}
3. 测试隔离
// ✅ 正确:每个测试独立
public function testUserCreation(): void
{
$user = User::create(['name' => 'John']);
$this->assertNotNull($user->id);
}
public function testUserQuery(): void
{
// 不依赖上一个测试
$user = User::create(['name' => 'Jane']);
$this->assertNotNull($user);
}
// ❌ 错误:测试之间依赖
private static $userId;
public function testUserCreation(): void
{
self::$userId = User::create(['name' => 'John'])->id;
}
public function testUserQuery(): void
{
// 依赖上一个测试
$user = User::find(self::$userId);
}
4. 使用数据提供者
/
* @dataProvider emailProvider
*/
public function testEmailValidation(string $email, bool $expected): void
{
$result = $this->validateEmail($email);
$this->assertEquals($expected, $result);
}
public function emailProvider(): array
{
return [
['valid@example.com', true],
['invalid-email', false],
['another@test.com', true],
];
}
5. 测试异常
public function testExceptionThrown(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid input');
// 触发异常
throw new \InvalidArgumentException('Invalid input');
}
常见问题
Q: 如何运行特定测试?
A: 使用 --filter 选项:
vendor/bin/phpunit --filter testUserCreation
Q: 如何跳过某些测试?
A: 使用 @group 注解:
/
* @group slow
*/
public function testSlowOperation(): void
{
// 慢速测试
}
// 运行时排除
vendor/bin/phpunit --exclude-group slow
Q: 测试数据库如何配置?
A: 在 phpunit.xml 或 .env.testing 中配置测试数据库。
Q: 如何模拟外部服务?
A: 使用 Mock 对象或测试替身(Test Doubles)。