나만의 학습 기록

최종 목적은 기술 블로그💩

백엔드 공부

테스트(2) - 테스트 코드

밈밍민믹 2026. 5. 15. 15:35

안녕하세요. 벌써 금요일이네요. 곧 주말이라서 그런지 아무것도 안하고 자고 유튜브 보면서 놀고싶네요...하지만 해야겠죠? 저번시간에는 단위, 기능, 통합 테스트에 대해서 학습했습니다. 이번 시간에는 프로젝트에서 테스트 코드를 작성할 때 어떤 것을 검증해야 하는지 알아보겠습니다.


테스트 코드

테스트 코드는 무엇을 검증해야 할까?

테스트 코드는 단순히 메서드가 실행되는지 확인하는 코드가 아닙니다. 입력값에 따라 기대한 결과가 나오는지, 잘못된 요청이 들어왔을 때 의도한 예외가 발생하는지, 데이터 흐름이 깨지지 않는지를 확인하는 코드입니다.

 

예를 들어 회원가입 기능일 경우 다음과 같은 내용을 검증할 수 있습니다.

  • 정상적인 이메일과 비밀번호가 들어오면 회원가입이 성공하는가?
  • 이미 존재하는 이메일이면 예외가 발생하는가?
  • 필수 값이 비어 있으면 요청이 실패하는가?

 

테스트 코드의 기본 구조 : 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 내부의 핵심 비즈니스 로직이 맞는지 확인하는 데 집중합니다.

 

  1. 이메일이 중복되지 않으면 회원가입 성공
  2. 이메일이 이미 존재하면 예외 발생
@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는 응답 결과를 검증하는 부분입니다.

테스트 작성 시 주의할 점

테스트 코드는 많이 작성하는 것보다 변경이 발생했을 때 중요한 기능이 깨졌는지 빠르게 알려주는 방향으로 작성하는 것이 중요합니다.

  1. 한 테스트에서는 하나의 목적만 검증하기
  2. 성공 케이스뿐 아니라 실패 케이스도 작성하기
  3. 테스트 이름만 보고도 어떤 상황을 검증하는지 알 수 있게 작성하기
  4. 실제 DB나 외부 API에 지나치게 의존하지 않기
  5. 구현 세부사항보다 결과와 동작 중심으로 검증하기

오늘은 테스트 코드에 대한 학습을 해보았습니다. 오늘은 하기 싫은데  마음도 있었지만 그래도 끝까지 정리해보았습니다. 다음 시간에는 CI/CD로 돌아오도록 하겠습니다. 수고하셨습니다.