> ## 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와 PHPUnit을 사용한 Laravel 애플리케이션의 테스트 작성 방법

## 테스트란

테스트란, 코드가 기대한 대로 동작하는지를 자동적으로 검증하는 구조입니다.
테스트를 작성함으로써, 기능 추가나 수정을 했을 때에 기존의 동작이 무너지지 않았는지를 신속하게 확인할 수 있습니다.
팀에서의 개발에서는, 테스트가 있음으로써 안심하고 코드를 변경·리뷰할 수 있습니다.

Laravel은 처음부터 테스트의 지원이 포함되어 있어, [Pest](https://pestphp.com)와 [PHPUnit](https://phpunit.de)의 양쪽을 사용할 수 있습니다.
신규 설치 시에는 `phpunit.xml` 설정 파일과 `tests/` 디렉터리가 자동적으로 준비되어 있습니다.

<Tip>
  Pest는 PHPUnit을 기반으로 구축되어 있어, 보다 간결하고 읽기 쉬운 구문으로 테스트를 작성할 수 있습니다. 지금부터 테스트를 시작하는 경우는 Pest를 선택하는 것이 추천됩니다.
</Tip>

## `tests/` 디렉터리의 구성

```
tests/
├── Feature/      # 기능 테스트
│   └── ExampleTest.php
├── Unit/         # 유닛 테스트
│   └── ExampleTest.php
└── TestCase.php
```

* **`Feature/`** — 기능 테스트를 둡니다. HTTP 리퀘스트를 포함하는 큰 단위의 테스트에 사용합니다. 여러 오브젝트가 연계하는 동작이나 API 엔드포인트의 검증 등, 애플리케이션 전체에 가까운 테스트입니다. **대부분의 테스트는 여기에 두게 됩니다.**
* **`Unit/`** — 유닛 테스트를 둡니다. 단일의 클래스나 메서드 등, 작은 단위의 테스트에 사용합니다. Laravel 애플리케이션은 기동하지 않기 때문에, 데이터베이스나 다른 프레임워크 기능은 사용할 수 없습니다.

## 테스트의 작성

`make:test` Artisan 명령으로 새로운 테스트 클래스를 생성합니다.

```shell theme={null}
# Feature 테스트를 작성 (기본값)
php artisan make:test TodoTest

# Unit 테스트를 작성
php artisan make:test TodoTest --unit
```

`tests/Feature/TodoTest.php`가 생성됩니다.

## 테스트의 실행

`php artisan test` 명령으로 테스트를 실행합니다.

```shell theme={null}
php artisan test
```

`vendor/bin/pest`나 `vendor/bin/phpunit`을 직접 실행해도 같은 결과를 얻을 수 있지만, `php artisan test` 쪽이 보기 좋은 출력을 얻을 수 있기 때문에 추천합니다.

특정한 테스트 스위트만을 실행하고 싶을 때는 옵션을 사용합니다.

```shell theme={null}
# Feature 테스트만 실행
php artisan test --testsuite=Feature

# 실패하면 즉시 정지
php artisan test --stop-on-failure
```

## 기본적인 테스트 작성 방법

심플한 어서션의 예입니다.

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('true는 true이다', function () {
      expect(true)->toBeTrue();
  });

  test('문자열이 포함되어 있는지 확인', function () {
      $message = 'Hello, Laravel!';

      expect($message)->toContain('Laravel');
  });
  ```

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

  namespace Tests\Unit;

  use PHPUnit\Framework\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_true_is_true(): void
      {
          $this->assertTrue(true);
      }

      public function test_string_contains_laravel(): void
      {
          $message = 'Hello, Laravel!';

          $this->assertStringContainsString('Laravel', $message);
      }
  }
  ```
</CodeGroup>

### 자주 사용하는 어서션

| Pest                          | PHPUnit                                     | 설명            |
| ----------------------------- | ------------------------------------------- | ------------- |
| `expect($x)->toBe($y)`        | `$this->assertSame($y, $x)`                 | 엄밀히 같음        |
| `expect($x)->toEqual($y)`     | `$this->assertEquals($y, $x)`               | 같음 (타입은 상관없음) |
| `expect($x)->toBeTrue()`      | `$this->assertTrue($x)`                     | `true`임       |
| `expect($x)->toBeFalse()`     | `$this->assertFalse($x)`                    | `false`임      |
| `expect($x)->toBeNull()`      | `$this->assertNull($x)`                     | `null`임       |
| `expect($x)->toContain($y)`   | `$this->assertStringContainsString($y, $x)` | 문자열을 포함       |
| `expect($x)->toHaveCount($n)` | `$this->assertCount($n, $x)`                | 요소수가 일치       |

## HTTP 테스트

Laravel의 HTTP 테스트에서는, 실제의 HTTP 서버를 세우지 않고, 라우트에의 리퀘스트를 시뮬레이트할 수 있습니다.
`Feature/` 디렉터리의 테스트 클래스에서 사용할 수 있는 강력한 기능입니다.

### 페이지의 표시를 확인

`get()` 메서드로 GET 리퀘스트를 보내고, 응답을 검증합니다.

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('톱 페이지가 표시된다', function () {
      $response = $this->get('/');

      $response->assertStatus(200);
  });
  ```

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

  namespace Tests\Feature;

  use Tests\TestCase;

  class ExampleTest extends TestCase
  {
      public function test_top_page_is_displayed(): void
      {
          $response = $this->get('/');

          $response->assertStatus(200);
      }
  }
  ```
</CodeGroup>

### ToDo 앱의 HTTP 테스트 예

`Todo` 모델이 있는 애플리케이션을 예로, CRUD의 각 조작을 테스트합니다.

<CodeGroup>
  ```php Pest theme={null}
  <?php

  use App\Models\Todo;
  use Illuminate\Foundation\Testing\RefreshDatabase;

  uses(RefreshDatabase::class);

  test('ToDo의 일람 페이지가 표시된다', function () {
      Todo::factory()->count(3)->create();

      $response = $this->get('/todos');

      $response->assertStatus(200);
      $response->assertSee('일람');
  });

  test('ToDo를 신규 작성할 수 있다', function () {
      $response = $this->post('/todos', [
          'title' => 'Laravel을 배운다',
      ]);

      $response->assertRedirect('/todos');
      $this->assertDatabaseHas('todos', ['title' => 'Laravel을 배운다']);
  });

  test('ToDo를 삭제할 수 있다', function () {
      $todo = Todo::factory()->create();

      $response = $this->delete("/todos/{$todo->id}");

      $response->assertRedirect('/todos');
      $this->assertDatabaseMissing('todos', ['id' => $todo->id]);
  });
  ```

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

  namespace Tests\Feature;

  use App\Models\Todo;
  use Illuminate\Foundation\Testing\RefreshDatabase;
  use Tests\TestCase;

  class TodoTest extends TestCase
  {
      use RefreshDatabase;

      public function test_todo_list_is_displayed(): void
      {
          Todo::factory()->count(3)->create();

          $response = $this->get('/todos');

          $response->assertStatus(200);
          $response->assertSee('일람');
      }

      public function test_todo_can_be_created(): void
      {
          $response = $this->post('/todos', [
              'title' => 'Laravel을 배운다',
          ]);

          $response->assertRedirect('/todos');
          $this->assertDatabaseHas('todos', ['title' => 'Laravel을 배운다']);
      }

      public function test_todo_can_be_deleted(): void
      {
          $todo = Todo::factory()->create();

          $response = $this->delete("/todos/{$todo->id}");

          $response->assertRedirect('/todos');
          $this->assertDatabaseMissing('todos', ['id' => $todo->id]);
      }
  }
  ```
</CodeGroup>

### 자주 사용하는 응답의 어서션

| 메서드                                     | 설명                       |
| --------------------------------------- | ------------------------ |
| `assertStatus(200)`                     | 응답의 HTTP 스테이터스 코드를 확인    |
| `assertOk()`                            | 스테이터스 코드가 200임을 확인       |
| `assertRedirect('/path')`               | 지정 URL에의 리다이렉트를 확인       |
| `assertSee('텍스트')`                      | 응답 본문에 텍스트가 포함되는지 확인     |
| `assertDontSee('텍스트')`                  | 응답 본문에 텍스트가 포함되지 않는지 확인  |
| `assertJson([...])`                     | JSON 응답의 데이터를 확인         |
| `assertDatabaseHas('table', [...])`     | 데이터베이스에 레코드가 존재하는지 확인    |
| `assertDatabaseMissing('table', [...])` | 데이터베이스에 레코드가 존재하지 않는지 확인 |

<Info>
  `RefreshDatabase` 트레이트를 사용하면, 각 테스트의 실행 후에 데이터베이스를 리셋합니다. 테스트 간에 데이터가 간섭하지 않도록 하기 위해 사용합니다.
</Info>

## 테스트 환경

### `phpunit.xml`의 설정

프로젝트 루트의 `phpunit.xml`에서 테스트 환경의 설정을 수행합니다.
기본적으로 세션과 캐시가 `array` 드라이버로 설정되어 있어, 테스트 실행 중에 데이터가 남지 않습니다.

```xml theme={null}
<env name="APP_ENV" value="testing"/>
<env name="CACHE_STORE" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
```

데이터베이스에는, 파일을 남기지 않는 SQLite의 인메모리 데이터베이스를 사용하는 것이 일반적입니다.

```xml theme={null}
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
```

### `.env.testing` 파일

프로젝트 루트에 `.env.testing` 파일을 작성하면, 테스트 실행 시에 `.env` 대신에 로드됩니다.
테스트 전용의 데이터베이스 접속이나 외부 서비스의 설정을 나누고 싶은 경우에 사용합니다.

```ini theme={null}
APP_ENV=testing
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
```

<Warning>
  설정을 캐시하고 있는 경우, `php artisan config:clear`를 실행한 후에 테스트를 실행해 주세요. 오래된 캐시가 사용되어 설정이 반영되지 않을 수 있습니다.
</Warning>

## 병렬 테스트 실행

테스트 수가 늘어나면 실행 시간이 길어집니다.
`--parallel` 옵션을 사용하면, 여러 프로세스에서 테스트를 동시 실행해 시간을 단축할 수 있습니다.

우선 `brianium/paratest` 패키지를 설치합니다.

```shell theme={null}
composer require brianium/paratest --dev
```

그 후, `--parallel` 옵션을 붙여 실행합니다.

```shell theme={null}
php artisan test --parallel
```

기본적으로 CPU의 코어 수만큼의 프로세스가 기동합니다. 프로세스 수를 지정하려면 `--processes` 옵션을 사용합니다.

```shell theme={null}
php artisan test --parallel --processes=4
```

<Tip>
  병렬 테스트를 사용할 때는, 각 프로세스가 독립된 데이터베이스를 사용해야 합니다. `RefreshDatabase` 트레이트와 `phpunit.xml`의 인메모리 SQLite 설정을 조합하면 문제없이 동작합니다.
</Tip>

## 다음 단계

<Card title="HTTP 테스트" icon="arrow-right" href="/ko/http-tests">
  리퀘스트의 시뮬레이션, 인증 첨부 테스트, JSON 어서션 등, 보다 자세한 HTTP 테스트의 방법을 확인할 수 있습니다.
</Card>


## Related topics

- [HTTP 테스트](/ko/http-tests.md)
- [Laravel Nightwatch 입문](/ko/blog/nightwatch-introduction.md)
- [Pest를 이용한 고급 테스트](/ko/advanced/testing-pest.md)
- [테스트](/ko/packages/laravel-bluesky/testing.md)
- [콘솔 테스트](/ko/console-tests.md)
