Test Double ๊ฐ๋ ์ดํดํ๊ธฐ
๊ธฐ๋ณธ ๊ฐ๋
Test Double์ ์ํํธ์จ์ด ํ ์คํธ์์ ์ค์ ๊ฐ์ฒด๋ฅผ ๋์ฒดํ๋ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ์๋ฏธํ๋ค. ์ํ ์ดฌ์์์ ์ํํ ์ฅ๋ฉด์ ๋์ ์ฐ๊ธฐํ๋ ์คํดํธ๋งจ(Stunt Double)์์ ์ ๋ํ ์ฉ์ด์ด๋ค.
์ฌ์ฉ ๋ชฉ์
- ํ ์คํธ ์คํ ์๋ ํฅ์
- ์ธ๋ถ ์์กด์ฑ ์ ๊ฑฐ
- ํน์ ์๋๋ฆฌ์ค ํ ์คํธ ์ฉ์ด์ฑ ํ๋ณด
- ์์ธก ๊ฐ๋ฅํ ํ ์คํธ ํ๊ฒฝ ๊ตฌ์ฑ
Test Double์ ์ข ๋ฅ
1. Dummy Objects
๊ฐ์ฅ ๋จ์ํ ํํ์ Test Double๋ก, ๋จ์ํ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฑ์ฐ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค.
class DummyLogger implements LoggerInterface
{
public function log(string $message): void
{
// ์๋ฌด ๋์๋ ํ์ง ์์
}
}
class UserService
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function doSomething(): void
{
// ๋ก์ง ์ํ
$this->logger->log("์์
์๋ฃ");
}
}
// ํ
์คํธ ์ฝ๋
$dummyLogger = new DummyLogger();
$service = new UserService($dummyLogger);
$service->doSomething(); // ๋ก๊น
์์ด ํ
์คํธ ๊ฐ๋ฅ2. Stub Objects
๋ฏธ๋ฆฌ ์ค๋น๋ ๋ต๋ณ์ ์ ๊ณตํ๋ ๊ฐ์ฒด์ด๋ค.
class StubPaymentGateway implements PaymentGateway
{
public function processPayment(float $amount): bool
{
return true; // ํญ์ ๊ฒฐ์ ์ฑ๊ณต ๋ฐํ
}
public function getBalance(): float
{
return 1000.00; // ํญ์ ๋์ผํ ์์ก ๋ฐํ
}
}
// ํ
์คํธ ์ฝ๋
$stub = new StubPaymentGateway();
$orderService = new OrderService($stub);
$result = $orderService->purchase(500.00);3. Spy Objects
๋ฉ์๋ ํธ์ถ์ ๊ธฐ๋กํ๊ณ ๋์ค์ ๊ฒ์ฆํ ์ ์๋ ๊ฐ์ฒด์ด๋ค.
class SpyEmailSender implements EmailSender
{
private array $sentEmails = [];
public function send(string $to, string $subject, string $body): void
{
$this->sentEmails[] = [
'to' => $to,
'subject' => $subject,
'body' => $body
];
}
public function getSentEmailsCount(): int
{
return count($this->sentEmails);
}
public function getLastEmailSent(): ?array
{
return empty($this->sentEmails) ? null : end($this->sentEmails);
}
}
// ํ
์คํธ ์ฝ๋
$spy = new SpyEmailSender();
$notificationService = new NotificationService($spy);
$notificationService->notifyUser('test@example.com');
assertEquals(1, $spy->getSentEmailsCount());
assertEquals('test@example.com', $spy->getLastEmailSent()['to']);4. Mock Objects
๊ธฐ๋ํ๋ ๋์์ ๋ฏธ๋ฆฌ ํ๋ก๊ทธ๋๋ฐํ๊ณ ๊ฒ์ฆํ๋ ๊ฐ์ฒด์ด๋ค.
// PHPUnit์ ์ฌ์ฉํ Mock ์์
public function testOrderProcessing(): void
{
// Mock ์์ฑ
$mock = $this->createMock(PaymentGateway::class);
// ๊ธฐ๋ ๋์ ์ค์
$mock->expects($this->once())
->method('processPayment')
->with(100.00)
->willReturn(true);
$orderService = new OrderService($mock);
$result = $orderService->processOrder(100.00);
$this->assertTrue($result);
}5. Fake Objects
์ค์ ๊ตฌํ์ ๋จ์ํํ ๊ฐ์ฒด์ด๋ค.
class FakeDatabase implements Database
{
private array $data = [];
public function save(string $key, mixed $value): void
{
$this->data[$key] = $value;
}
public function get(string $key): mixed
{
return $this->data[$key] ?? null;
}
public function delete(string $key): void
{
unset($this->data[$key]);
}
}
// ํ
์คํธ ์ฝ๋
$fake = new FakeDatabase();
$userRepository = new UserRepository($fake);
$userRepository->saveUser($user);์ฌ์ฉ ์๋๋ฆฌ์ค์ Process Flow
flowchart TB A[ํ ์คํธ ์์] --> B{์์กด์ฑ ์ ํ ํ์ } B -->|๋จ์ ํ๋ผ๋ฏธํฐ| C[Dummy] B -->|๊ณ ์ ์๋ต ํ์| D[Stub] B -->|๋์ ๊ฒ์ฆ ํ์| E[Spy] B -->|๊ธฐ๋๊ฐ ๊ฒ์ฆ ํ์| F[Mock] B -->|๊ฐ๋จํ ์ค์ ๊ตฌํ ํ์| G[Fake] C --> H[ํ ์คํธ ์คํ] D --> H E --> H F --> H G --> H H --> I[๊ฒฐ๊ณผ ๊ฒ์ฆ]
๊ตฌํ ๊ฐ์ด๋๋ผ์ธ
1. Test Double ์ ํ ๊ธฐ์ค
- Dummy: ํ๋ผ๋ฏธํฐ๋ง ํ์ํ ๊ฒฝ์ฐ
- Stub: ๋จ์ํ ๋ฐํ๊ฐ์ด ํ์ํ ๊ฒฝ์ฐ
- Spy: ํธ์ถ ์ฌ๋ถ์ ๋ฐฉ๋ฒ ๊ฒ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ
- Mock: ์ ํํ ๋์ ๋ฐฉ์ ๊ฒ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ
- Fake: ์ค์ ์ ์ ์ฌํ ๋์์ด ํ์ํ ๊ฒฝ์ฐ
2. ํจ๊ณผ์ ์ธ ์ฌ์ฉ๋ฒ
// ์๋ชป๋ ์์ - ๊ณผ๋ํ Mock ์ฌ์ฉ
public function testUserRegistration(): void
{
$mockEmail = $this->createMock(EmailSender::class);
$mockLogger = $this->createMock(Logger::class);
$mockCache = $this->createMock(Cache::class);
// ๋๋ฌด ๋ง์ Mock์ ํ
์คํธ๋ฅผ ๋ณต์กํ๊ฒ ๋ง๋ ๋ค
}
// ์ฌ๋ฐ๋ฅธ ์์ - ํ์ํ ๋ถ๋ถ๋ง Mock
public function testUserRegistration(): void
{
$mockEmail = $this->createMock(EmailSender::class);
$logger = new SimpleLogger(); // ์ค์ ๊ตฌํ์ฒด ์ฌ์ฉ
$cache = new ArrayCache(); // ๊ฐ๋จํ Fake ์ฌ์ฉ
}์ฃผ์์ฌํญ
-
๊ณผ๋ํ Test Double ์ฌ์ฉ ํผํ๊ธฐ
- ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ์ค์ ๊ฐ์ฒด๋ก ํ ์คํธํ๋ค
- ์ธ๋ถ ์์กด์ฑ์ด ์๋ ๋ถ๋ถ๋ง Test Double์ ์ฌ์ฉํ๋ค
-
ํ ์คํธ ๊ฐ๋ ์ฑ ์ ์ง
- Test Double์ ๋ชฉ์ ์ ๋ช ํํ ํ๋ค
- ๋ณต์กํ ์ค์ ์ ํฌํผ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ๋ค
-
์ค์ ๋์๊ณผ์ ์ฐจ์ด ์ธ์ง
- Test Double์ ์ค์ ๋์์ ์๋ฒฝํ ๋ชจ๋ฐฉํ ์ ์๋ค
- ์ค์ํ ์๋๋ฆฌ์ค๋ ํตํฉ ํ ์คํธ๋ก ๋ณด์ํ๋ค
Security ๊ณ ๋ ค์ฌํญ
// ์๋ชป๋ ์์ - ๋ฏผ๊ฐํ ์ ๋ณด ๋
ธ์ถ
$stubAuth = new StubAuthenticator();
$stubAuth->setApiKey('actual-production-key'); // ์ค์ ํค ๋
ธ์ถ
// ์ฌ๋ฐ๋ฅธ ์์ - ๊ฐ์ง ๋ฐ์ดํฐ ์ฌ์ฉ
$stubAuth = new StubAuthenticator();
$stubAuth->setApiKey('test-' . uniqid()); // ํ
์คํธ์ฉ ๊ฐ์ง ํคPerformance ๊ณ ๋ ค์ฌํญ
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
public function tearDown(): void
{
// Test Double ์ ๋ฆฌ
Mockery::close();
parent::tearDown();
}- ํ ์คํธ ์คํ ์๋
- ๋ฌด๊ฑฐ์ด ์์กด์ฑ์ ๊ฐ๋ฒผ์ด Test Double๋ก ๋์ฒด
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋์ ๋ฐฐ์ด ๊ธฐ๋ฐ Fake ์ฌ์ฉ
๊ฒฐ๋ก
Test Double์ ํจ๊ณผ์ ์ธ ๋จ์ ํ ์คํธ๋ฅผ ์ํ ํ์ ๋๊ตฌ์ด๋ค. ๋ค์ ์ฌํญ์ ๊ณ ๋ คํ์ฌ ์ฌ์ฉํ๋ค:
- ๋ชฉ์ ์ ๋ง๋ ์ ์ ํ Test Double ์ ํ
- ์ค์ ๊ฐ์ฒด์ Test Double์ ์ ์ ํ ๊ท ํ
- ํ ์คํธ ์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ ๊ณ ๋ ค
- ๋ณด์๊ณผ ์ฑ๋ฅ ์ธก๋ฉด์ ๊ณ ๋ ค์ฌํญ ์ค์
์ด๋ฌํ ๊ฐ์ด๋๋ผ์ธ์ ๋ฐ๋ฅด๋ฉด ๋ ํจ๊ณผ์ ์ด๊ณ ์์ ์ ์ธ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.