Search

Mock과 Stub에 대한 아카이빙 (Test Double)

Mock과 Stub에 대한 아카이빙 (Test Double)

Mock과 Stub에 대한 이해 및 차이점

1. Mock과 Stub의 차이

먼저 간단하게 정의를 정리하고 가겠습니다.
Mock과 Stub은 테스트 과정에서 자주 사용되는 용어이지만, 그 차이를 명확히 이해하는 것이 중요합니다.
Stub: 테스트 중 특정 입력에 대해 미리 정해진 출력을 반환하는 객체입니다. Stub은 주로 상태 검증에 사용됩니다.
상태 검증: 반환된 값에 초점을 맞추어 검증.
Mock: 특정 동작을 검증하기 위해 사용하는 객체입니다. Mock은 주로 행위 검증에 사용됩니다.
행위 검증: 특정 행동이 의도대로 이루어졌는지를 검증.
1-1. 값 검증 (Stub처럼 사용된 예제)
from unittest.mock import Mock # Mock 객체 생성 mailSendClient = Mock() # Stubbing 설정: send 메서드가 호출되면 True를 반환 mailSendClient.send.return_value = True # 테스트 중 값 검증 result = mailSendClient.send('test@example.com', 'Hello') assert result == True print("값 검증 완료: Stub처럼 사용된 경우")
Python
복사
1-2. 행위 검증 (Mock처럼 사용된 예제)
from unittest.mock import Mock # Mock 객체 생성 mailSendClient = Mock() # Stubbing 설정: send 메서드가 호출되면 True를 반환 mailSendClient.send.return_value = True # 메서드 호출 mailSendClient.send('test@example.com', 'Hello') # 행위 검증: send 메서드가 특정 인수와 함께 호출되었는지 확인 mailSendClient.send.assert_called_with('test@example.com', 'Hello') print("행위 검증 완료: Mock처럼 사용된 경우")
Python
복사
1-3. 상태 검증과 행위 검증을 함께 사용한 예제
from unittest.mock import Mock # Mock 객체 생성 mailSendClient = Mock() # Stubbing 설정: send 메서드가 호출되면 True를 반환 mailSendClient.send.return_value = True # 메서드 호출 result = mailSendClient.send('test@example.com', 'Hello') # 값 검증 (Stub처럼 사용) assert result == True # 행위 검증 (Mock처럼 사용) mailSendClient.send.assert_called_with('test@example.com', 'Hello') print("상태 검증과 행위 검증 완료")
Python
복사
값 검증 (Stub처럼 사용된 예제):
mailSendClient.send가 특정 값을 반환하도록 설정하고, 반환된 값을 검증합니다.
행위 검증 (Mock처럼 사용된 예제):
mailSendClient.send가 특정 인수와 함께 호출되었는지 검증합니다.
상태 검증과 행위 검증을 함께 사용한 예제:
메서드 호출 후 반환된 값을 검증하고, 호출된 행위도 검증합니다.

1차 결론

Stub: 상태 검증에 초점, 미리 준비된 값을 반환.
Mock: 행위 검증에 초점, 특정 동작이 의도된 대로 호출되었는지 검증.

상태 검증과 행위 검증

하지만 상태 검증과 행위 검증에 대해서 잘 이해가 가지 않을 수 있으므로, 더 정리를 해보겠습니다.
테스트에서 상태 검증행위 검증은 서로 다른 접근 방식으로 객체의 동작을 검증하는 방법입니다. 두 방법의 차이를 구체적인 예제와 함께 설명하겠습니다.
상태 검증 (State Verification)
상태 검증은 객체의 메서드가 호출된 후 객체의 상태나 반환된 값이 예상한 대로인지 확인하는 방식입니다.
예시: 은행 계좌 클래스
class BankAccount: def __init__(self, balance=0): self.balance = balance def deposit(self, amount): self.balance += amount def withdraw(self, amount): if amount <= self.balance: self.balance -= amount return True else: return False # 상태 검증 예제 def test_bank_account(): account = BankAccount() # 초기 상태 검증 assert account.balance == 0 # 입금 후 상태 검증 account.deposit(100) assert account.balance == 100 # 출금 후 상태 검증 result = account.withdraw(50) assert result == True assert account.balance == 50 # 출금 실패 상태 검증 result = account.withdraw(100) assert result == False assert account.balance == 50 print("상태 검증 테스트 완료") test_bank_account()
Python
복사
BankAccount 클래스의 메서드를 호출한 후, balance 속성과 메서드 반환 값을 통해 상태를 검증합니다.
행위 검증 (Behavior Verification)
행위 검증은 객체의 특정 메서드가 예상대로 호출되었는지 확인하는 방식입니다.
이 방법은 주로 Mock 객체와 함께 사용됩니다.
예시: 이메일 전송 서비스
from unittest.mock import Mock class EmailService: def __init__(self, mail_client): self.mail_client = mail_client def send_welcome_email(self, email_address): self.mail_client.send(email_address, "Welcome!", "Welcome to our service!") # 행위 검증 예제 def test_email_service(): # Mock 객체 생성 mail_client_mock = Mock() # EmailService 객체 생성 email_service = EmailService(mail_client_mock) # 메서드 호출 email_service.send_welcome_email('test@example.com') # 행위 검증: send 메서드가 특정 인수와 함께 호출되었는지 확인 mail_client_mock.send.assert_called_with('test@example.com', "Welcome!", "Welcome to our service!") print("행위 검증 테스트 완료") test_email_service()
Python
복사
mail_client_mock 객체는 Mock 객체로, EmailService 클래스의 send_welcome_email 메서드가 mail_client_mocksend 메서드를 예상한 대로 호출하는지 검증합니다.

차이점 정리

상태 검증:
메서드 호출 후 객체의 상태나 반환 값을 검증.
상태 검증은 객체의 내부 상태
예제: BankAccount 클래스의 balance 속성을 검증.
내부 프로퍼티 확인
행위 검증:
메서드가 예상한 인수와 함께 호출되었는지 검증.
객체의 상호작용을 확인
예제: EmailService 클래스의 send_welcome_email 메서드가 Mock 객체의 send 메서드를 예상한 대로 호출하는지 검증.

2. Mock 객체에서 Stubbing을 정의할 경우

다시 돌아와서, 행위 검증과 상태 검증을 기반으로 mock, stub이 함께 쓰였을 때의 관계를 추가 설명하겠습니다.
Mock 객체에서 특정 값을 반환하도록 설정(Stubbing)할 수 있지만, 애초에 행위 검증을 위해 Mock 객체를 만들었기 때문에 여전히 Mock 객체로 봅니다.
검증 단계에서 값 위주로 검증하면 Stub 객체처럼 사용된 것이고, 행동을 검증하면 Mock 객체로 사용된 것입니다.
분류
결과
값 위주 검증
Stub 객체
행동 위주 검증
Mock 객체
아래는 Python으로 변환한 예제 코드입니다.
mailSendClient 객체의 send 메서드에 대해 Stubbing을 설정하고, 특정 값을 검증합니다.
from unittest.mock import Mock, call # Mock 객체 생성 mailSendClient = Mock() # Stubbing 설정 mailSendClient.send.return_value = True # 검증 assert mailSendClient.send(...) == True # 행위 검증 mailSendHistoryRepository.save.assert_called_once_with(any(MailSendHistory))
Python
복사
when(mailSendClient.send(...)).thenReturn(true);
특정 값을 반환하도록 설정한 것으로 Stubbing입니다.
verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class));
save 메서드의 행위를 중심으로 검증한 것으로 Mocking입니다.

3. Mock과 Stub의 검증 방식

MockingStubbing은 사용 방법에 대한 개념입니다.
Mock 객체를 Stubbing하여 값 검증에만 쓰면 사실 Stub 객체처럼 사용하여 상태 검증을 한 것,
행위 검증을 한다면 Mocking했다고 할 수 있습니다.
즉, 객체를 생성해서 어떻게 검증을 하고 있는지까지 확인해보아야 합니다.
MockistClassicist는 테스트 철학에 대한 개념으로, 테스트 시 실제 객체를 사용할 것인지 Mock/Stub 객체를 사용할 것인지에 대한 논쟁입니다.
Mocking/Stubbing은 도구의 사용 방식이고, Mockist/Classicist는 테스트 전략에 관한 철학입니다.
이 둘은 다른 레벨에서 이해해야 합니다.

Test Double의 종류와 차이점

테스트 더블은 테스트하고자 하는 대상만 독립적으로 테스트할 수 있도록 별개로 구현한, 실제 객체보다 단순한 객체를 의미합니다.
테스트 대상을 SUT(System Under Test)라고 하고, SUT가 의존하고 있는 구성 요소를 DOC(Depended-on Component)라고 합니다. 테스트 더블은 이 DOC와 동일한 API를 제공합니다.
테스트에서 수행하는 역할에 따라 여러 종류로 나뉘며, 아래와 같은 종류가 있습니다.
Dummy: 아무런 동작도 하지 않습니다. 인스턴스화된 객체만 필요하고, 기능까지는 필요하지 않은 경우 Dummy를 사용합니다. 주로 파라미터로 전달되기 위해 사용됩니다.
Fake: 실제 동작하는 구현을 가지고 있지만, 프로덕션에서는 사용하기 적합하지 않은 객체입니다 (예: 메모리 DB).
Stub: Dummy가 마치 실제로 동작하는 것처럼 보이게 만든 객체입니다. 미리 반환할 데이터가 정의되어 있으며, 메소드를 호출했을 경우 그것을 그대로 반환하는 역할만 수행합니다.
Spy: 실제 객체를 부분적으로 Stubbing하면서 동시에 약간의 정보를 기록하는 객체입니다. 기록하는 정보에는 메소드 호출 여부, 메소드 호출 횟수 등이 포함됩니다.
Mock: 호출에 대한 기대를 명세할 수 있고, 그 명세 내용에 따라 동작하도록 프로그래밍된 객체입니다.
테스트 더블을 공부하면서 가장 혼란스러웠던 부분은 같은 개념을 많은 곳에서 다르게 혹은 모호하게 설명하고 있다는 점입니다.
즉, 테스트 더블의 경계는 명확하지 않으므로 스펙트럼으로 바라보는 것이 바람직합니다.

차이점 요약

기존 객체(Stub 등): 상태 검증 중심.
Mock 객체: 행동 검증 중심.
기존 객체:
객체 선언, 설정, 상태 검증.
Mock 객체:
객체 선언, 기대 동작 선언, 행동 검증.
Mock과 Stub의 차이점은 검증 방식(상태 검증 vs. 행동 검증)에 있으며, 이를 고려하여 테스트 전략을 세우는 것이 중요합니다.