7.7 KiB
아래는 2장. 단위 테스트 작성에 대한 글입니다. 이 장은 스프링 부트에서 단위 테스트의 개념을 설명하고, 실습 가능한 예제와 함께 구체적인 작성 방법을 다룹니다. 초보자와 중급 개발자 모두 이해할 수 있도록 단계적으로 접근했습니다.
2장. 단위 테스트 작성
단위 테스트는 애플리케이션의 개별 구성 요소가 독립적으로 올바르게 동작하는지 확인하는 과정입니다. 스프링 부트에서는 단위 테스트를 통해 컨트롤러, 서비스, 리포지토리와 같은 각 계층의 로직을 검증할 수 있습니다. 이 장에서는 단위 테스트의 정의와 중요성, 스프링 부트에서의 설정 방법, 그리고 실습 예제를 다룹니다.
2.1 단위 테스트란 무엇인가
단위 테스트(Unit Test)는 코드의 가장 작은 단위—보통 메서드나 클래스—를 독립적으로 테스트하는 것을 의미합니다. 외부 의존성(데이터베이스, 외부 API 등)에 의존하지 않고, 해당 단위가 기대한 대로 동작하는지 확인하는 데 초점이 맞춰져 있습니다. 단위 테스트의 주요 장점은 다음과 같습니다:
- 빠른 피드백: 실행 속도가 빠르며, 문제를 조기에 발견할 수 있습니다.
- 리팩토링 안전성: 코드 변경 시 기존 기능이 깨지지 않음을 보장합니다.
- 문서화: 테스트 코드는 코드의 사용법과 의도를 보여주는 살아있는 문서 역할을 합니다.
스프링 부트에서는 단위 테스트를 작성할 때 의존성 주입이나 스프링 컨텍스트를 최소화하고, 필요 시 모킹(mock)을 활용해 독립성을 유지합니다.
2.2 스프링 부트에서 단위 테스트 설정
스프링 부트에서 단위 테스트를 시작하려면 기본적으로 spring-boot-starter-test 의존성이 필요합니다(1장에서 설정 방법 참고). 단위 테스트는 스프링 컨텍스트를 로드하지 않으므로 @SpringBootTest 대신 JUnit의 기본 애너테이션만 사용하거나, 필요한 경우 특정 빈을 모킹합니다.
예를 들어, 간단한 서비스 클래스를 테스트한다고 가정해 봅시다. 먼저 테스트 클래스를 다음과 같이 설정합니다:
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에 의존한다고 가정해 봅시다:
@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를 모킹합니다:
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 계층만 로드하고 의존성을 모킹합니다:
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와 같은 도구를 활용하면 복잡한 의존성도 쉽게 관리할 수 있습니다. 다음 장에서는 단위 테스트를 넘어, 애플리케이션 전체를 통합적으로 테스트하는 방법을 배워보겠습니다. 단위 테스트를 마스터하면 더 큰 테스트 세계로의 첫걸음을 내딛게 될 것입니다!
이 글은 실습 중심으로 작성되었으며, 코드 예제를 통해 실질적인 이해를 돕고자 했습니다. 추가 설명이나 수정이 필요하면 말씀해 주세요!