안녕하세요. 벌써 금요일이네요. 곧 주말이라서 그런지 아무것도 안하고 자고 유튜브 보면서 놀고싶네요...하지만 해야겠죠? 저번시간에는 단위, 기능, 통합 테스트에 대해서 학습했습니다. 이번 시간에는 프로젝트에서 테스트 코드를 작성할 때 어떤 것을 검증해야 하는지 알아보겠습니다.
테스트 코드
테스트 코드는 무엇을 검증해야 할까?
테스트 코드는 단순히 메서드가 실행되는지 확인하는 코드가 아닙니다. 입력값에 따라 기대한 결과가 나오는지, 잘못된 요청이 들어왔을 때 의도한 예외가 발생하는지, 데이터 흐름이 깨지지 않는지를 확인하는 코드입니다.
예를 들어 회원가입 기능일 경우 다음과 같은 내용을 검증할 수 있습니다.
- 정상적인 이메일과 비밀번호가 들어오면 회원가입이 성공하는가?
- 이미 존재하는 이메일이면 예외가 발생하는가?
- 필수 값이 비어 있으면 요청이 실패하는가?
테스트 코드의 기본 구조 : given-when-then
given-when-then은 테스트 코드를 읽기 쉽게 나누는 방식입니다.
- given은 테스트에 필요한 상황을 준비하는 단계입니다.
- when은 실제 테스트할 동작을 실행하는 단계입니다.
- then은 실행 결과가 기대한 값과 일치하는지 검증하는 단계입니다.
JUnit의 역할
JUnit은 Java에서 테스트 코드를 작성하고 실행하기 위해 사용하는 대표적인 테스트 프레임워크입니다.
@Test 어노테이션을 통해 테스트 메서드를 표시하고, assertThat, assertEquals, assertThrows 등을 사용해 결과를 검증할 수 있습니다.
@Test
void 회원가입_성공() {
// given
// when
// then
}
Mockito의 역할
Service 로직을 테스트할 때 실제 데이터베이스까지 연결하면 테스트 범위가 커지고 속도도 느려질 수 있습니다.
이때 Repository 같은 의존 객체를 가짜 객체로 대체해 원하는 상황을 만들 수 있는데, 이때 사용하는 도구가 Mockito입니다.
JUnit이 테스트를 실행하고 결과를 검증하는 역할이라면 Mockito는 테스트에 필요한 의존 객체의 동작을 가짜로 정의하는 역할을 합니다.
when(userRepository.existsByEmail("test@email.com"))
.thenReturn(true);
위 코드는 실제 데이터베이스를 조회하는 것이 아니라 userRepository.existsByEmail("test@email.com")이 호출되면 true를 반환하도록 가짜 동작을 정의한 것입니다.
Service 단위 테스트 예시
회원가입 Service 테스트에서는 Controller나 실제 DB 연결보다 Service 내부의 핵심 비즈니스 로직이 맞는지 확인하는 데 집중합니다.
- 이메일이 중복되지 않으면 회원가입 성공
- 이메일이 이미 존재하면 예외 발생
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void 이미_존재하는_이메일이면_회원가입에_실패한다() {
// given
SignUpRequest request = new SignUpRequest("test@email.com", "12345678");
when(userRepository.existsByEmail("test@email.com"))
.thenReturn(true);
// when & then
assertThrows(IllegalArgumentException.class, () -> {
userService.signUp(request);
});
}
}
이 테스트 역시 실제 데이터베이스를 조회하지 않습니다. 대신 Mockito를 이용해 이미 해당 이메일이 존재하는 상황을 만들고, 회원가입 메서드를 실행했을 때 예외가 발생하는지 확인합니다.
즉, 이 테스트의 목적을 DB 저장 여부가 아니라 "중복 이메일이면 회원가입을 막는 로직이 정상적으로 동작하는가"를 검증하는 것입니다.
Controller 테스트와 MockMvc
Controller 테스트는 사용자가 API를 호출했을 때 요청이 올바르게 처리되고, 기대한 HTTP 상태 코드와 응답이 반환되는지 확인하느 테스트입니다.
MockMvc는 실제 서버를 실행하지 않고도 Controller에 HTTP 요청을 보내는 것처럼 테스트 할 수 있도록 도와주는 도구입니다.
예시로 아래와 같은 검증이 진행됩니다.
- POST /api/users 요청이 201 Created가 반환되는가?
- 필수 값이 없을 때 400 Bad Request가 반환되는가?
- 인증이 필요한 API에 토큰 없이 접근하면 401 Unauthorized가 반환되는가?
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
void 회원가입_요청이_성공하면_201을_반환한다() throws Exception {
// given
SignUpRequest request = new SignUpRequest("test@email.com", "12345678");
// when & then
mockMvc.perform(post("/api/users/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated());
}
}
- @WebMvcTest는 Cotroller 계층만 테스트할 때 사용합니다.
- MockMvc는 실제 서버를 실행하지 않고도 HTTP 요청을 보내는 것처럼 테스트할 수 있게 해줍니다.
- contentType은 요청 데이터 형식이 JSON임을 의미합니다.
- content는 요청 본문에 들어갈 데이터를 의미합니다.
- andExpect는 응답 결과를 검증하는 부분입니다.
테스트 작성 시 주의할 점
테스트 코드는 많이 작성하는 것보다 변경이 발생했을 때 중요한 기능이 깨졌는지 빠르게 알려주는 방향으로 작성하는 것이 중요합니다.
- 한 테스트에서는 하나의 목적만 검증하기
- 성공 케이스뿐 아니라 실패 케이스도 작성하기
- 테스트 이름만 보고도 어떤 상황을 검증하는지 알 수 있게 작성하기
- 실제 DB나 외부 API에 지나치게 의존하지 않기
- 구현 세부사항보다 결과와 동작 중심으로 검증하기
오늘은 테스트 코드에 대한 학습을 해보았습니다. 오늘은 하기 싫은데 마음도 있었지만 그래도 끝까지 정리해보았습니다. 다음 시간에는 CI/CD로 돌아오도록 하겠습니다. 수고하셨습니다.
'백엔드 공부' 카테고리의 다른 글
| CI/CD(2) - CI/CD 도구와 GitHub Actions (0) | 2026.05.19 |
|---|---|
| CI/CD(1) - CI/CD란 무엇인가? (0) | 2026.05.18 |
| 테스트(1) - 단위 테스트, 통합 테스트, 기능 테스트 (1) | 2026.05.14 |
| 웹 보안 지식(6) - 콘텐츠 보안 정책 (0) | 2026.05.13 |
| 웹 보안 지식(5) - OWASP 보안 취약점 (0) | 2026.05.12 |