Java/CS

테스트코드 개념/ 종류/ 장점/ 주의사항/ TDD

吳버플로우 2025. 3. 20. 17:06

테스트 코드란?

테스트 코드(Test Code)는 개발한 소프트웨어가 예상대로 동작하는지 자동으로 검증하는 코드입니다.

일반적으로 단위 테스트(Unit Test), 통합 테스트(Integration Test), 기능 테스트 (Functional Test, End-to-End Test, E2E)

등의 형태로 작성됩니다.

사진 출처 : https://www.freecodecamp.org/news/types-of-software-testing/

1. 단위 테스트 (Unit Test)

✔️ 정의:

  • 프로그램의 개별 단위(함수, 메서드, 클래스)가 정상적으로 동작하는지 검증하는 테스트
  • 보통 Mock 객체를 활용하여 다른 의존성을 제거하고 해당 단위만 테스트

✔️ 특징:

  • 빠르고 독립적 (테스트 실행 속도가 빠름)
  • 데이터베이스, 네트워크 같은 외부 의존성 없이 실행 (Mocking 활용)
  • 버그를 조기에 발견할 수 있음
  • 자바에서는 JUnit 프레임워크를 통해 단위테스트 코드를 작성할 수 있음

2. 통합 테스트 (Integration Test)

✔️ 정의:

  • 여러 모듈 또는 컴포넌트가 올바르게 작동하는지 검증하는 테스트
  • 단위 테스트와 달리, 데이터베이스, 외부 API, 네트워크 등의 실제 환경과의 연동을 포함

✔️ 특징:

  • 실제 데이터베이스, API, 파일 시스템 등을 사용
  • 여러 모듈이 서로 올바르게 연동되는지 확인
  • 속도가 단위 테스트보다 느릴 수 있음

3. 기능 테스트 (Functional Test, End-to-End Test, E2E)

✔️ 정의:

  • 애플리케이션의 전체 기능이 요구사항대로 동작하는지 검증하는 테스트
  • 실제 사용자 흐름(로그인 → 상품 주문 → 결제 등)을 시뮬레이션

✔️ 특징:

  • 실제 운영 환경과 유사한 환경에서 테스트 진행
  • UI/UX까지 포함하여 사용자 경험을 테스트
  • 실행 속도가 가장 느림
  • 배포 전 최종 검증 단계에서 주로 사용

테스트 코드 작성 시 주의사항

  1. 단일 책임 원칙(Single Responsibility Principle) 준수
    • 하나의 테스트는 하나의 기능만 검증해야 합니다.
    • 여러 기능을 한 테스트에서 검증하면, 어디서 문제가 발생했는지 찾기 어렵습니다.
  2. 테스트 케이스의 독립성 유지
    • 각 테스트는 서로 영향을 주지 않도록 독립적으로 실행 가능해야 합니다.
    • 공유된 전역 상태를 변경하거나, 이전 테스트 결과에 의존하지 않도록 해야 합니다.
  3. AAA 패턴(Arrange, Act, Assert) 준수 (3A pattern) 
    • Arrange(준비): 필요한 데이터나 환경 설정
    • Act(실행): 테스트할 기능 실행
    • Assert(검증): 기대한 결과와 실제 결과 비교

저 또한 프로젝트 당시 작성한 단위테스트 코드에서 이러한 AAA패턴을 준수하도록 노력하였습니다. 

이 함수는 예약 확정 실패 시, 이미 같은 시간과 방에 대해 확정된 예약이 있는 경우 발생하는 예외를 테스트하는 단위 테스트입니다.

void confirmReservation_Fail_AlreadyConfirmedSameTimeAndRoom() throws Exception {
    // Arrange (준비)
    Long id = 1L;
    Long userId = 1L;

    when(reservationService.confirmStatusReservation(any(), anyLong()))
            .thenThrow(new AppException(RESERVATION_CONFIRMED_DUPLICATED_TIME_ROOM));

    // Act (실행)
    ResultActions result = mockMvc.perform(patch("/agents/reservations/{id}", id)
                    .with(SecurityMockMvcRequestPostProcessors.csrf()))
            .andDo(print());

    // Assert (검증)
    result.andExpect(status().isConflict())
          .andExpect(jsonPath("$.resultCode").value("ERROR"))
          .andExpect(jsonPath("$.code").value(RESERVATION_CONFIRMED_DUPLICATED_TIME_ROOM.name()))
          .andExpect(jsonPath("$.message").value(RESERVATION_CONFIRMED_DUPLICATED_TIME_ROOM.getMessage()));
}

@MockBean을 통해 HTTP 요청을 직접 Mocking하지 않고도 내부 로직을 쉽게 Mocking할 수 있다

 

일반적으로 HTTP Mocking을 하려면 실제 요청을 Mock 서버로 보내고 응답을 가짜로 설정하는 방식을 사용해야 합니다. 하지만 @MockBean을 활용하면, Spring 컨텍스트 내부에서 특정 빈의 동작을 직접 Mocking할 수 있으므로, 굳이 HTTP 레벨에서 Mocking할 필요 없이 더 간단하게 테스트가 가능합니다.

즉, "Mock Server를 만들 필요 없이 @MockBean을 이용해 직접 서비스 계층의 동작을 제어할 수 있다"는 의미입니다.

   @MockBean
    private ReservationService reservationService;

이렇게 Mocking을 하면 HTTP Mocking에 비해서 비교적 쉽게 여러 가지 테스트 케이스의 코드 작성이 가능하게 되며 최종적으로 폭넓은 테스트 케이스를 커버할 수 있게 됩니다.


테스트 코드 장점

1. 코드 품질 향상

테스트 코드 작성은 서비스의 품질을 향상시킬 수 있습니다. 테스트 코드를 통해 발생 가능성 있는 버그를 사전에 찾아내고 방지할 수 있으며, 이는 개발자가 신뢰할 수 있는 코드를 작성할 수 있게 도와줍니다

2. 문서화

코드의 목적과 동작을 명확하게 설명하여, 개발자가 테스트를 쉽게 이해하고 유지보수할 수 있도록 돕는다는 점입니다. 즉, 테스트 코드 자체를 문서처럼 활용하여 테스트가 무엇을 검증하는지, 왜 필요한지, 어떤 조건에서 실행되는지 등을 설명하는 것입니다.

 

저는 @DisplayName 어노테이션을 사용해서 테스트코드의 가독성을 향상 시켰습니다.

 

    @Test
    @DisplayName("잘못된 요청으로 예약 가능 시간 조회 실패")
    void getAvailableTimes_BadRequest() throws Exception {
        // Given
        Long roomId = 1L;
        String invalidDate = "invalid-date";

        // When & Then
        mockMvc.perform(get("/reservations/available-times")
                        .param("roomId", String.valueOf(roomId))
                        .param("date", invalidDate)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isBadRequest());
    }

3. 리팩토링

리팩토링을 진행할 때, 기존 코드의 동작이 깨지지 않도록 확인하는 것이 매우 중요합니다. 테스트 코드는 리팩토링 후 기존 기능이 정상적으로 동작하는지 확인하는 안전망 역할을 합니다. 테스트가 잘 작성된 경우, 리팩토링 후 기존 기능의 동작이 변경되지 않았는지 자동으로 검증할 수 있습니다.


@WebMvcTest

Spring MVC의 웹 계층을 테스트할 때 사용하는 JUnit 테스트 어노테이션입니다. 이 어노테이션은 컨트롤러와 관련된 테스트를 수행하는 데 유용합니다. @WebMvcTest를 사용하면 웹 계층만 테스트하며, 서비스나 리포지토리 등 다른 계층은 실제로 실행되지 않도록 하여, 빠르고 효율적인 웹 계층 테스트가 가능하게 됩니다.

@WebMvcTest(controllers = AgentReservationController.class,
        excludeAutoConfiguration = SecurityAutoConfiguration.class,
        excludeFilters =
                {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {OncePerRequestFilter.class})})

TDD란?

TDD(Test-Driven Development)테스트 주도 개발이라고도 하며, 테스트를 먼저 작성하고 그 테스트를 통과할 수 있는 코드를 작성하는 개발 방법론입니다.

TDD의 기본 흐름 (Red-Green-Refactor)

  1. Red - 테스트 작성: 먼저 원하는 기능에 대한 테스트 코드를 작성합니다. 이 테스트는 기능이 구현되지 않았기 때문에 당연히 실패합니다.
  2. Green - 코드 구현: 테스트가 실패하는 것을 확인한 후, 해당 테스트를 통과할 수 있는 최소한의 코드를 작성하여 테스트를 통과하도록 합니다.
  3. Refactor - 리팩토링: 테스트가 통과한 후, 작성한 코드를 리팩토링하여 더 깔끔하고 효율적으로 만듭니다. 리팩토링 후에도 테스트는 계속 통과해야 합니다.

저 또한 프로젝트에서 TDD 방식을 통해 프로젝트를 진행했으며, 작성된 코드는 자동화된 테스트에 의해 지속적으로 검증되므로 버그 발생 가능성이 확연히 적음을 몸소 느낄 수 있었습니다. TDD 개발자이자 미국의 소프트웨어 엔지니어 켄트 벡은 TDD를 이용한 소프트웨어 개발이란 코드변경에 대한 두려움에서 지루함으로 바뀌는 과정이라고 이야기했습니다. 실제로 저를 포함한 팀원들은 코드를 작성하는 것보다 때로는 테스트 코드 작성이 더 오래걸리기도하며 지루함을 느끼기도 하였습니다. 하지만 그 지루함 뒤에는, 생각한 대로 코드가 동작한다는 확신을 얻을 수 있었습니다. 

 

다음 프로젝트를 할 때에는 unit테스트에서 더 나아가 통합, E2E 테스트를 통해서 더 유지보수가 용이하고 품질 높은 코드를 작성해보고 싶습니다. 

 

참고자료

예제로 사용된 코드 

 

GitHub - Teammyong/Ownbang: 화상 통화를 이용한 비대면 부동산 중개 서비스

화상 통화를 이용한 비대면 부동산 중개 서비스. Contribute to Teammyong/Ownbang development by creating an account on GitHub.

github.com

 

실무에서 적용하는 테스트 코드 작성 방법과 노하우 Part 1: 효율적인 Mock Test

 

실무에서 적용하는 테스트 코드 작성 방법과 노하우 Part 1: 효율적인 Mock Test | 카카오페이 기술

Mock 테스트 코드 작성 중에 마주한 문제들과 그 문제를 해결하는 방법과 노하우를 소개드립니다.

tech.kakaopay.com

 

실무에서 적용하는 테스트 코드 작성 방법과 노하우 Part 3: Given 지옥에서 벗어나기 - 객체 기반 데이터 셋업의 한계

 

실무에서 적용하는 테스트 코드 작성 방법과 노하우 Part 3: Given 지옥에서 벗어나기 - 객체 기반

Mock 테스트 코드 작성 중에 마주한 문제들과 그 문제를 해결하는 방법과 노하우를 소개드립니다.

tech.kakaopay.com