跳转到主要内容

为什么 Pest 成为 Laravel 的默认

从 Laravel 11 开始,用 laravel new 创建项目时会默认选择 Pest 作为测试框架。PHPUnit 长期以来是 PHP 事实上的标准测试工具,而 Pest 在 PHPUnit 之上提供了更简洁、更易读的测试写法。
Pest 运行在 PHPUnit 之上,现有的 PHPUnit 测试可以直接运行,可以按阶段迁移。
官方选择 Pest,背后的目标是减少写测试时的摩擦。省掉“定义类、写方法”这一套仪式化步骤,让你把注意力放在“想验证什么”本身,从而更容易养成写测试的习惯。

与 PHPUnit 的主要差异

测试写法

最直观的差异就是测试的写法。
test('用户能够登录', function () {
    $user = User::factory()->create();

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

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

expect() API

Pest 最鲜明的特点是基于 expect() 的链式断言。
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() 等断言像英文一样自然。也可以用方法链把多条断言串到一起。
expect($user)
    ->name->toBe('张三')
    ->email->toBe('[email protected]')
    ->is_admin->toBeFalse();

setup 与 teardown

PHPUnit 里的 setUp() 对应 Pest 的 beforeEach(),tearDown() 对应 afterEach()
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
test('无效的邮箱会得到校验错误', function (string $email) {
    $response = $this->postJson('/api/users', [
        'name' => '张三',
        'email' => $email,
    ]);

    $response->assertUnprocessable();
})->with([
    '空邮箱' => [''],
    '格式错误' => ['notanemail'],
    '双 @' => ['a@@example.com'],
]);
每一组的标签(如 '空邮箱')会附在测试名上,一眼就能看出是哪个模式失败了。

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

Pest 的 arch() 用来验证代码库的结构。可以自动检查“控制器有没有直接依赖模型?”、“模型是不是继承了 Eloquent?”这类架构规则。
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');
});
arch() 测试通过对源代码做静态分析来执行,不会真的发 HTTP 请求或访问数据库,速度非常快。
Pest 也提供了常用的架构规则预设。
test('遵循 Laravel 的架构规则', function () {
    arch()->preset()->laravel();
});
laravel() 预设集中了模型命名、控制器继承、中间件结构等符合 Laravel 惯例的规则。

Laravel 项目中的实践

创建测试

php artisan make:test UserTest
Laravel 11 及以后按默认配置执行时,会生成 Pest 风格的测试文件。
<?php

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

直接使用 Laravel 的测试辅助器

Pest 继承 Laravel 的 TestCase,所以 actingAs()assertDatabaseHas()、HTTP 测试辅助器等所有 Laravel 测试辅助器都能直接用。
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);
});

工厂与数据库事务

RefreshDatabaseDatabaseTransactions trait 也可以通过 uses() 轻松应用。写在文件开头就作用于整个文件。
uses(Tests\TestCase::class, Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
tests/Pest.php 里设置一次,所有 Feature 测试都会自动启用 RefreshDatabase。个别测试文件里也可以覆盖。

与现存 PHPUnit 测试共存

Pest 和 PHPUnit 可以在同一个项目里共存。既有的 PHPUnit 测试不用改,新的测试直接用 Pest 写就好。
./vendor/bin/pest       # 用 Pest 跑所有测试(也包括 PHPUnit 的)
./vendor/bin/pest --filter="用户"  # 按测试名过滤
php artisan test        # 用 Artisan 命令也走 Pest
Laravel 11 及以后,如果安装了 Pest,php artisan test 会自动用 Pest 来执行。

迁移建议

  1. 先在 tests/Pest.php 里配置好 uses()
  2. 新增的测试用 Pest 写法
  3. 既有的 PHPUnit 测试边确认行为边慢慢迁移
不需要急着把所有测试都改写。Pest 的语法比 PHPUnit 学习成本低,团队接受起来也比较顺。

总结

对比项PHPUnitPest
测试定义类 + 方法test() / it() 函数
断言$this->assert*()expect()->to*()
setupsetUp()beforeEach()
数据驱动@dataProvider->with()
架构检查arch()
执行速度标准相当(基于 PHPUnit 运行)
Pest 不是 PHPUnit 的替代品,而是包裹在 PHPUnit 之上的上层框架。它与 Laravel 集成很深,启动套件生成时都默认选中它。对既有项目引入成本低,从新测试开始试用就能享受红利。 写测试的量上去后,bug 会更早被发现,重构的心理门槛也会降低。Pest 是一款专门用来降低“写测试本身的摩擦”的工具。

Pest 官方文档

Pest 的完整功能(数据集、覆盖率、并行执行等)请查看官方文档。
最后修改于 2026年7月13日