为什么 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' );
});
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' );
}
}
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 );
});
工厂与数据库事务
RefreshDatabase 和 DatabaseTransactions 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 来执行。
迁移建议
先在 tests/Pest.php 里配置好 uses()
新增的测试用 Pest 写法
既有的 PHPUnit 测试边确认行为边慢慢迁移
不需要急着把所有测试都改写。Pest 的语法比 PHPUnit 学习成本低,团队接受起来也比较顺。
对比项 PHPUnit Pest 测试定义 类 + 方法 test() / it() 函数断言 $this->assert*()expect()->to*()setup setUp()beforeEach()数据驱动 @dataProvider->with()架构检查 无 arch()执行速度 标准 相当(基于 PHPUnit 运行)
Pest 不是 PHPUnit 的替代品,而是包裹在 PHPUnit 之上的上层框架。它与 Laravel 集成很深,启动套件生成时都默认选中它。对既有项目引入成本低,从新测试开始试用就能享受红利。
写测试的量上去后,bug 会更早被发现,重构的心理门槛也会降低。Pest 是一款专门用来降低“写测试本身的摩擦”的工具。
Pest 官方文档 Pest 的完整功能(数据集、覆盖率、并行执行等)请查看官方文档。