본격적으로 스프링 테스트 코드에 관해 공부하기 전에 그에 관한 내용들을 정리해보는 시간을 가져보았다.
스프링 애플리케이션을 개발하다 보면, 테스트는 코드 품질을 유지하고 애플리케이션의 안정성을 보장하는 필수적인 과정입니다. 이 글에서는 스프링에서 자주 사용되는 테스트 방법들과 AOP, 예외 처리 등의 기술을 다루며, 각 기술을 효과적으로 활용할 수 있는 팁과 주의할 점을 정리해보았다.
단위 테스트란?
단위 테스트는 애플리케이션의 작은 단위, 즉 클래스나 메서드가 올바르게 동작하는지 확인하는 테스트입니다. Spring에서는 @WebMvcTest
나 @MockBean
을 사용하여 컨트롤러나 서비스 로직을 손쉽게 단위 테스트할 수 있습니다.
@WebMvcTest(AuthController.class)
class AuthControllerTest {
@MockBean
private AuthService authService;
@Test
void loginTest() throws Exception {
when(authService.login(any())).thenReturn(new JwtToken());
mockMvc.perform(post("/auth/login")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
팁:
단위 테스트는 다른 컴포넌트와 독립적으로 테스트해야 합니다. 이를 위해 @MockBean
을 사용하여 필요한 의존성을 모의(mock)할 수 있습니다. 테스트에서 실제 데이터베이스나 외부 API를 호출하는 것은 지양해야 합니다.
중요한 팁:
테스트 메서드의 가독성을 높이기 위해 given-when-then 패턴을 사용해보세요. 이는 테스트 조건, 실행, 결과 검증을 명확하게 구분하는 방식입니다.
통합 테스트란?
통합 테스트는 여러 레이어(Controller, Service, Repository 등)가 상호작용할 때 올바르게 동작하는지 확인하는 방법입니다. @SpringBootTest
와 MockMvc
를 사용하여 애플리케이션 전체를 실행하고, 실제 시나리오에 가까운 테스트를 할 수 있습니다.
@SpringBootTest
@AutoConfigureMockMvc
class AuthIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void testSignup() throws Exception {
mockMvc.perform(post("/auth/signup")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"test\",\"password\":\"1234\"}"))
.andExpect(status().isOk());
}
}
유의할 점:
통합 테스트는 애플리케이션의 실제 환경과 매우 유사하게 실행되므로 시간이 더 오래 걸릴 수 있습니다. 따라서 필요에 따라 꼭 필요한 통합 테스트만 작성하는 것이 좋습니다.
팁:
테스트에서 환경 변수를 관리하는 방법도 중요합니다. 통합 테스트는 종종 여러 환경에서 실행되므로, @TestPropertySource
나 별도의 테스트 설정 파일을 사용해 환경별로 테스트를 수행할 수 있습니다.
컨트롤러 테스트란?
컨트롤러는 HTTP 요청을 처리하는 역할을 하므로, 이를 테스트할 때는 MockMvc
를 사용하여 요청을 모의하고, 그에 대한 응답이 예상대로 나오는지 확인합니다.
@WebMvcTest(AuthController.class)
class AuthControllerTest {
@Test
void testLogin() throws Exception {
mockMvc.perform(post("/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"test\",\"password\":\"1234\"}"))
.andExpect(status().isOk());
}
}
팁:
테스트 코드에서 MockMvc는 매우 강력한 도구입니다. 이를 활용하여 다양한 요청을 모의할 수 있고, 상태 코드, 응답 내용 등을 쉽게 검증할 수 있습니다.
API는 외부 클라이언트와의 통신에서 오류가 발생할 수 있습니다. 이때 적절한 예외처리를 통해 사용자에게 명확한 에러 메시지를 전달하는 것이 중요합니다. Spring에서는 @ExceptionHandler
나 @ControllerAdvice
로 예외를 처리할 수 있습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
유의할 점:
예외 처리 시, 지나치게 포괄적인 예외를 잡지 않도록 주의해야 합니다. 이는 디버깅을 어렵게 만들 수 있습니다.
팁:
예외 메시지는 사용자에게 친절하고 명확하게 전달해야 합니다. 기술적인 메시지보다는 사용자 친화적인 메시지를 제공해야 한다.
글로벌 예외처리는 모든 컨트롤러에서 발생하는 예외를 하나의 클래스에서 처리할 수 있게 해줍니다. @ControllerAdvice
를 통해 중복된 예외 처리 로직을 제거하고, 일관된 방식으로 예외를 처리할 수 있습니다.
팁:
글로벌 예외처리는 여러 컨트롤러에서 발생하는 공통된 예외를 처리할 때 유용합니다. 다양한 예외에 대해 미리 예외 처리 방식을 정의해두면 유지 보수가 쉬워집니다.
다양한 언어로 사용자에게 에러 메시지를 제공하기 위해 MessageSource
를 활용하여 다국어를 지원할 수 있습니다. 이를 통해 에러 메시지를 일관되게 관리하고, 애플리케이션의 유연성을 높일 수 있습니다.
@Autowired
private MessageSource messageSource;
public String getErrorMessage(String code, Locale locale) {
return messageSource.getMessage(code, null, locale);
}
팁:
에러 메시지를 관리할 때는 코드로만 제공하지 말고, 명확한 템플릿을 사용하여 에러 메시지를 쉽게 관리할 수 있도록 설정 파일로 분리하는 것이 좋습니다.
스프링에서 단위 테스트와 통합 테스트, 그리고 AOP와 예외 처리까지 다양한 방법들을 살펴보았습니다. 테스트는 애플리케이션의 안정성을 보장하는 가장 중요한 요소 중 하나입니다. 잘 작성된 테스트는 코드의 버그를 미리 잡아내고, 빠른 피드백을 통해 개발 속도를 높이는 데 큰 기여를 합니다.
테스트는 단순히 작성해야 하는 것이 아니라, 더 좋은 코드를 작성하기 위한 도구입니다. 하지만 테스트를 통해 더 깨끗하고 유지 보수하기 쉬운 코드를 작성할 수 있음으로 자신의 코드를 발전시킬 수 있습니다.