[LARAVEL] 테스트

김세연·2025년 9월 23일

Laravel

목록 보기
13/14
post-thumbnail

테스트 (Testing)

테스트는 코드가 예상대로 정확하게 동작하는지를 자동으로 검증하는 과정이다.
이는 버그를 줄이고, 미래에 코드를 변경하는 것에 대한 자신감을 주는 매우 중요한 활동이다.

훌륭한 곡예사는 안전망 없이도 공연을 할 수 있지만, 안전망(테스트)이 있기 때문에 더 대담하고 새로운 기술에 자신감 있게 도전할 수 있다.

마찬가지로, 개발자는 테스트 코드가 있기 때문에 새로운 기능을 추가하거나 기존 코드를 리팩토링(개선)할 때, "혹시 다른 기능이 고장 나지 않았을까?" 라는 불안감 없이 자신감 있게 코드를 변경할 수 있다.

라라벨의 테스트 종류

라라벨은 기본적으로 두 가지 종류의 테스트를 지원하며, tests 폴더 안에 각각 UnitFeature라는 하위 폴더로 구분되어 있다.

  • 유닛 테스트 (Unit Tests - 부품 검사)
    애플리케이션의 가장 작은 단위(예: 클래스의 특정 메서드 하나)를 독립적으로 검사하는 테스트이다.
    "이 calculatePrice() 메서드에 1000원과 10% 할인율을 넣으면, 정확히 900원이 반환되는가?" 와 같이 매우 작고 고립된 부분을 테스트한다.

  • 기능 테스트 (Feature Tests - 완제품 검사)
    사용자의 행동처럼 여러 부분이 함께 동작하는 전체 흐름을 테스트한다.
    "사용자가 /tasks 주소로 POST 요청을 보내면, 실제로 데이터베이스에 새로운 task가 생성되고, 성공했다는 JSON 응답이 돌아오는가?" 와 같이 API 엔드포인트나 웹 페이지의 전체 동작을 검증한다.
    백엔드 개발에서는 주로 기능 테스트를 작성하게 된다.


테스트 작성 및 실행 방법

  • 테스트 파일 생성
    php artisan make:test 명령어로 새로운 테스트 파일을 생성한다.
# 기능 테스트 생성
php artisan make:test TaskApiTest

# 유닛 테스트 생성
php artisan make:test TaskTest --unit

이 명령어는 tests/Feature/TaskApiTest.php 파일을 생성한다.

  • 테스트 코드 작성 (tests/Feature/TaskApiTest.php)
    테스트 메서드는 보통 test_ 또는 @test 어노테이션으로 시작한다.
    메서드 이름은 무엇을 테스트하는지 명확하게 짓는 것이 중요하다.

    • 준비 (Arrange): 테스트에 필요한 데이터를 준비한다. (예: 사용자 생성)

    • 실행 (Act): 실제 테스트할 행동을 실행한다. (예: API 요청)

    • 단언 (Assert): 실행 결과가 우리가 예상한 것과 같은지 확인한다.

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\Task;
use App\Models\User;

class TaskApiTest extends TestCase
{
    // 테스트 실행 시마다 데이터베이스를 초기화해주는 유용한 기능
    use RefreshDatabase;

    /** @test */
    public function a_user_can_get_a_list_of_their_tasks()
    {
        // 1. 준비 (Arrange)
        // 테스트용 사용자를 한 명 만들고, 그 사용자의 할 일을 2개 만든다.
        $user = User::factory()->create();
        Task::factory()->count(2)->create(['user_id' => $user->id]);

        // 2. 실행 (Act)
        // 해당 사용자로 로그인한 것처럼 API에 GET 요청을 보낸다.
        $response = $this->actingAs($user)->getJson('/api/tasks');

        // 3. 단언 (Assert)
        // 응답 상태 코드가 200 (성공)인지 확인한다.
        $response->assertStatus(200);
        // 응답으로 받은 JSON 데이터가 정확히 2개인지 확인한다.
        $response->assertJsonCount(2);
    }
}
  • 테스트 실행
    터미널에서 다음 명령어를 실행하면 tests 폴더 안의 모든 테스트를 자동으로 실행하고 결과를 보여줍니다.
php artisan test

특정 파일만 테스트하고 싶다면 파일 경로를 추가할 수 있다:
php artisan test tests/Feature/TaskApiTest.php


팩토리 (Factories) - 테스트 데이터 공장

테스트를 작성할 때마다 Task::create(['title' => ...]) 처럼 테스트 데이터를 수동으로 만드는 것은 번거롭다.
팩토리(Factory)는 실제 데이터처럼 보이는 가짜 테스트 데이터를 대량으로, 그리고 손쉽게 생성해주는 강력한 도구이다.

  • 정의하기 (database/factories/TaskFactory.php):
    어떤 모델의 가짜 데이터를 어떤 규칙으로 만들지 정의합니다. Faker라는 라이브러리를 사용하여 진짜 같은 가짜 데이터를 만들 수 있다.
public function definition()
{
    return [
        'user_id' => User::factory(),
        'title' => $this->faker->sentence(), // 가짜 문장
        'completed' => $this->faker->boolean(), // true 또는 false
    ];
}
  • 사용하기 (테스트 파일에서):
    이제 테스트 코드에서 매우 간결하게 데이터를 생성할 수 있다.
// Task 1개 생성
$task = Task::factory()->create();

// 특정 user에 속한 Task 10개 생성
$user = User::factory()->create();
$tasks = Task::factory()->count(10)->create(['user_id' => $user->id]);

// 완료된 상태의 Task 1개 생성
$completedTask = Task::factory()->completed()->create(); // '상태(State)' 기능 활용

모킹과 페이킹 (Mocking & Faking) - 가짜 연기자

외부 API(예: 결제 시스템, 날씨 정보 API)와 통신하는 코드를 테스트해야 할 때, 실제 API를 호출하는 것은 느리고, 비용이 발생하며, 불안정하다.
이때 모킹(Mocking)이나 페이킹(Faking)을 사용하여 외부 서비스의 행동을 흉내 내는 '가짜 객체'를 만들어 테스트를 안정적으로 만든다.

  • 역할:
    "실제 결제사에 요청을 보내는 대신, '성공'이라는 응답을 보냈다고 가정하고 테스트하자."

  • 라라벨의 페이킹 기능: 라라벨은 Mail, Notification, Queue, Http 등 많은 내장 기능에 대해 간편한 페이킹 메서드를 제공한다.

use Illuminate\Support\Facades\Http;

/** @test */
public function it_fetches_weather_data_correctly()
{
    // Http 클라이언트를 가짜로 대체하고, 특정 URL 요청에 대해 가짜 응답을 설정
    Http::fake([
        'api.weather.com/*' => Http::response(['temperature' => 25], 200),
    ]);

    $response = $this->get('/api/weather');
    $response->assertOk();
    $response->assertJson(['temp_celsius' => 25]);

    // 실제로 api.weather.com 에 요청이 갔는지도 검증할 수 있음
    Http::assertSent(function ($request) {
        return $request->url() == 'https://api.weather.com/seoul';
    });
}

데이터베이스 트랜잭션과 RefreshDatabase

테스트는 서로에게 영향을 주지 않고 독립적으로 실행되어야 한다.
RefreshDatabase 트레이트를 사용하면, 각각의 테스트 메서드가 실행되기 전에 데이터베이스를 깨끗한 초기 상태로 되돌려준다.

  • 동작 원리:
    첫 테스트 시작 시 한 번만 마이그레이션을 실행하고, 그 이후 각 테스트는 데이터베이스 트랜잭션 내에서 실행된다.
    테스트가 끝나면 해당 트랜잭션을 롤백하여 모든 변경사항을 취소하는 방식으로 매우 빠르게 데이터베이스를 초기화한다.

테스트 주도 개발 (TDD - Test-Driven Development)

TDD는 코드를 작성하는 방식에 대한 하나의 개발 방법론이다.
"실제 기능 코드를 작성하기 전에, 그 기능에 대한 실패하는 테스트 코드를 먼저 작성한다"는 것이 핵심이다.

  • 개발 순서:

    • Red: 실패할 것을 예상하고 테스트 코드를 먼저 작성한다. (php artisan test -> 실패)

    • Green: 이 테스트를 통과할 만큼의 최소한의 실제 코드를 작성한다. (php artisan test -> 성공)

    • Refactor: 코드를 개선하고 정리한다. (테스트는 계속 성공 상태 유지)


Pest - 더 표현적인 테스트 프레임워크

Pest는 라라벨의 기본 테스트 프레임워크인 PHPUnit 위에 만들어진, 더 읽기 쉽고 표현적인 문법을 제공하는 인기 있는 테스트 프레임워크이다.

  • PHPUnit (기본):
public function test_a_user_can_get_tasks() { ... }
  • Pest:
test('a user can get their tasks', function () { ... });
// it('allows a user to get their tasks', function () { ... });

마치 일반적인 문장처럼 테스트 코드를 작성할 수 있어 가독성을 중요하게 생각하는 개발자들에게 인기가 많다.

마지막 한 걸음

테스트 커버리지 (Test Coverage) - 내 코드의 건강 검진표

테스트 커버리지는 작성한 테스트 코드가 실제 애플리케이션 코드의 몇 퍼센트를 실행했는지를 측정하는 지표이다.
이를 통해 테스트하지 않은 '사각지대'를 찾아낼 수 있다.

  • 실행 방법:
# --coverage 옵션을 추가하면 테스트 실행 후 커버리지 리포트를 보여준다.
php artisan test --coverage
  • 주의할 점:
    100% 커버리지를 맹목적으로 추구할 필요는 없다.
    중요한 비즈니스 로직과 복잡한 조건문 위주로 높은 커버리지를 유지하는 것이 더 효율적이다.

병렬 테스트 (Parallel Testing) - 속도의 혁신

애플리케이션이 커지면 테스트의 개수도 수백, 수천 개로 늘어나 모든 테스트를 실행하는 데 몇 분씩 걸릴 수 있다.
병렬 테스트는 여러 CPU 코어를 사용하여 여러 테스트를 동시에 실행함으로써 이 시간을 획기적으로 단축시켜 준다.

  • 실행 방법:
# --parallel 옵션을 추가하면 알아서 테스트를 분산 실행한다.
php artisan test --parallel

대규모 프로젝트에서는 CI/CD 파이프라인의 시간을 절약해 주는 필수적인 기능이다.

브라우저 테스트 (Browser Testing) - Dusk

지금까지 배운 기능 테스트는 HTTP 요청을 시뮬레이션하는 백엔드 중심의 테스트였다.
만약 자바스크립트로 구현된 프론트엔드 UI(버튼 클릭, 폼 입력 등)까지 실제 크롬 브라우저를 조종하여 테스트하고 싶다면, 라라벨 Dusk를 사용한다.

  • 역할:
    "로그인 버튼을 누르고, 아이디/비밀번호를 입력한 다음, '로그인' 버튼을 클릭했을 때, 실제로 대시보드 페이지로 이동하는가?" 와 같은 사용자 시나리오를 자동화한다.

결론

테스트는 단순히 버그를 찾는 행위를 넘어, 소프트웨어의 품질과 안정성을 보장하는 가장 근본적인 활동이다.

테스트 코드는 다음과 같은 가치를 제공하는 살아있는 문서이다.

  • 신뢰성:
    내 코드가 의도한 대로 동작한다는 것을 증명한다.

  • 유지보수성:
    코드를 수정한 후에도 기존 기능이 고장 나지 않았다는 자신감을 준다.

  • 협업:
    다른 개발자가 내 코드를 쉽게 이해하고 안전하게 수정할 수 있도록 돕는다.

profile
공부 재밌따

0개의 댓글