测试指南

最后更新: 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)。

相关文档