Mocking, Stubbing, Spying, and Faking in PHP: 실용 가이드 (샌드박스 예제 포함)

발행: (2025년 12월 3일 오전 04:00 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

현대 PHP 애플리케이션은 API, 데이터베이스, 파일 시스템, 난수 생성기, 시간 제공자, 외부와 통신하는 서비스 등 많은 외부 구성 요소에 의존합니다.

테스트를 작성할 때는 이러한 실제 서비스를 거의 사용하고 싶지 않습니다. 실제 서비스를 호출하면 테스트가 느려지고, 예측 불가능해지며, 격리된 상태로 실행하기 어려워집니다.

이때 테스트 더블이 등장합니다.

테스트 더블은 테스트 중에 실제 의존성을 대체하는 모든 대체 객체를 말합니다. PHP에서는 이러한 객체를 수동으로 만들거나 Mockery 같은 라이브러리, 혹은 PHPUnit에 내장된 목킹 도구를 통해 만들 수 있습니다.

이 글에서 배울 내용

  • 목(mock), 스텁(stub), 스파이(spy), 페이크(fake)가 정확히 무엇인지
  • 각각의 차이점과 언제 사용해야 하는지
  • 순수 PHP로 이들을 만드는 방법
  • 외부 서비스에 의존하는 코드를 테스트하는 방법
  • 목킹할 때 개발자들이 흔히 저지르는 실수
  • onlinephp.io에서 바로 실행할 수 있는 간단한 예제

테스트 더블의 네 가지 주요 유형

테스트 더블은 여러 카테고리로 나뉘며, 각각은 매우 구체적인 목적을 가집니다.

Stub

스텁은 미리 정의된 값을 반환합니다. 어떻게 호출되는지는 신경 쓰지 않고 단순히 응답합니다.

Mock

목은 특정 호출(메서드 이름, 인자)을 기대합니다. 기대한 호출이 발생하지 않으면 테스트가 실패합니다.

Fake

페이크는 가벼운 대체 구현입니다. 실제 객체와 비슷하게 동작하지만 단순화된 방식으로 동작합니다.

Spy

스파이는 나중에 검증할 수 있도록 메서드 호출을 기록합니다. 목과 달리 스파이는 사전에 기대값을 강제하지 않습니다.

어떤 테스트 더블이 필요한지 이해하면 테스트 가독성이 크게 향상됩니다.

샌드박스 예제: 다양한 PHP 테스트 더블 테스트하기

이 샌드박스는 PHP에서 네 가지 주요 테스트 더블을 모두 보여줍니다.

  • Stub – 호출 방식을 확인하지 않고 고정된 결정적 값을 반환
  • Mock – 메서드가 기대한 파라미터와 함께 호출됐는지 검증
  • Fake – 현실적인 동작을 시뮬레이션하는 가벼운 인‑메모리 구현 제공
  • Spy – 나중에 검증할 수 있도록 메서드 호출을 기록

브라우저에서 바로 실행할 수 있습니다:

expectedAmount = $expectedAmount;
    }

    public function charge(int $amount): bool {
        $this->called = true;
        $this->chargedAmount = $amount;
        return true;
    }

    public function verify(): bool {
        return $this->called && $this->chargedAmount === $this->expectedAmount;
    }
}

// -------------------------------------
// 3) Fake: stores transactions in memory
// -------------------------------------
class PaymentGatewayFake implements PaymentGateway {
    public array $transactions = [];

    public function charge(int $amount): bool {
        $this->transactions[] = [
            'amount' => $amount,
            'time'   => date('H:i:s'),
        ];
        return true;
    }
}

// -------------------------------------
// 4) Spy: records method calls for later inspection
// -------------------------------------
class PaymentGatewaySpy implements PaymentGateway {
    public array $calls = [];

    public function charge(int $amount): bool {
        $this->calls[] = $amount;
        return true;
    }
}

// -------------------------------------
// Function under test
// -------------------------------------
function processOrder(PaymentGateway $gateway, int $amount): bool {
    return $gateway->charge($amount);
}

// -------------------------------------
// Run examples
// -------------------------------------

echo "=== STUB ===\n";
$stub = new PaymentGatewayStub();
echo processOrder($stub, 1000) ? "Order OK\n\n" : "Order FAIL\n\n";

echo "=== MOCK ===\n";
$mock = new PaymentGatewayMock(500);
processOrder($mock, 500);
echo $mock->verify() ? "Mock expectations met!\n\n" : "Mock expectations NOT met!\n\n";

echo "=== FAKE ===\n";
$fake = new PaymentGatewayFake();
$fake->charge(300);
$fake->charge(400);
echo "Fake stored " . count($fake->transactions) . " transactions:\n";
print_r($fake->transactions);
echo "\n";

echo "=== SPY ===\n";
$spy = new PaymentGatewaySpy();
$spy->charge(100);
$spy->charge(200);
echo "Spy recorded calls:\n";
print_r($spy->calls);

테스트 더블 네 가지 유형을 깊이 있게 이해하기

짧은 정의만으로도 개요를 잡을 수 있지만, 각 유형을 더 자세히 살펴보면 목적과 사용 시점을 명확히 알 수 있습니다.

Stub

스텁은 메서드 호출에 대해 미리 정해진 응답을 제공하는 단순 객체입니다. 테스트 대상 시스템이 어떻게 사용하든 상관없이 일관된 데이터를 반환하는 것이 전부입니다.

사용 상황

  • API나 데이터베이스처럼 느리거나 신뢰할 수 없는 의존성을 결정적인 응답으로 대체
  • 부작용 없이 특정 반환값에 의존하는 로직을 테스트

예시 시나리오
결제 처리 메서드를 테스트하고 싶지만 실제로 신용카드를 청구하고 싶지는 않을 때, 스텁은 모든 청구에 대해 true를 반환해 주어 로직 자체에 집중할 수 있게 합니다.

Mock

목은 스텁보다 엄격합니다. 특정 메서드가 정확한 인자와 함께 호출되는지를 기대합니다. 기대가 충족되지 않으면 테스트가 실패합니다.

사용 상황

  • 코드가 외부 서비스와 올바르게 상호작용하는지 검증
  • 이메일 전송, 로그 기록 등 부수 효과를 확인

예시 시나리오
charge()가 정확한 금액으로 호출됐는지 확인하고 싶을 때, 시스템이 다른 금액으로 호출하거나 호출 자체가 없으면 목이 테스트를 실패시킵니다.

Fake

페이크는 완전하게 동작하지만 단순화된 의존성 구현입니다. 스텁과 달리 실제 로직을 구현하며, 보통 메모리 상에서 동작해 실제 외부 시스템을 건드리지 않습니다.

사용 상황

  • 복잡한 동작을 실제 리소스를 사용하지 않고 시뮬레이션
  • 느리거나 불안정한 인프라에 의존하지 않고 현실적인 상호작용이 필요한 테스트

예시 시나리오
거래 내역을 메모리에 저장하는 인‑메모리 결제 게이트웨이를 만들면, 실제 결제 시스템에 연결하지 않고도 여러 차례 청구하고 거래 기록을 검사하는 전체 워크플로를 실행할 수 있습니다.

Spy

스파이는 테스트 중에 어떻게 사용됐는지를 기록합니다. 목과 달리 사전에 기대값을 강제하지 않으며, 테스트가 끝난 뒤 기록을 확인해 행동을 검증합니다.

사용 상황

  • 메서드 호출 및 인자를 모니터링하고 검증
  • 즉시 실패시키지 않고 상호작용에 대한 추가 정보를 캡처

예시 시나리오
charge()가 두 번 호출됐고 각각 특정 금액인지 확인하고 싶을 때, 스파이는 모든 호출을 저장해 두어 사후에 어설션을 할 수 있게 해 줍니다.

결론

목킹과 페이킹은 현대 PHP 테스트에서 필수적인 기법입니다. 올바르게 사용하면 다음을 달성할 수 있습니다.

  • 복잡한 코드를 격리된 상태로 테스트
  • 느리고 신뢰할 수 없는 외부 호출 회피
  • 빠르고 결정적인 테스트 작성
  • 실제 시스템에서는 트리거하기 어려운 엣지 케이스 시뮬레이션

적절한 테스트 더블—스텁, 목, 페이크, 스파이—을 선택함으로써 테스트 스위트를 유지보수하기 쉽고, 표현력이 풍부하며, 신뢰할 수 있게 만들 수 있습니다.

Back to Blog

관련 글

더 보기 »