> ## 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();
```

### 셋업과 티어다운

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`을 사용합니다.

```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` 트레이트도 `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>
  `php artisan test`는 Laravel 11 이후, Pest가 설치되어 있는 경우 자동으로 Pest를 사용하여 실행합니다.
</Info>

### 이관 진행 방법

1. 우선 `tests/Pest.php`에 `uses()`의 설정을 추가한다
2. 새로 추가하는 테스트는 Pest 형식으로 쓴다
3. 기존 PHPUnit 테스트는 동작을 확인하면서 조금씩 이관한다

서둘러 전 테스트를 다시 쓸 필요는 없습니다. Pest의 문법은 PHPUnit에 비해 학습 비용이 낮으므로, 팀으로의 침투도 비교적 부드럽습니다.

***

## 정리

| 비교 항목   | PHPUnit            | Pest                 |
| ------- | ------------------ | -------------------- |
| 테스트 정의  | 클래스 + 메서드          | `test()` / `it()` 함수 |
| 어서션     | `$this->assert*()` | `expect()->to*()`    |
| 셋업      | `setUp()`          | `beforeEach()`       |
| 데이터 구동  | `@dataProvider`    | `->with()`           |
| 아키텍처 검증 | 없음                 | `arch()`             |
| 실행 속도   | 표준                 | 동등 (PHPUnit 상에서 동작)  |

Pest는 PHPUnit의 치환이 아니라, PHPUnit을 랩한 상위 레이어입니다. Laravel과의 통합도 깊고, 스타터 킷 생성 시부터 기본으로 선택되는 정도가 되어 있습니다. 기존 프로젝트로의 도입 비용은 낮고, 새 테스트부터 시험 삼아 시작하는 것만으로 혜택을 받을 수 있습니다.

테스트를 쓰는 양이 늘면 버그의 발견이 빨라지고, 리팩토링에 대한 심리적 허들도 낮아집니다. Pest는 그 "테스트를 쓰는 것 자체의 마찰"을 줄이기 위한 도구입니다.

<Card title="Pest 공식 문서" icon="book-open" href="https://pestphp.com/docs">
  데이터셋, 커버리지, 병렬 실행 등 Pest의 모든 기능에 대해서는 공식 문서를 참고하세요.
</Card>


## Related topics

- [테스트 입문](/ko/testing.md)
- [Laravel이란](/ko/introduction.md)
- [Pest를 이용한 고급 테스트](/ko/advanced/testing-pest.md)
- [HTTP 테스트](/ko/http-tests.md)
- [Orchestra Testbench로 Laravel 패키지를 테스트하기](/ko/advanced/package-testing.md)
