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 ์‚ฌ์šฉ
}

์ฃผ์˜์‚ฌํ•ญ

  1. ๊ณผ๋„ํ•œ Test Double ์‚ฌ์šฉ ํ”ผํ•˜๊ธฐ

    • ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ์‹ค์ œ ๊ฐ์ฒด๋กœ ํ…Œ์ŠคํŠธํ•œ๋‹ค
    • ์™ธ๋ถ€ ์˜์กด์„ฑ์ด ์žˆ๋Š” ๋ถ€๋ถ„๋งŒ Test Double์„ ์‚ฌ์šฉํ•œ๋‹ค
  2. ํ…Œ์ŠคํŠธ ๊ฐ€๋…์„ฑ ์œ ์ง€

    • Test Double์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•œ๋‹ค
    • ๋ณต์žกํ•œ ์„ค์ •์€ ํ—ฌํผ ๋ฉ”์†Œ๋“œ๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค
  3. ์‹ค์ œ ๋™์ž‘๊ณผ์˜ ์ฐจ์ด ์ธ์ง€

    • Test Double์€ ์‹ค์ œ ๋™์ž‘์„ ์™„๋ฒฝํžˆ ๋ชจ๋ฐฉํ•  ์ˆ˜ ์—†๋‹ค
    • ์ค‘์š”ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋กœ ๋ณด์™„ํ•œ๋‹ค

Security ๊ณ ๋ ค์‚ฌํ•ญ

// ์ž˜๋ชป๋œ ์˜ˆ์‹œ - ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋…ธ์ถœ
$stubAuth = new StubAuthenticator();
$stubAuth->setApiKey('actual-production-key'); // ์‹ค์ œ ํ‚ค ๋…ธ์ถœ
 
// ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ - ๊ฐ€์งœ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
$stubAuth = new StubAuthenticator();
$stubAuth->setApiKey('test-' . uniqid()); // ํ…Œ์ŠคํŠธ์šฉ ๊ฐ€์งœ ํ‚ค

Performance ๊ณ ๋ ค์‚ฌํ•ญ

  1. ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ
public function tearDown(): void 
{
    // Test Double ์ •๋ฆฌ
    Mockery::close();
    parent::tearDown();
}
  1. ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์†๋„
  • ๋ฌด๊ฑฐ์šด ์˜์กด์„ฑ์€ ๊ฐ€๋ฒผ์šด Test Double๋กœ ๋Œ€์ฒด
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋Œ€์‹  ๋ฐฐ์—ด ๊ธฐ๋ฐ˜ Fake ์‚ฌ์šฉ

๊ฒฐ๋ก 

Test Double์€ ํšจ๊ณผ์ ์ธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ•„์ˆ˜ ๋„๊ตฌ์ด๋‹ค. ๋‹ค์Œ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค:

  1. ๋ชฉ์ ์— ๋งž๋Š” ์ ์ ˆํ•œ Test Double ์„ ํƒ
  2. ์‹ค์ œ ๊ฐ์ฒด์™€ Test Double์˜ ์ ์ ˆํ•œ ๊ท ํ˜•
  3. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ ๊ณ ๋ ค
  4. ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ ์ธก๋ฉด์˜ ๊ณ ๋ ค์‚ฌํ•ญ ์ค€์ˆ˜

์ด๋Ÿฌํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋”ฐ๋ฅด๋ฉด ๋” ํšจ๊ณผ์ ์ด๊ณ  ์•ˆ์ •์ ์ธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.