반응형
정답 : https://shoulditestprivatemethods.com/
1. Private 메소드 테스트가 불필요한 이유
1.1 캡슐화 원칙 위배
- Private 메소드는 클래스의 내부 구현 세부사항
- 외부에서 직접 접근하여 테스트하는 것이 캡슐화 원칙에 위배됨
1.2 간접적인 테스트 커버리지
public class OrderService {
public Order processOrder(OrderRequest request) {
validateOrder(request); // private 메소드
return createOrder(request); // private 메소드
}
private void validateOrder(OrderRequest request) {
// 검증 로직
}
}
- Public 메소드 테스트를 통해 자연스럽게 Private 메소드도 테스트가됨
1.3 리팩토링 용이성
- Private 메소드 테스트는 내부 구현에 대한 의존성이 생성됨
- 코드 리팩토링 시 테스트 코드도 함께 수정 필요함
2. Private 메소드 테스트가 필요할 수 있는 경우
2.1 복잡한 비즈니스 로직
public class TaxCalculator {
public BigDecimal calculate(Income income) {
return calculateIncomeTax(income)
.add(calculateLocalTax(income))
.add(calculateSpecialTax(income));
}
private BigDecimal calculateIncomeTax(Income income) {
// 복잡한 소득세 계산 로직
}
}
- 각 세금 계산 로직이 복잡하여 개별 검증이 필요한 경우
2.2 레거시 코드 리팩토링
- 기존 코드의 안전한 리팩토링을 위한 임시적으로 필요함.
- 점진적 개선을 위한 안전장치로 활용
레거시( Legacy ) 코드란?
> 나를 포함한 모든 개발자가 기존에 개발했던 코드
- 다른 사람으로부터 상속 된 소스 코드
- 이전 버전의 소프트웨어에서 상속 된 소스 코드
- 개발자가 변경하기를 두려워하는 코드
- 테스트없는 코드
3. 외부 라이브러리를 활용한 접근 방법
3.1 Lombok을 활용한 접근 - @UtilityClass
@UtilityClass 어노테이션 사용시, 컴파일 시점에서 생성자 Private 으로 만들어 주며, 모든 메서드를 static으로 만들어줌
참고: https://projectlombok.org/features/experimental/UtilityClass
@UtilityClass
public class ValidationUtils {
public static boolean isValidOrder(OrderRequest request) {
return validateItems(request.getItems())
&& validatePayment(request.getPayment());
}
private static boolean validateItems(List<OrderItem> items) {
// 검증 로직
}
}
- 유틸리티성 메소드들을 별도 클래스로 분리
- Static 메소드로 자동 변환되어 테스트시 용이함
3.2 Mockito를 활용한 접근
3.2.1 기본적인 Mock테스트
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private PaymentGateway paymentGateway;
@InjectMocks
private OrderService orderService;
@Test
void processOrder_WithValidPayment_Success() {
// Given
OrderRequest request = createSampleOrder();
when(paymentGateway.process(any())).thenReturn(true);
// When
Order result = orderService.processOrder(request);
// Then
assertThat(result.getStatus()).isEqualTo(OrderStatus.COMPLETED);
}
}
- 외부 의존성을 Mock으로 대체함
- 결과 검증을 통한 간접적인 Private 메소드 테스트
3.3 외부 라이브러리 사용 시 주의해야할 점
3.3.1 과도한 모킹( Mocking ) 지양하기
// 안티패턴
@Test
void excessiveMocking() {
when(service.method1()).thenReturn(value1);
when(service.method2()).thenReturn(value2);
doNothing().when(service).method3();
// ... 계속되는 모킹
}
Mocking이란?
Mocking은 테스트 시, 외부 의존성이나 실제 객체를 가짜 객체(mock)로 대체하여 코드의 특정 부분만을 검증하는 방법이다.
Mockito 같은 라이브러리는 이를 쉽게 구현할 수 있게 도와준다.
3.3.2 상태 검증 우선
@Test
void stateVerificationPreferred() {
// 권장되지 않는 방식
verify(service).privateMethod(any());
// 권장되는 방식
assertThat(result.getStatus()).isEqualTo(EXPECTED_STATUS);
assertThat(result.getValue()).isGreaterThan(MINIMUM_VALUE);
}
결론
1. private 메소드 테스트는 대부분 불필요하며, public API를 통한 간접 테스트가 바람직함.
2. 복잡한 로직은 별도 클래스로 분리하여 테스트 용이성 확보하기
3. 외부 라이브러리는 신중하게 활용하되, 과도한 의존은 피해야함
4. 테스트 어려움은 설계 개선의 기회로 활용하기
반응형
'Spring Boot' 카테고리의 다른 글
| [Spring Boot] 스프링부트에서 WebSocket, STOMP를 이용한 채팅기능 구현하기 (2) - 도메인 모델과 Redis 연동 (3) | 2024.11.29 |
|---|---|
| [Spring Boot] 스프링부트에서 WebSocket, STOMP를 이용한 채팅기능 구현하기 (1) (0) | 2024.11.25 |
| @requiredargsconstructor @autowired 차이 (3) | 2024.10.06 |