TDD에 대해 공부하던 중 Stub 과 Mock의 개념에 대해 인지하게되었다.
간략히 찾아보았을때
Stub은 테스트 중 필요한 외부 의존성을 대체하고,
Mock은 테스트 중 객체간의 상호작용을 확인하거나 제어한다.
음.....
두가지가 어떻게 쓰이고 어떤 역할을 하는지
정확한 개념을 알아보고자 찾아보게되었다.
위 개념을 이해하기 위해선 Test Double에 대한 이해가 필요하다 !
테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체
Dummy, Stub, Spy, Mock, Fake
만약 PaymentCallbackImpl의 pay 메소드가 실행되면 실제로 결제된다는 아래의 가상의 상황에서 각각의 test double을 이해해보자 !public static void main(String[] args) { // 총 금액 int totalAmount = 100; // PaymentService에서 실제 동작하게끔하기 PaymentServiceImpl paymentService = new PaymentServiceImpl(); // PaymentCallback 객체 생성 PaymentCallback callback = new PaymentCallbackImpl(); // 실제 주문 처리 paymentService.processPayment(totalAmount, callback); }public interface PaymentService { void processPayment(int amount, PaymentCallback callback); }public class PaymentServiceImpl implements PaymentService { @Override public void processPayment (int amount, PaymentCallback callback) { callback.pay(amount); } }public interface PaymentCallback { void pay(int amount); }public class PaymentCallbackImpl implements PaymentCallback { @Override public void pay(int amount){ System.out.printf("실제로 %d 원이 결제됩니다", amount); } }
아무런 동작도 하지 않음
인터페이스를 따르기 위해 매개변수의 자리를 차지하기 위해 사용
주로 파라미터로 전달되기 위함@Test public void payDummyTest() { int totalAmount = 100; PaymentServiceImpl paymentService = new PaymentServiceImpl(); // 람다식을 이용한 Dummy 객체 생성 PaymentCallback callback = amount -> { // 이 Dummy 객체는 아무 동작도 하지 않습니다. }; paymentService.processPayment(totalAmount, callback); }위와같은 Dummy 객체로 실제 결제가 동작하는 부분을 대신 끼워 맞춰 동작을 테스트 한다 !
dummy에 대해 이해해보고자 예시를 작성해봤는데 들어맞는지는 모르겠다..
특정 메소드가 호출될 때 고정된 값을 반환하도록 설정하는 객체
외부 의존성을 대체하여 테스트를 간단히 만들거나
특정 조건에 따른 다양한 동작을 테스트하는데 사용private class PaymentServiceStub implements PaymentService { @Override public void processPayment(int amount, PaymentCallback callback) { // 테스트 시나리오에 따라 성공 또는 실패를 시뮬레이션 if (amount >= 0) { callback.pay(amount); } else { throw new RuntimeException("결제 실패: 유효하지 않은 금액"); } } }@Test public void payTest_success() { int totalAmount = 100; // Stub 객체 생성 (람다식 이용) PaymentCallback stubCallback = amount -> { // 결제 성공을 시뮬레이션 System.out.printf("결제 성공: %d 원이 결제됩니다\n", amount); }; PaymentService paymentService = new PaymentServiceStub(); paymentService.processPayment(totalAmount, stubCallback); }Stub에 대해 작성하다보니 Dummy가 실제 동작하는 것 처럼 보이게 만드는것이 아닐까 라는 생각이 들었다.
Spy는 실제 객체를 감시하여 호출된 메서드의 인자나 호출 횟수 등을 기록하는 객체
// PaymentCallback의 pay 메서드가 정확히 한 번 호출되는지 확인하는 Spy 테스트 @Test public void CallPayOnceSpyTest() { int totalAmount = 100; PaymentCallback callback = spy(new PaymentCallbackImpl()); PaymentService paymentService = new PaymentServiceImpl(); paymentService.processPayment(totalAmount, callback); verify(callback, times(1)).pay(totalAmount); // pay 메서드가 정확히 한 번 호출되었는지 검증 }위의 경우는 테스트를 실행해볼경우 실제 결제가 이루어질것이다.
Mock 객체를 생성하는 방법을 통해 실제 결제가 이루어지지않도록 테스트하자 !
특정 행위를 시뮬레이션하고 테스트 중에 특정 조건이나 행위를 검증하는 객체
테스트를 위한 가짜 객체를 생성@Mock PaymentCallback mockCallback; @Test public void testProcessPayment_Success() { int totalAmount = 100; PaymentService paymentService = new PaymentServiceImpl(); paymentService.processPayment(totalAmount, mockCallback); verify(mockCallback).pay(totalAmount); // pay 메서드가 호출되었는지 검증 } }@Mock 어노테이션 사용 혹은
mock(classname.class) 이용하여 Mock 객체를 생성한다 !
실제 동작하는 것처럼 보이지만 실제로 동작하지 않는 가짜 객체
@Test public void testProcessPayment_Success() { int totalAmount = 100; PaymentCallback fakeCallback = new FakePaymentCallbackImpl(); PaymentService paymentService = new PaymentServiceImpl(); paymentService.processPayment(totalAmount, fakeCallback); }?????????? dummy랑 똑같나 ??????????
목적과 동작에 차이가 있다 !
- Dummy는 단순히 값을 전달하기 위해 사용, 아무런 동작도 하지 않음.
- Fake는 실제 동작이나 데이터 저장을 위해 경량화된 형태로 구현되어 실제 객체와 유사한 동작을 제공.