서론
저를 포함한 많은 개발하시는 분들이 테스트 코드 작성에 부담감을 느끼는 것 같습니다.
그 중 한가지 이유로 여러 종류의 테스트(통합 테스트, 컨트롤러, 서비스, 도메인 등)를 모두 작성해야 한다는 부담감 때문입니다.
이 글에서는 제가 테스트 코드를 작성하면서 오해했던 부분들과 공부를 통해 깨달은 점들을 공유하고자 합니다.
또한, 효과적인 테스트 작성을 위한 우선순위에 대해서도 작성해보았습니다. (부담감이 줄어들길 바라면서..!)
올바르지 않은 정보가 있을 수 있습니다. 댓글로 알려주시면 감사하겠습니다 🙇♀️
테스트에 대한 오해와 깨달음
- 테스트 기법에 대해 과도하게 집중했습니다.
처음에는 다양한 테스트 기법을 배우는 데 집중했습니다.
하지만 테스트를 작성하면서 깨달은 점은, 가장 중요한 것은 프로덕션 코드의 정확성을 검증하는 것이지 어렵고 복잡한 기법이 아니라는 점입니다. - POJO 테스트의 중요성을 간과했습니다.
처음에는 POJO(Plain Old Java Object) 테스트의 중요성을 간과했습니다.
하지만 POJO 테스트, 특히 도메인 테스트가 비즈니스 로직의 핵심을 검증하는 가장 중요한 부분이라는 것을 깨달았습니다. - 통합 테스트에 대해 오해했습니다,,
처음에는 통합 테스트가 무겁고 비효율적이라고 생각해 Slice 테스트로 대체하려 했습니다.
하지만 통합 테스트가 전체 시스템의 동작을 검증하는 데 중요하다는 것을 알게 되었습니다. - 과도한 모킹(Mocking)을 수행했습니다.
의존성을 최대한 분리하기 위해 과도하게 모킹을 사용했습니다.
그러나 이로 인해 테스트의 복잡성만 증가 되고, 실제 프로덕션 환경과의 괴리를 만든다는 것을 깨달았습니다.
@Test
void testProcessOrderWithExcessiveMocking() {
// given
OrderRepository mockOrderRepo = mock(OrderRepository.class);
PaymentService mockPaymentService = mock(PaymentService.class);
Order order = new Order(1L, "Product A", 2, 100.0);
given(mockOrderRepo.findById(1L)).willReturn(Optional.of(order));
given(mockPaymentService.processPayment(any())).willReturn(true);
OrderService orderService = new OrderService(mockOrderRepo, mockPaymentService);
// when
boolean result = orderService.processOrder(1L);
// then
then(result).isTrue();
then(mockPaymentService).should().processPayment(any());
}
mocking은 외부 시스템의 의존성이 있는 서비스에 사용하는 것을 추천드립니다.
테스트 우선순위
위와 같은 생각을 하면서 어떤 테스트에 더 집중하고, 덜 집중하면 좋을지 생각해보았습니다.
물론 모든 테스트는 중요합니다. 어떤 테스트에 시간을 더 쏟으면 좋을지로 이해해주시면 감사하겠습니다.
1. 도메인(POJO) 테스트
- 비즈니스 로직의 정확성을 보장하기 때문에 가장 중요합니다.
- 외부 의존성 없이 순수한 도메인 로직만을 테스트합니다.
- 테스트 실행 속도가 빠릅니다.
public class FoodTest {
@Test
public void 음식_생성_성공() {
// when
Food pizza = new Food("피자", 15000);
// then
then(pizza.getName()).isEqualTo("피자");
then(pizza.getPrice()).isEqualTo(15000);
}
@Test
public void 가격_인상_성공() {
// given
Food burger = new Food("햄버거", 8000);
// when
burger.increasePrice(1000);
// then
then(burger.getPrice()).isEqualTo(9000);
}
}
2. 통합 테스트
- 실제 운영 환경과 유사한 환경에서 테스트할 수 있어 중요합니다.
- end-to-end 동작을 확인합니다.
public class FoodApiTest extends IntegrationTestSupport {
@Test
public void 음식등록_테스트() throws Exception {
final String requestBody = readJson("/food-registration.json");
this.mockMvc.perform(
post("/foods")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody)
)
.andExpect(status().isCreated());
}
}
3. 선택적인 추가 테스트
상황에 따라 필요한 경우 추가하면 좋을 것 같습니다.
a) Slice 테스트 (예: @WebMvcTest, @DataJpaTest)
- 애플리케이션의 특정 "슬라이스" 또는 레이어만을 테스트합니다.
- @WebMvcTest : 웹 레이어(컨트롤러)만을 테스트
- @DataJpaTest : JPA 컴포넌트(리포지토리)만을 테스트
@WebMvcTest(FoodController.class)
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class FoodControllerTest {
private final MockMvc mockMvc;
@MockBean
private FoodService foodService;
FoodControllerTest(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
@Test
void 음식_조회_성공() throws Exception {
// given
given(foodService.getFood(1L)).willReturn(new Food(1L, "피자", 15000));
// when
ResultActions result = mockMvc.perform(get("/foods/1"));
// then
then(result)
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("피자"))
.andExpect(jsonPath("$.price").value(15000))
}
}
b) 서비스 Mock 테스트
- 서비스 레이어의 로직을 단위 테스트 합니다.
- 서비스가 의존하는 다른 컴포넌트(예: 리포지토리)를 모의(mock) 객체로 대체합니다.
- Mockito 프레임워크를 주로 사용합니다.
pojo라고 볼수도 있지만, 의존하는 컴포넌트를 mocking하므로 pojo와 분리해서 작성했습니다.
@ExtendWith(MockitoExtension.class)
class FoodServiceTest {
@Mock
private FoodRepository foodRepository;
@InjectMocks
private FoodService foodService;
@Test
void 음식_조회_성공() {
// given
Food expectedFood = new Food(1L, "치킨", 18000);
given(foodRepository.findById(1L)).willReturn(Optional.of(expectedFood));
// when
Food actualFood = foodService.getFood(1L);
// then
then(actualFood).isEqualTo(expectedFood);
verify(foodRepository).findById(1L);
}
}
결론
테스트 작성의 핵심은 아래 두 가지에 집중하는 것입니다.
1, 비즈니스 로직이 정확하게 동작하는지 검증 (POJO)
2. 시스템 전체가 end-to-end로 올바르게 동작하는지 검증(통합 테스트)
이 두가지를 중심으로 테스트를 작성하고, 필요에 따라 추가적으로 작성해보는 것을 제안드립니다.
테스트 코드가 많을 수록 좋다고 생각할 수 있지만(다다익선?) 중복된 검증이나 과도한 테스트는 오히려 유지 보수 비용을 증가시킬 수 있다고 생각합니다.
따라서 테스트의 양보다는 질과 효율성에 초점을 맞추는 것이 중요합니다.
이 글이 저처럼 테스트 코드에 막연한 부담감과 두려움을 가진 분들에게 도움이 되었으면 좋겠습니다.
결국,, 테스트 코드 작성은 선택이 아닌 필수이기 때문입니다. 😂
읽어주셔서 감사합니다.
정확하지 않은 부분이 있고 배우는 중이니 잘못된 부분은 알려주시면 감사하겠습니다 !! 🙇♀️
'문제&해결' 카테고리의 다른 글
Redis 도입하기 : CI/CD 파이프라인 수정기 (Blue/Green 무중단 배포) (12) | 2024.10.08 |
---|---|
OAuth 소셜 로그인 안전하게 구현하기 (redirect_uri, redis, token) (1) | 2024.09.21 |
Spring WebSocket 예외 처리 - @MessageExceptionHandler, StompSubProtocolErrorHandler (0) | 2024.06.18 |
Spring WebSocket 애플리케이션에 Spring Security 적용하기(simpUser, Interceptor, handler) (2) | 2024.05.15 |
[Test] @Mock, @InjectMocks 동작 원리 및 주의 사항 (0) | 2024.04.10 |