Files
spring-boot-examples/docs/test/02_단위 테스트.md
2025-04-08 19:56:24 +09:00

158 lines
7.7 KiB
Markdown

아래는 **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와 같은 도구를 활용하면 복잡한 의존성도 쉽게 관리할 수 있습니다. 다음 장에서는 단위 테스트를 넘어, 애플리케이션 전체를 통합적으로 테스트하는 방법을 배워보겠습니다. 단위 테스트를 마스터하면 더 큰 테스트 세계로의 첫걸음을 내딛게 될 것입니다!
---
이 글은 실습 중심으로 작성되었으며, 코드 예제를 통해 실질적인 이해를 돕고자 했습니다. 추가 설명이나 수정이 필요하면 말씀해 주세요!