final 메소드를 stub 해야 하는 상황이 생긴다면 , 무언가 설계가 잘못된 것이다.
- final 메서드는 오버라이딩이 불가능하고 Mocking도 어렵기 때문에 직접 호출 해야만 한다.
- 즉, 테스트 시 해당 메서드의 실제 동작을 실행해야 하므로, 의존성이 강해짐.
- 이 경우, 테스트 코드의 독립성이 깨지며 Mocking할 수 없어서 결합도가 높아짐.
final 메소드를 stub 해야하는 상황이 생긴다면 , 무언가 설계가 잘못된 것이다.
final 메소드는 변경하지 않겠다는 것인데 , stub 하는 상황이 생긴다는 것은 메소드를 overwrite 하겠다는 것이기 때문이다.
final 메소드를 별도의 클래스로 만들어
final 메소드에 걸린 의존성을 약하게 하는 방법을 생각해봐야 한다.
Stub 이란
- 테스트 코드에서 특정 메서드의 동작을 가짜로 구현하는 기법
- 주로 Mocking 라이브러리를 사용하여 특정 메서드가 항상 동일한 값을 반환하도록 설정.
fianl 메서드는 기본적으로 Mocking이 불가능하다. 이유는 Mocking 라이브러리가 런타임에 프록시 객체를 생성하는 방식으로 동작하기 때문이다.
final 메소드는 컴파일 타임에 이미 결정되며, 오버라이딩이 불가능하도록 JVM의 Method Area에 저장되기 때문이다.
해결 법 : final 메서드를 별도의 클래스로 분리하면 , 인터페이스를 통해 의존성을 낮출 수 있다.
- PaymentValidator 인터페이스를 만들어서 DI(Dependency Injection) 적용
- Mocking 할 때는 Stub(가짜 구현체)를 주입하여 테스트가 쉬워짐.
🔹 (1) 인터페이스 정의
public interface PaymentValidator {
boolean isValidPayment(String cardNumber);
}
🔹 (2) final 메서드를 별도의 클래스로 분리
public class DefaultPaymentValidator implements PaymentValidator {
@Override
public final boolean isValidPayment(String cardNumber) { // 여전히 final 사용
return cardNumber.startsWith("1234");
}
}
🔹 (3) PaymentService에서 인터페이스 주입 (DI 적용)
public class PaymentService {
private final PaymentValidator paymentValidator;
public PaymentService(PaymentValidator paymentValidator) {
this.paymentValidator = paymentValidator;
}
public void processPayment(String cardNumber) {
if (paymentValidator.isValidPayment(cardNumber)) {
System.out.println("Payment processed!");
} else {
System.out.println("Invalid card number.");
}
}
}
✔ PaymentService는 final 메서드를 직접 호출하지 않고, 인터페이스를 통해 호출
✔ 의존성이 약해지면서(Mock 가능) 테스트가 쉬워짐
🔹 (4) 테스트 코드에서 Mocking (Stub 주입)
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
class PaymentServiceTest {
@Test
void testProcessPayment() {
// 가짜 PaymentValidator (Stub) 생성
PaymentValidator stubValidator = mock(PaymentValidator.class);
when(stubValidator.isValidPayment("123456")).thenReturn(true);
// 가짜 Validator를 PaymentService에 주입
PaymentService paymentService = new PaymentService(stubValidator);
paymentService.processPayment("123456");
// 출력: Payment processed!
}
}
'클린 코드' 카테고리의 다른 글
static 메서드가 Mocking이 불가능한 이유? (0) | 2025.03.04 |
---|---|
객체의 결합도 의미와 단계 정리 (0) | 2025.02.09 |