> ## 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()` 函式接收 closure。因為不需定義類別或方法，測試的意圖從第一行就清楚。`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('wang@example.com')
    ->is_admin->toBeFalse();
```

### Setup 與 Teardown

PHPUnit 的 `setUp()` 對應 `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 — 表格式驅動測試

若要以多筆資料執行同一測試邏輯，可用 `dataset`。

```php theme={null}
test('無效的電子郵件會驗證失敗', function (string $email) {
    $response = $this->postJson('/api/users', [
        'name' => '王小明',
        'email' => $email,
    ]);

    $response->assertUnprocessable();
})->with([
    '沒有 email' => [''],
    '格式錯誤' => ['notanemail'],
    '雙 @' => ['a@@example.com'],
]);
```

每筆資料集的 label（如 `'沒有 email'`）會出現在測試名稱上，能一眼看出是哪個模式失敗。

***

## `arch()` 測試 — 自動檢查架構

Pest 的 `arch()` 是驗證程式碼庫結構的測試。像「Controller 是否直接依賴 Model」「Model 是否繼承 Eloquent」這類架構規則能自動驗證。

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

test('Controller 皆為 final', function () {
    arch()->expect('App\Http\Controllers')
        ->toBeFinal();
});

test('Service 類別不依賴 Controller', function () {
    arch()->expect('App\Services')
        ->not->toUse('App\Http\Controllers');
});
```

<Tip>
  `arch()` 測試以靜態分析原始碼執行，不會真的發 HTTP 請求或存取 DB，速度非常快。
</Tip>

Pest 也提供常用架構規則的預設集。

```php theme={null}
test('遵循 Laravel 架構規則', function () {
    arch()->preset()->laravel();
});
```

`laravel()` 預設集會依 Laravel 一般慣例驗證 Model 命名、Controller 繼承、Middleware 結構等。

***

## Laravel 專案的實務範例

### 建立測試

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

於 Laravel 11 以後保持預設設定執行時，會產生 Pest 格式的測試檔。

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

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

### 直接使用 Laravel 的測試 helper

Pest 繼承自 Laravel 的 `TestCase`，因此 `actingAs()`、`assertDatabaseHas()`、HTTP 測試 helper 等所有 Laravel 測試 helper 都可以直接使用。

```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);
});
```

### Factory 與資料庫交易

`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 起，`php artisan test` 若已安裝 Pest 會自動用 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">
  Dataset、覆蓋率、平行執行等 Pest 完整功能，請參考官方文件。
</Card>


## Related topics

- [部落格](/zh-TW/blog/index.md)
- [測試入門](/zh-TW/testing.md)
- [使用 Pest 進行進階測試](/zh-TW/advanced/testing-pest.md)
- [什麼是 Laravel](/zh-TW/introduction.md)
- [Laravel Maestro — 啟動套件開發的協調器](/zh-TW/blog/maestro-introduction.md)
