0Pricing
PHP Academy · 강의

통합 테스트와 기능 테스트

실제 환경에서 구성 요소들이 함께 작동하는 방식을 테스트합니다.

통합 테스트와 기능 테스트은(는) CoddyKit의 무료 PHP Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PHP Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

단위 테스트를 넘어서

단위 테스트는 모든 것을 모의 객체로 대체한 상태에서 클래스가 고립되어 작동한다는 것을 입증합니다. 하지만 모의 객체는 거짓된 결과를 낼 수 있습니다. 실제 데이터베이스, 프레임워크 라우터 또는 HTTP 계층은 다르게 동작하기 때문입니다. 통합 테스트는 둘 이상의 실제 구성 요소를 연결해 함께 검증하고, 기능 테스트는 공개 진입점(HTTP 요청, 콘솔 명령)을 통해 애플리케이션을 처음부터 끝까지 실행합니다. 이 레슨에서는 두 종류의 테스트를 적절히 사용하고 작성합니다.

테스트 피라미드

건강한 테스트 모음은 피라미드 형태입니다.

  • 많은 단위 테스트 — 빠르고 고립되어 있으며 지속적으로 실행됩니다.
  • 더 적은 통합 테스트 — 실제 데이터베이스/대기열/캐시를 사용하므로 더 느립니다.
  • 소수의 기능/종단 간 테스트 — 전체 스택을 사용하고 가장 느리며 가장 불안정합니다.

이를 거꾸로 구성해 대부분 종단 간 테스트인 '아이스크림 콘' 형태로 만들면 느리고 불안정한 테스트 모음이 됩니다. 가능할 때마다 커버리지를 더 낮은 계층으로 내리십시오.

통합 테스트: 실제 데이터베이스를 대상으로 하는 저장소

저장소에 대한 통합 테스트는 모의 객체가 아닌 실제 데이터베이스 연결을 사용해 SQL, 스키마 및 매핑 오류를 찾아냅니다. 전용 테스트 데이터베이스를 사용하거나, 사용하는 데이터베이스 문법과 충분히 비슷한 메모리 내 SQLite를 사용하고 실제 PDO를 연결하십시오.

<?php
use PHPUnit\Framework\TestCase;

final class UserRepositoryTest extends TestCase
{
    private \PDO $pdo;

    protected function setUp(): void
    {
        $this->pdo = new \PDO('sqlite::memory:');
        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
    }

    public function test_persists_and_finds_a_user(): void
    {
        $repo = new UserRepository($this->pdo);
        $id = $repo->create('ada@example.com');
        self::assertSame('ada@example.com', $repo->find($id)->email);
    }
}

격리: 트랜잭션 롤백

통합 테스트는 서로의 상태를 유출해서는 안 됩니다. 가장 빠른 패턴은 모든 테스트를 트랜잭션으로 감싼 뒤 tearDown()에서 롤백하는 것입니다. 그러면 다시 데이터를 채우지 않아도 데이터베이스가 깨끗한 상태로 돌아갑니다. (테스트 대상 코드가 커밋하거나 DDL을 사용하는 경우에는 테이블 비우기가 대안입니다.)

<?php
protected function setUp(): void
{
    $this->pdo = TestDb::connection();
    $this->pdo->beginTransaction();
}

protected function tearDown(): void
{
    $this->pdo->rollBack();   // undo everything this test did
}

픽스처와 팩토리

테스트에는 데이터가 필요합니다. 깨지기 쉬운 거대한 SQL 덤프 대신, 테스트에 필요한 행만 생성하는 작은 팩토리를 사용하고 각 경우에 맞게 합리적인 기본값을 덮어쓰십시오. 이렇게 하면 각 테스트의 의도가 분명해지고 스키마가 확장되어도 잘 유지됩니다.

<?php
final class UserFactory
{
    public static function create(\PDO $pdo, array $overrides = []): int
    {
        $data = array_merge(['email' => 'user'.uniqid().'@test.dev'], $overrides);
        $stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
        $stmt->execute($data);
        return (int) $pdo->lastInsertId();
    }
}

기능 테스트: 클라이언트처럼 애플리케이션 호출하기

기능 테스트는 실제 애플리케이션 커널/라우터에 시뮬레이션한 요청을 보내고 상태 코드, 헤더, 본문과 같은 응답을 검증합니다. Symfony의 WebTestCase와 Laravel의 HTTP 테스트 도우미는 실제 웹 서버를 시작하지 않고도 정확히 이 작업을 수행합니다. 아래는 Symfony 방식입니다.

<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

final class HealthControllerTest extends WebTestCase
{
    public function test_health_endpoint_returns_ok(): void
    {
        $client = static::createClient();
        $client->request('GET', '/health');

        self::assertResponseIsSuccessful();        // 2xx
        self::assertJson($client->getResponse()->getContent());
    }
}

응답 검증하기

기능 테스트는 경계에서 관찰 가능한 동작을 검증합니다. 상태, JSON 구조, 데이터베이스의 부수 효과가 그 예입니다. 아래에서는 POST로 리소스를 생성한 뒤 HTTP 응답과 레코드가 실제 저장소에 저장되었는지를 모두 확인해 전체 스택이 협력했음을 입증합니다.

<?php
public function test_creating_a_user_persists_it(): void
{
    $client = static::createClient();
    $client->request('POST', '/users', server: [
        'CONTENT_TYPE' => 'application/json',
    ], content: json_encode(['email' => 'ada@example.com']));

    self::assertResponseStatusCodeSame(201);

    $repo = static::getContainer()->get(UserRepository::class);
    self::assertNotNull($repo->findByEmail('ada@example.com'));
}

외부 경계만 선택적으로 가짜 처리하기

기능 테스트에서도 실제 결제 제공업체를 호출하거나 실제 이메일을 보내서는 안 됩니다. 가장 바깥쪽의 외부 서비스만 교체하십시오. 테스트 컨테이너에서 가짜 구현이나 메모리 내 구현으로 바꾸고, 자체 코드와 라우팅 및 데이터베이스는 실제 상태로 유지합니다. 이렇게 하면 불안정한 네트워크 호출 없이도 종단 간 동작의 충실도를 유지할 수 있습니다.

<?php
public function test_checkout_charges_via_gateway(): void
{
    $client = static::createClient();
    // Replace the real gateway binding with an in-memory fake:
    static::getContainer()->set(PaymentGateway::class, new FakePaymentGateway());

    $client->request('POST', '/checkout', content: json_encode(['cart' => 1]));
    self::assertResponseIsSuccessful();
}

테스트 모음은 분리하고 실행 속도도 나누기

phpunit.xml을 테스트 모음으로 나누어 빠른 단위 테스트 모음은 저장할 때마다 실행하고, 느린 통합/기능 테스트 모음은 필요할 때나 지속적 통합 환경에서 실행하십시오. 디렉터리별로 그룹화하고 --testsuite로 선택하면 됩니다.

<phpunit>
  <testsuites>
    <testsuite name="unit">
      <directory>tests/Unit</directory>
    </testsuite>
    <testsuite name="integration">
      <directory>tests/Integration</directory>
    </testsuite>
    <testsuite name="functional">
      <directory>tests/Functional</directory>
    </testsuite>
  </testsuites>
</phpunit>

커버리지보다 결정성이 중요합니다

테스트 모음에 대한 신뢰를 가장 빠르게 잃게 만드는 것은 불안정성입니다. 통합/기능 테스트에서 항상 같은 결과가 나오도록 하십시오.

  • 시간을 고정하고 (time()을 호출하는 대신 시계를 주입합니다).
  • 무작위성의 시드를 설정하고, 정렬되지 않은 조회 결과에 순서 의존 검증을 하지 않습니다.
  • 모든 테스트 후 데이터베이스 상태를 초기화합니다(트랜잭션 롤백 또는 테이블 비우기).
  • 테스트 실행 순서에 절대 의존하지 않습니다.

테스트 데이터베이스를 운영 환경과 일치시키기

메모리 내 SQLite는 빠르지만 방언 차이(대소문자 구분, JSON 함수, 외래 키 적용, 자료형) 때문에 실제 데이터베이스 엔진에서만 드러나는 오류를 숨길 수 있습니다. 엔진별 SQL을 사용하는 코드라면 편리한 대체품 대신 운영 환경과 동일한 엔진을 대상으로 통합 테스트를 실행하십시오. 일반적으로 CI에서 일회용 Docker 컨테이너를 사용합니다.

<?php
// Read connection from environment so CI points at a real Postgres/MySQL container
$dsn = getenv('TEST_DATABASE_URL') ?: 'sqlite::memory:';
$pdo = new \PDO($dsn);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// In CI, TEST_DATABASE_URL targets the same engine as production.

빠른 확인

기능 테스트에서는 타사 결제 제공업체를 어떻게 다뤄야 할까요?

복습

구성 요소를 함께 테스트하는 방법을 배웠습니다.

  • 통합 테스트는 실제 데이터베이스/서비스를 사용해 모의 객체가 숨기는 문제를 찾아내며, 트랜잭션 롤백으로 격리합니다.
  • 기능 테스트는 시뮬레이션한 요청으로 실제 커널을 실행하고 응답과 부수 효과를 검증합니다.
  • 팩토리는 최소한의 데이터를 생성하고, 가장 바깥쪽의 외부 경계만 가짜로 대체합니다.
  • 테스트 모음을 속도별로 나누고 느린 테스트에서도 결정성을 유지합니다.

다음: 변이 테스트로 테스트 품질 측정하기.

자주 묻는 질문

“통합 테스트와 기능 테스트” 강의는 무료인가요?

네 — “통합 테스트와 기능 테스트” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PHP Academy 강의 전체를 잠금 해제할 수 있습니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“통합 테스트와 기능 테스트”에서 뭘 배우나요?

실제 환경에서 구성 요소들이 함께 작동하는 방식을 테스트합니다. 브라우저에서 직접 실행하는 실습 코드로 PHP Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

PHP Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 PHP Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“통합 테스트와 기능 테스트” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 PHP Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 PHP Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 테스트 주도 개발 워크플로
  2. Mockery로 모킹과 스터빙
  3. 통합 테스트와 기능 테스트
  4. Infection으로 변이 테스트
← PHP Academy(으)로 돌아가기