Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20241213 유닛테스트와 통합테스트의 경계 본문

개발Article

[TIL]20241213 유닛테스트와 통합테스트의 경계

최밤빵 2024. 12. 13. 06:45

프로젝트를 잠시 쉬어가는 동안, 능동적으로 무언가를 해보고 싶어 설계와 공부에 집중하고 있었다. 그중에서도 평소 흐린눈하며 넘어갔던 테스트 코드를 작성해보기로 하면서, 작성하다 보니 유닛 테스트와 통합 테스트의 경계를 명확히 구분하는 일이 생각보다 쉽지 않았다. 이론적으로는 둘의 차이를 알고 있지만, 실제 코드에 적용하는 과정은 항상 어려운 도전처럼 느껴진다. 이번 개발일지에서는 테스트 코드를 작성하면서 겪었던 고민들과 어떻게 해결했는지 정리했다.


1. 유닛 테스트와 통합 테스트

유닛 테스트는 개별 메서드나 클래스 단위로 동작을 검증하는 테스트이다. 의존성을 Mock으로 처리해 테스트의 초점이 대상 코드에만 맞춰지도록 한다. 반면, 통합 테스트는 여러 모듈이나 계층이 함께 동작하는지를 확인하는 테스트로, 실제 환경에서의 동작을 검증하는 데 초점이 있다.

  • 유닛 테스트 
    - 빠르게 실행된다.
    - 의존성이 최소화되어 있다.
    - 단일 기능에 초점이 맞춰져 있다.
  • 통합 테스트 
    - 실제 데이터베이스나 외부 API를 호출한다.
    - 의존성이 포함되어 있어 실행 속도가 느릴 수 있다.
    - 전체적인 동작을 검증한다.

  • 유닛 테스트는 코드의 가장 작은 단위(보통 메서드나 클래스)를 독립적으로 검증하는 테스트로, 의존성(데이터베이스, 네트워크 등)을 포함하지 않고, 주로 Mock 객체를 사용하여 외부 요소를 대체한다.
  • 통합 테스트는 여러 컴포넌트나 계층이 올바르게 동작하는지 검증하는 테스트로, 실제 데이터베이스나 외부 API와 통합하여 동작을 확인한다.
테스트 유형 목적 특징
유닛 테스트 단일 메서드 또는 클래스의 동작 확인 빠르고, 독립적이며 Mock 객체를 자주 사용.
통합 테스트 여러 컴포넌트가 함께 작동하는지 검증 실제 데이터베이스, 네트워크 등 외부 의존성을 포함. 상대적으로 느림.

2. 고민했던 점

고민 1: Mock 사용 범위

ClanPlayersService에서 데이터를 변환하는 로직을 검증하기 위해 PlayersRepository를 Mock 처리했지만, 실제 데이터베이스와의 상호작용이 제대로 동작하는지 확인이 필요했다. Mock을 얼마나 활용해야 할지, 그리고 실제 환경과의 차이를 어떻게 줄일지 고민이 많았다.

고민 2: 테스트 데이터 관리

테스트 데이터가 많아질수록 데이터베이스와의 연동 테스트에서 데이터가 꼬이거나, 의도하지 않은 결과를 초래하는 문제가 발생했다. 이를 해결하기 위해 @BeforeEach를 활용해 매번 테스트 데이터를 초기화하거나, 임시 데이터베이스를 활용하는 방법을 시도했다.

고민 3: 테스트 실패 원인 분석

테스트가 실패했을 때, 실패 원인을 파악하는 과정이 복잡했다. 실패의 원인이 코드의 버그인지, 테스트 로직의 문제인지, 아니면 Mock 설정의 문제인지 구분하는 데 많은 시간이 걸렸다.

고민 4: 유닛 테스트와 통합 테스트의 경계

Service 계층의 유닛 테스트를 작성했지만, Repository 계층과 연동되는 부분에서 테스트가 통합 테스트처럼 변질되는 문제가 있었다. 유닛 테스트를 작성하며 의존성을 Mock으로 처리했지만, 실제 데이터와의 연동이 제대로 이루어지는지 확인하기 위해 통합 테스트를 따로 작성해야 했다.

고민 5: H2 데이터베이스 도입 여부

초기에는 실제 데이터베이스를 사용해 테스트를 진행했지만, 데이터가 진짜 마구마구 이상해져가는 것과 테스트 실행 속도의 문제를 겪었다. 이를 해결하기 위해 H2 데이터베이스를 도입하는 방안을 생각했다 (연결실패한 적 있음.....)


3. 작성한 테스트 코드

유닛 테스트: ClanPlayersService 테스트

@ExtendWith(MockitoExtension.class)
class ClanPlayersServiceTest {

    private final PlayersRepository playersRepository;
    private final ClanPlayersService clanPlayersService;

    public ClanPlayersServiceTest() {
        this.playersRepository = mock(PlayersRepository.class);
        this.clanPlayersService = new ClanPlayersService(playersRepository);
    }

    @Test
    void testGetPlayerRankedStats() {
        // given
        String playerId = "1234";
        String seasonId = "2023-01";
        String platform = "steam";

        when(playersRepository.findByPlayersId(playerId))
            .thenReturn(Optional.of(new Player("1234", "TestPlayer")));

        // when
        RankedStatsResponseDto response = clanPlayersService.getPlayerRankedStats(platform, playerId, seasonId);

        // then
        assertNotNull(response);
        assertEquals("TestPlayer", response.getPlayersName());
    }
}

통합 테스트 : Controller 테스트

@SpringBootTest
@AutoConfigureMockMvc
class ClanPlayersControllerTest {

    private final MockMvc mockMvc;

    public ClanPlayersControllerTest(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }

    @Test
    void testGetPlayerRankedStatsIntegration() throws Exception {
        mockMvc.perform(get("/open/clanplayers/steam/players/1234/seasons/2023-01/ranked"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.playersName").value("TestPlayer"));
    }
}

4. 해결한 방법과 배운 점

해결 방법

Mock 사용 가이드라인 정의
  • 유닛 테스트에서는 외부 의존성을 모두 Mock으로 처리해서, 테스트 대상 코드에만 초점을 맞췄다.
  • 통합 테스트에서는 실제 데이터베이스와의 연동을 검증하기 위해 Spring Boot의 @SpringBootTest@AutoConfigureMockMvc를 활용했다.
테스트 데이터 관리
  • 테스트 전 초기화 작업을 통해 데이터 일관성을 유지했다.
  • 통합 테스트에서는 H2 데이터베이스를 사용하여 테스트 환경을 실제 환경과 유사하게 설정했다.
H2 데이터베이스 사용 이유
  • H2는 가볍고 인메모리 방식으로 동작하기때문에, 테스트 실행 속도가 빠르다.
  • 테스트마다 데이터베이스 상태를 초기화할 수 있어 이상한 데이터가 들어가는걸 방지한다.
  • 실제 데이터베이스와의 호환성이 높아 테스트 환경과 운영 환경 간의 차이를 최소화할 수 있다.
테스트 실패 원인 분석 도구 활용
  • 테스트 로그와 디버깅 도구를 활용해 테스트 실패 원인을 빠르게 파악할 수 있었다.

배운 점

  • 유닛 테스트와 통합 테스트의 경계를 명확히 정의하는 것이 중요하다.
  • Mocking은 적절히 사용해야 하며, 지나치게 의존할 경우 실제 동작과 괴리가 생길 수 있다.
  • 테스트 데이터는 간결하고 일관성 있게 관리해야 한다.
  • 테스트는 단순히 코드가 잘 작동하는지 확인하는 도구가 아니라, 리팩토링과 유지보수를 위한 도구라는 점을 또 배웠다.....!
  • 초보 개발자로서, 테스트 코드를 작성하면서 테스트 코드 자체의 품질도 중요하다는 점을 배우게 되었다.

 
유닛 테스트와 통합 테스트를 작성하며 많은 고민과 시행착오를 겪게됐다.(테스트 코드작성은 익숙하지가 않아서 매번 작성할때 마다 그렇긴하다.) 테스트 코드는 단순히 작성하는 데서 끝나는 것이 아니라, 코드의 품질을 높이고 장기적인 유지보수를 가능하게 하는 필수 요소다. 이번 경험을 통해 유닛 테스트는 코드의 정확성을 검증하고, 통합 테스트는 시스템의 안정성을 보장한다는 각자의 역할을 좀 더 명확히 이해할 수 있었다. 특히, 테스트 환경에서 H2 데이터베이스를 사용함으로써 테스트 속도를 높이고 데이터 오염을 방지할 수 있었다. 앞으로는 각 테스트의 목적과 역할을 더욱 명확히 구분하며, 프로젝트 특성에 맞는 테스트 전략을 지속적으로 이용해 볼 계획이다.