> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 用 Pest PHP 开始 Laravel 测试

> 以资深工程师视角介绍从 Laravel 11 起成为默认的 Pest PHP。从与 PHPUnit 的差异、迁移成本,到 `arch()` 测试的用法,面向实际项目落地地进行讲解。

## 为什么 Pest 成为 Laravel 的默认

从 Laravel 11 开始,用 `laravel new` 创建项目时会默认选择 **Pest** 作为测试框架。PHPUnit 长期以来是 PHP 事实上的标准测试工具,而 Pest 在 PHPUnit 之上提供了更简洁、更易读的测试写法。

<Info>
  Pest 运行在 PHPUnit 之上,现有的 PHPUnit 测试可以直接运行,可以按阶段迁移。
</Info>

官方选择 Pest,背后的目标是减少写测试时的摩擦。省掉“定义类、写方法”这一套仪式化步骤,让你把注意力放在“想验证什么”本身,从而更容易养成写测试的习惯。

***

## 与 PHPUnit 的主要差异

### 测试写法

最直观的差异就是测试的写法。

<Tabs>
  <Tab title="Pest">
    ```php theme={null}
    test('用户能够登录', function () {
        $user = User::factory()->create();

        $response = $this->post('/login', [
            'email' => $user->email,
            'password' => 'password',
        ]);

        $response->assertRedirect('/dashboard');
    });
    ```
  </Tab>

  <Tab title="PHPUnit">
    ```php theme={null}
    class AuthTest extends TestCase
    {
        public function test_user_can_login(): void
        {
            $user = User::factory()->create();

            $response = $this->post('/login', [
                'email' => $user->email,
                'password' => 'password',
            ]);

            $response->assertRedirect('/dashboard');
        }
    }
    ```
  </Tab>
</Tabs>

Pest 的 `test()` 函数接收一个闭包。不需要类和方法定义,从第一行就能看清测试的意图。`it()` 也一样。用英语写时 `it('can login', ...)` 也能作为自然的英语句读。

### `expect()` API

Pest 最鲜明的特点是基于 `expect()` 的链式断言。

```php theme={null}
test('文章列表 API 以 JSON 返回', function () {
    $posts = Post::factory(3)->create();

    $response = $this->getJson('/api/posts');

    expect($response->status())->toBe(200);
    expect($response->json('data'))->toHaveCount(3);
    expect($response->json('data.0.title'))->not->toBeEmpty();
});
```

`expect($value)->toBe()`、`->toBeNull()`、`->toContain()`、`->toHaveCount()` 等断言像英文一样自然。也可以用方法链把多条断言串到一起。

```php theme={null}
expect($user)
    ->name->toBe('张三')
    ->email->toBe('tanaka@example.com')
    ->is_admin->toBeFalse();
```

### setup 与 teardown

PHPUnit 里的 `setUp()` 对应 Pest 的 `beforeEach()`,`tearDown()` 对应 `afterEach()`。

```php theme={null}
beforeEach(function () {
    $this->user = User::factory()->create();
    $this->actingAs($this->user);
});

test('已登录用户能访问个人主页', function () {
    $response = $this->get('/profile');
    $response->assertOk();
});

test('已登录用户能更新个人主页', function () {
    $response = $this->patch('/profile', ['name' => '新名字']);
    $response->assertRedirect('/profile');
});
```

### 数据集 —— 表格驱动测试

想用多组数据跑同一段测试逻辑时,用 `dataset`。

```php theme={null}
test('无效的邮箱会得到校验错误', function (string $email) {
    $response = $this->postJson('/api/users', [
        'name' => '张三',
        'email' => $email,
    ]);

    $response->assertUnprocessable();
})->with([
    '空邮箱' => [''],
    '格式错误' => ['notanemail'],
    '双 @' => ['a@@example.com'],
]);
```

每一组的标签(如 `'空邮箱'`)会附在测试名上,一眼就能看出是哪个模式失败了。

***

## `arch()` 测试 —— 架构自动校验

Pest 的 `arch()` 用来验证代码库的结构。可以自动检查“控制器有没有直接依赖模型?”、“模型是不是继承了 Eloquent?”这类架构规则。

```php theme={null}
test('模型继承自 Eloquent', function () {
    arch()->expect('App\Models')
        ->toExtend(Illuminate\Database\Eloquent\Model::class);
});

test('控制器以 final 定义', function () {
    arch()->expect('App\Http\Controllers')
        ->toBeFinal();
});

test('服务类不依赖控制器', function () {
    arch()->expect('App\Services')
        ->not->toUse('App\Http\Controllers');
});
```

<Tip>
  `arch()` 测试通过对源代码做静态分析来执行,不会真的发 HTTP 请求或访问数据库,速度非常快。
</Tip>

Pest 也提供了常用的架构规则预设。

```php theme={null}
test('遵循 Laravel 的架构规则', function () {
    arch()->preset()->laravel();
});
```

`laravel()` 预设集中了模型命名、控制器继承、中间件结构等符合 Laravel 惯例的规则。

***

## Laravel 项目中的实践

### 创建测试

```shell theme={null}
php artisan make:test UserTest
```

Laravel 11 及以后按默认配置执行时,会生成 Pest 风格的测试文件。

```php theme={null}
<?php

test('example', function () {
    expect(true)->toBeTrue();
});
```

### 直接使用 Laravel 的测试辅助器

Pest 继承 Laravel 的 `TestCase`,所以 `actingAs()`、`assertDatabaseHas()`、HTTP 测试辅助器等所有 Laravel 测试辅助器都能直接用。

```php theme={null}
use App\Models\Post;
use App\Models\User;

test('用户能删除自己的文章', function () {
    $user = User::factory()->create();
    $post = Post::factory()->for($user)->create();

    $response = $this
        ->actingAs($user)
        ->delete("/posts/{$post->id}");

    $response->assertRedirect('/posts');
    $this->assertModelMissing($post);
});

test('不能删除他人的文章', function () {
    $user = User::factory()->create();
    $otherUser = User::factory()->create();
    $post = Post::factory()->for($otherUser)->create();

    $this
        ->actingAs($user)
        ->delete("/posts/{$post->id}")
        ->assertForbidden();

    $this->assertModelExists($post);
});
```

### 工厂与数据库事务

`RefreshDatabase` 和 `DatabaseTransactions` trait 也可以通过 `uses()` 轻松应用。写在文件开头就作用于整个文件。

```php theme={null}
uses(Tests\TestCase::class, Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
```

在 `tests/Pest.php` 里设置一次,所有 Feature 测试都会自动启用 `RefreshDatabase`。个别测试文件里也可以覆盖。

***

## 与现存 PHPUnit 测试共存

Pest 和 PHPUnit 可以在同一个项目里共存。既有的 PHPUnit 测试不用改,新的测试直接用 Pest 写就好。

```shell theme={null}
./vendor/bin/pest       # 用 Pest 跑所有测试(也包括 PHPUnit 的)
./vendor/bin/pest --filter="用户"  # 按测试名过滤
php artisan test        # 用 Artisan 命令也走 Pest
```

<Info>
  Laravel 11 及以后,如果安装了 Pest,`php artisan test` 会自动用 Pest 来执行。
</Info>

### 迁移建议

1. 先在 `tests/Pest.php` 里配置好 `uses()`
2. 新增的测试用 Pest 写法
3. 既有的 PHPUnit 测试边确认行为边慢慢迁移

不需要急着把所有测试都改写。Pest 的语法比 PHPUnit 学习成本低,团队接受起来也比较顺。

***

## 总结

| 对比项   | PHPUnit            | Pest                 |
| ----- | ------------------ | -------------------- |
| 测试定义  | 类 + 方法             | `test()` / `it()` 函数 |
| 断言    | `$this->assert*()` | `expect()->to*()`    |
| setup | `setUp()`          | `beforeEach()`       |
| 数据驱动  | `@dataProvider`    | `->with()`           |
| 架构检查  | 无                  | `arch()`             |
| 执行速度  | 标准                 | 相当(基于 PHPUnit 运行)    |

Pest 不是 PHPUnit 的替代品,而是包裹在 PHPUnit 之上的上层框架。它与 Laravel 集成很深,启动套件生成时都默认选中它。对既有项目引入成本低,从新测试开始试用就能享受红利。

写测试的量上去后,bug 会更早被发现,重构的心理门槛也会降低。Pest 是一款专门用来降低“写测试本身的摩擦”的工具。

<Card title="Pest 官方文档" icon="book-open" href="https://pestphp.com/docs">
  Pest 的完整功能(数据集、覆盖率、并行执行等)请查看官方文档。
</Card>


## Related topics

- [使用 Pest 进行进阶测试](/zh/advanced/testing-pest.md)
- [测试入门](/zh/testing.md)
- [HTTP 测试](/zh/http-tests.md)
- [用 WSL 安装 PHP、Composer 和 Node.js(Windows)](/zh/blog/windows-setup.md)
- [用 Laravel 构建 MCP 服务器](/zh/advanced/mcp-server.md)
