Mockery로 모킹과 스터빙
유연한 테스트 대역으로 단위를 격리합니다.
Mockery로 모킹과 스터빙은(는) CoddyKit의 무료 PHP Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PHP Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
전용 모의 객체 라이브러리가 필요한 이유
PHPUnit에는 자체 테스트 대역 API가 포함되어 있지만, 전용 모의 객체 라이브러리는 더 유창하고 표현력 있는 문법과 PHPUnit의 모의 객체에 없는 기능을 제공합니다. 부분 모의 객체, 스파이, 유연한 인수 매칭, 순서가 있는 예상 등이 이에 해당합니다. 협력이 복잡한 코드에서는 이러한 라이브러리를 사용하는 편이 훨씬 읽기 쉬운 경우가 많습니다. 이 강의에서는 테스트 대역의 전체 범위와 각각을 사용할 시점을 다룹니다.
composer require --dev mockery/mockery스텁, 모의 객체, 스파이 비교
정확한 용어를 사용하면 테스트의 혼란을 막을 수 있습니다.
- 스텁 — 미리 정해 둔 값을 반환하며, 상태 즉 결과를 단언합니다.
- 모의 객체 — 호출에 대한 예상이 있으며, 동작 즉 올바르게 호출되었는지를 단언합니다. 예상이 충족되지 않으면 테스트가 실패합니다.
- 스파이 — 호출을 기록하고 사후에 단언할 수 있게 합니다.
일반적인 원칙은 다음과 같습니다. 조회에는 스텁을, 명령에는 모의 객체나 스파이를 우선 사용하십시오.
allows()를 사용하는 스텁
Mockery::mock()와 allows() 또는 shouldReceive()->andReturn()을 사용하여 호출이 발생했는지를 단언하지 않고 미리 정해 둔 반환값을 제공하십시오. 여기서는 게이트웨이 스텁이 알려진 환율을 제공하므로 계산을 독립적으로 테스트할 수 있습니다.
<?php
use Mockery;
use PHPUnit\Framework\TestCase;
final class ConverterTest extends TestCase
{
public function test_converts_using_rate(): void
{
$rates = Mockery::mock(RateGateway::class);
$rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);
$sut = new Converter($rates);
self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));
Mockery::close();
}
}
expects()를 사용하는 모의 객체
상호 작용 자체가 계약인 경우, 예를 들어 “이메일을 정확히 한 번 보내야 한다”와 같은 경우에는 expects() 또는 shouldReceive()->once()를 사용하십시오. 모의 객체 라이브러리는 Mockery::close() 중에 예상을 검증하며, 충족되지 않은 호출이 있으면 테스트가 실패합니다.
<?php
use Mockery;
$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
->send(Mockery::type(Message::class))
->once();
$service = new SignupService($mailer);
$service->register('ada@example.com');
Mockery::close(); // fails here if send() was never called
인수 매처
모의 객체 라이브러리의 매처를 사용하면 필요에 따라 예상 조건을 느슨하게 또는 엄격하게 설정할 수 있습니다.
Mockery::any()— 모든 값입니다.Mockery::type('string')또는 클래스 이름 — 형식을 확인합니다.Mockery::on(fn($a) => ...)— 사용자 지정 조건식입니다.Mockery::capture($var)— 나중에 단언할 수 있도록 인수를 캡처합니다.
<?php
use Mockery;
$repo = Mockery::mock(UserRepo::class);
$repo->expects()
->save(Mockery::on(fn(User $u) => $u->isActive()))
->once()
->andReturnTrue();
반환 순서와 동적 반환값
호출마다 여러 반환값을 차례로 지정하거나, andReturnUsing()을 사용하여 인수로부터 반환값을 계산할 수 있습니다. 이를 통해 재시도 로직, 페이지 매김 또는 상태를 가진 협력 객체를 모델링할 수 있습니다.
<?php
use Mockery;
$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
->twice()
->andThrow(new \RuntimeException('timeout'))
->andReturn('{"ok":true}');
// Or derive the return from the input:
$api->shouldReceive('echo')
->andReturnUsing(fn(string $in) => strtoupper($in));
스파이: 사후에 단언하기
스파이는 순서를 뒤집습니다. 먼저 동작을 실행한 다음 단언합니다. Mockery::spy()가 호출을 기록하고, 이후 shouldHaveReceived()로 호출을 조회합니다. 사전에 설정한 예상으로 준비 단계가 복잡해지는 것을 원하지 않을 때 스파이를 사용하면 준비-실행-단언 구조를 깔끔하게 유지할 수 있습니다.
<?php
use Mockery;
$logger = Mockery::spy(Logger::class);
$service = new PaymentService($logger);
$service->charge(500);
// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
->with(Mockery::pattern('/charged 500/'))
->once();
Mockery::close();
부분 모의 객체
실제 객체를 사용하되 메서드 하나만 재정의하고 싶은 경우가 있습니다. 이것이 부분 모의 객체입니다. makePartial()은 예상이 설정된 메서드를 제외하고 실제 메서드를 호출합니다. 부분 모의 객체에 지나치게 의존하면 대개 클래스가 너무 많은 일을 한다는 신호이므로 신중하게 사용하십시오.
<?php
use Mockery;
$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);
// real summarize() runs, using the stubbed data
$summary = $report->summarize();
소유하지 않은 것은 모의 객체로 만들지 마십시오
핵심적인 테스트 원칙은 타사 클래스를 직접 모의 객체로 만들지 않는 것입니다. 타사 클래스의 API는 변경될 수 있으며, 그러면 모의 객체가 실제 동작과 조용히 달라질 수 있습니다. 대신 직접 정의한 인터페이스 뒤에 타사 클래스를 감싸고 그 인터페이스를 모의 객체로 만드십시오. 그러면 모의 객체는 여러분의 계약을 검증하고, 어댑터 통합 테스트는 실제 연결을 검증합니다.
<?php
interface PaymentGateway { // you own this
public function charge(int $cents, string $token): string;
}
final class StripeGateway implements PaymentGateway {
public function __construct(private \Stripe\StripeClient $client) {}
public function charge(int $cents, string $token): string {
return $this->client->paymentIntents->create([/* ... */])->id;
}
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.
항상 종료하고 호출 횟수를 검증하기
운영상 반드시 지켜야 할 두 가지가 있습니다.
tearDown()에서Mockery::close()를 호출하거나MockeryPHPUnitIntegration특성을 사용하여 예상이 실제로 검증되고 전역 상태가 정리되도록 하십시오.- 명시적인 호출 횟수인
once(),times(n),never()를 사용하십시오. 모호한 모의 객체는 버그를 통과시킵니다.
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
final class OrderServiceTest extends TestCase
{
use MockeryPHPUnitIntegration; // auto-calls Mockery::close()
public function test_does_not_refund_paid_orders(): void
{
$gw = \Mockery::mock(PaymentGateway::class);
$gw->shouldReceive('refund')->never();
// ... exercise SUT ...
}
}
순서가 있는 예상
호출 순서가 계약의 일부인 경우가 있습니다. 예를 들어 commit()보다 먼저 beginTransaction()을 호출해야 합니다. ordered()는 순서를 강제하며 호출이 잘못된 순서로 들어오면 테스트를 실패시킵니다. 순서가 실제로 중요할 때만 사용하십시오. 순서를 과도하게 명시하면 테스트가 취약해집니다.
<?php
use Mockery;
$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();
$service = new TransferService($tx);
$service->run();
Mockery::close(); // fails if commit() happened before begin()
빠른 확인
조회와 명령에는 각각 스텁과 모의 객체 중 무엇을 사용해야 합니까?
정리
모의 객체 라이브러리의 테스트 대역을 익혔습니다.
- 조회에는 스텁(
allows)을, 명령에는 모의 객체(expects)를, 사후 단언에는 스파이를 사용합니다. - 인수 매처(
type,on,capture)로 엄격함의 정도를 조정합니다. - 반환 순서와
andReturnUsing으로 상태를 가진 협력 객체와 동적 협력 객체를 모델링합니다. - 부분 모의 객체는 메서드 하나를 재정의하며 신중하게 사용해야 합니다.
- 소유하지 않은 것은 모의 객체로 만들지 말고, 타사 기능을 여러분의 인터페이스 뒤에 감싸십시오.
- 항상
Mockery::close()를 호출하고 명시적인 호출 횟수를 단언하십시오.
다음 주제는 통합 테스트와 기능 테스트입니다.
자주 묻는 질문
“Mockery로 모킹과 스터빙” 강의는 무료인가요?
네 — “Mockery로 모킹과 스터빙” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PHP Academy 강의 전체를 잠금 해제할 수 있습니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“Mockery로 모킹과 스터빙”에서 뭘 배우나요?
유연한 테스트 대역으로 단위를 격리합니다. 브라우저에서 직접 실행하는 실습 코드로 PHP Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
PHP Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 PHP Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“Mockery로 모킹과 스터빙” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 PHP Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 PHP Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 테스트 주도 개발 워크플로
- Mockery로 모킹과 스터빙
- 통합 테스트와 기능 테스트
- Infection으로 변이 테스트