TIL - 20260317

juni·2026년 3월 17일

TIL

목록 보기
295/318

0317 스프링 부트 프로젝트 (9/N): 테스트 코드 작성 전략


✅ 1. 테스트 코드의 중요성과 테스트 피라미드

  • 테스트 코드는 내가 작성한 코드가 의도한 대로 정확하게 동작하는지를 검증하는 코드로, 소프트웨어의 안정성을 보장하고, 리팩토링 시 자신감을 주며, 코드 자체를 설명하는 살아있는 문서의 역할을 합니다.

  • 테스트 피라미드: 안정적이고 효율적인 테스트 전략을 수립하기 위한 모델입니다.

    1. 단위 테스트 (Unit Test) - 넓은 기반: 가장 작고 격리된 코드 단위(메서드, 클래스)를 테스트. 매우 빠르고 안정적입니다.
    2. 통합 테스트 (Integration Test) - 중간: 여러 컴포넌트가 함께 연동되는 과정을 테스트. 상대적으로 느립니다.
    3. E2E 테스트 (End-to-End Test) - 좁은 상단: 실제 사용자의 시나리오 전체를 테스트. 가장 느리고 비용이 많이 듭니다.
  • 전략: 빠르고 안정적인 단위 테스트를 가장 많이 작성하고, 꼭 필요한 부분에만 통합 테스트를 추가하여 효율적인 테스트 스위트를 구성해야 합니다.


✅ 2. 단위 테스트 (Unit Test): Service 계층 테스트하기

  • 목표: 서비스 계층의 비즈니스 로직을 외부 의존성(Repository 등)으로부터 완전히 격리하여 테스트합니다.
  • 핵심 기술 (Mocking): 실제 데이터베이스에 접근하는 Repository 대신, 우리가 원하는 행동을 하도록 미리 정의한 가짜(Mock) 객체를 사용하여 테스트를 진행합니다.

➕ Mockito를 이용한 단위 테스트

  • @ExtendWith(MockitoExtension.class): Spring 컨테이너의 도움 없이, 순수 JUnit과 Mockito만으로 테스트를 실행하여 매우 가볍고 빠릅니다.
  • @Mock: 가짜(Mock) 객체를 생성합니다. (e.g., PostRepository)
  • @InjectMocks: 테스트 대상이 되는 객체를 생성하고, @Mock으로 생성된 가짜 객체들을 자동으로 주입해줍니다. (e.g., PostService)
  • given(...).willReturn(...): Mock 객체의 특정 메서드가 호출될 때, 어떤 값을 반환할지를 정의(Stubbing)합니다.
@ExtendWith(MockitoExtension.class)
class PostServiceTest {

    @Mock
    private PostRepository postRepository;

    @InjectMocks
    private PostService postService;

    @Test
    @DisplayName("게시글 단건 조회 성공")
    void findPostById_Success() {
        // given - 테스트 준비
        Long postId = 1L;
        Post mockPost = new Post("테스트 제목", "테스트 내용");

        // postRepository.findById(1L)가 호출되면, mockPost를 담은 Optional을 반환하도록 정의
        given(postRepository.findById(postId)).willReturn(Optional.of(mockPost));

        // when - 테스트 실행
        PostResponse response = postService.findPostById(postId);

        // then - 결과 검증
        assertThat(response.getTitle()).isEqualTo("테스트 제목");
        assertThat(response.getContent()).isEqualTo("테스트 내용");

        // postRepository의 findById 메서드가 정확히 1번 호출되었는지 검증
        verify(postRepository, times(1)).findById(postId);
    }
}

✅ 3. 통합 테스트 (Integration Test): Controller 계층 테스트하기

  • 목표: 컨트롤러의 역할(HTTP 요청/응답, 데이터 바인딩, 유효성 검증 등)을 실제와 유사한 환경에서 테스트합니다.
  • @WebMvcTest: 웹 계층만(@Controller 등)을 테스트하기 위한 슬라이스 테스트 어노테이션입니다. @Service, @Repository 등은 로드하지 않으므로, 이들은 @MockBean으로 대체해야 합니다.

➕ MockMvc를 이용한 API 테스트

  • MockMvc: 실제 서버를 띄우지 않고도, HTTP 요청을 시뮬레이션하고 응답 결과를 검증할 수 있게 해주는 강력한 테스트 도구입니다.
@WebMvcTest(PostController.class) // PostController와 관련된 웹 계층만 테스트
class PostControllerTest {

    @Autowired
    private MockMvc mockMvc; // HTTP 요청 시뮬레이터

    @MockBean // PostController가 의존하는 PostService를 가짜 Bean으로 등록
    private PostService postService;

    @Test
    @DisplayName("게시글 단건 조회 API 성공")
    void getPost_Success() throws Exception {
        // given
        Long postId = 1L;
        PostResponse mockResponse = new PostResponse(postId, "제목", "내용", LocalDateTime.now());

        // postService.findPostById(1L)가 호출되면, mockResponse를 반환하도록 정의
        given(postService.findPostById(postId)).willReturn(mockResponse);

        // when & then
        mockMvc.perform(get("/api/posts/{id}", postId) // GET /api/posts/1 요청
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk()) // HTTP 상태 코드가 200 OK인지 확인
                .andExpect(jsonPath("$.title").value("제목")) // 응답 JSON의 title 필드 값 확인
                .andDo(print()); // 요청/응답 내용 상세 출력
    }
}

📌 요약

  • 효율적인 테스트 전략은 빠르고 안정적인 단위 테스트를 기반으로 합니다.
  • 서비스 계층은 외부 의존성을 Mocking하여 비즈니스 로직의 정확성을 검증하는 단위 테스트(@ExtendWith(MockitoExtension.class))에 적합합니다.
  • 컨트롤러 계층은 HTTP 요청/응답을 시뮬레이션하는 통합 테스트에 적합하며, @WebMvcTestMockMvc를 사용하여 웹 계층만 가볍고 빠르게 테스트할 수 있습니다.
  • 테스트 코드 작성은 단순히 버그를 찾는 것을 넘어, 설계 개선의 기회를 제공하고 시스템의 안정성과 유지보수성을 크게 향상시키는 필수적인 개발 활동입니다.

0개의 댓글