아래는 **2장. 단위 테스트 작성**에 대한 글입니다. 이 장은 스프링 부트에서 단위 테스트의 개념을 설명하고, 실습 가능한 예제와 함께 구체적인 작성 방법을 다룹니다. 초보자와 중급 개발자 모두 이해할 수 있도록 단계적으로 접근했습니다. --- ## 2장. 단위 테스트 작성 단위 테스트는 애플리케이션의 개별 구성 요소가 독립적으로 올바르게 동작하는지 확인하는 과정입니다. 스프링 부트에서는 단위 테스트를 통해 컨트롤러, 서비스, 리포지토리와 같은 각 계층의 로직을 검증할 수 있습니다. 이 장에서는 단위 테스트의 정의와 중요성, 스프링 부트에서의 설정 방법, 그리고 실습 예제를 다룹니다. ### 2.1 단위 테스트란 무엇인가 단위 테스트(Unit Test)는 코드의 가장 작은 단위—보통 메서드나 클래스—를 독립적으로 테스트하는 것을 의미합니다. 외부 의존성(데이터베이스, 외부 API 등)에 의존하지 않고, 해당 단위가 기대한 대로 동작하는지 확인하는 데 초점이 맞춰져 있습니다. 단위 테스트의 주요 장점은 다음과 같습니다: - **빠른 피드백**: 실행 속도가 빠르며, 문제를 조기에 발견할 수 있습니다. - **리팩토링 안전성**: 코드 변경 시 기존 기능이 깨지지 않음을 보장합니다. - **문서화**: 테스트 코드는 코드의 사용법과 의도를 보여주는 살아있는 문서 역할을 합니다. 스프링 부트에서는 단위 테스트를 작성할 때 의존성 주입이나 스프링 컨텍스트를 최소화하고, 필요 시 모킹(mock)을 활용해 독립성을 유지합니다. ### 2.2 스프링 부트에서 단위 테스트 설정 스프링 부트에서 단위 테스트를 시작하려면 기본적으로 `spring-boot-starter-test` 의존성이 필요합니다(1장에서 설정 방법 참고). 단위 테스트는 스프링 컨텍스트를 로드하지 않으므로 `@SpringBootTest` 대신 JUnit의 기본 애너테이션만 사용하거나, 필요한 경우 특정 빈을 모킹합니다. 예를 들어, 간단한 서비스 클래스를 테스트한다고 가정해 봅시다. 먼저 테스트 클래스를 다음과 같이 설정합니다: ```java import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class MyServiceTest { @Test void simpleTest() { MyService service = new MyService(); String result = service.sayHello("World"); assertThat(result).isEqualTo("Hello, World!"); } } ``` 위 코드는 스프링 없이 순수 자바 객체를 테스트하는 예제입니다. 하지만 스프링 부트 애플리케이션에서는 의존성이 있는 경우가 많으므로, 이를 모킹하는 방법을 배워야 합니다. ### 2.3 Mockito를 활용한 모킹 실제 애플리케이션에서는 서비스가 리포지토리나 외부 API에 의존하는 경우가 많습니다. 이런 의존성을 제거하고 단위 테스트를 독립적으로 실행하려면 **Mockito**를 사용합니다. Mockito는 가짜 객체(mock)를 만들어 의존성을 대체하며, 특정 동작을 시뮬레이션할 수 있게 해줍니다. 예를 들어, `UserService`가 `UserRepository`에 의존한다고 가정해 봅시다: ```java @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public String getUserName(Long id) { User user = userRepository.findById(id); return user != null ? user.getName() : "Unknown"; } } ``` 이를 테스트하려면 Mockito를 사용해 `UserRepository`를 모킹합니다: ```java import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.when; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) // Mockito를 JUnit 5와 통합 class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test void getUserName_withExistingUser_returnsName() { // 모킹 설정: ID가 1인 경우 "Alice"라는 사용자를 반환 when(userRepository.findById(1L)).thenReturn(new User(1L, "Alice")); String result = userService.getUserName(1L); assertThat(result).isEqualTo("Alice"); } @Test void getUserName_withNonExistingUser_returnsUnknown() { // 모킹 설정: ID가 2인 경우 null 반환 when(userRepository.findById(2L)).thenReturn(null); String result = userService.getUserName(2L); assertThat(result).isEqualTo("Unknown"); } } ``` `@Mock`으로 가짜 객체를 만들고, `@InjectMocks`로 이를 주입받는 `UserService`를 테스트합니다. `when().thenReturn()`을 사용해 모킹된 객체의 동작을 정의합니다. ### 2.4 컨트롤러, 서비스, 리포지토리 단위 테스트 예제 이제 각 계층별로 단위 테스트를 작성하는 방법을 살펴보겠습니다. #### 컨트롤러 단위 테스트 컨트롤러는 HTTP 요청을 처리하므로, `@WebMvcTest`를 사용해 MVC 계층만 로드하고 의존성을 모킹합니다: ```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void getUserName_returnsName() throws Exception { when(userService.getUserName(1L)).thenReturn("Alice"); mockMvc.perform(get("/users/1/name")) .andExpect(status().isOk()) .andExpect(content().string("Alice")); } } ``` #### 서비스 단위 테스트 위의 `UserServiceTest` 예제를 참고하세요. 서비스는 비즈니스 로직을 테스트하며, 의존성을 모킹해 독립적으로 검증합니다. #### 리포지토리 단위 테스트 리포지토리는 데이터베이스와 직접 상호작용하므로, 단위 테스트에서는 구현체 대신 인터페이스를 모킹하거나, 간단한 메모리 기반 구현을 사용할 수 있습니다. 통합 테스트에서 실제 DB를 다루는 경우가 많으므로, 여기서는 간단히 생략하고 3장에서 다룹니다. --- ### 마무리 단위 테스트는 스프링 부트 애플리케이션의 각 부분을 독립적으로 검증하며, 빠르고 신뢰할 수 있는 피드백을 제공합니다. Mockito와 같은 도구를 활용하면 복잡한 의존성도 쉽게 관리할 수 있습니다. 다음 장에서는 단위 테스트를 넘어, 애플리케이션 전체를 통합적으로 테스트하는 방법을 배워보겠습니다. 단위 테스트를 마스터하면 더 큰 테스트 세계로의 첫걸음을 내딛게 될 것입니다! --- 이 글은 실습 중심으로 작성되었으며, 코드 예제를 통해 실질적인 이해를 돕고자 했습니다. 추가 설명이나 수정이 필요하면 말씀해 주세요!