아래는 **9장. 테스트 피라미드와 실무 적용**에 대한 글입니다. 이 장은 테스트 피라미드라는 이론적 프레임워크를 기반으로 스프링 부트 프로젝트에서 실무적으로 테스트를 적용하는 방법을 다룹니다. 실습 예제와 실무 팁을 포함해 실질적인 가치를 제공하도록 작성했습니다. --- ## 9장. 테스트 피라미드와 실무 적용 테스트는 단순히 코드를 검증하는 도구를 넘어, 프로젝트의 품질과 유지보수성을 높이는 전략입니다. 테스트 피라미드는 이를 체계적으로 설계하는 데 유용한 개념으로, 스프링 부트 프로젝트에서 어떻게 적용할 수 있는지 이 장에서 다룹니다. 실무에서의 전략, 흔한 실수, 그리고 성공적인 테스트 문화를 구축하는 방법까지 살펴보겠습니다. ### 9.1 테스트 피라미드 이해 테스트 피라미드는 테스트의 종류와 비율을 계층적으로 표현한 모델입니다. 아래에서 위로 갈수록 테스트 범위는 넓어지지만, 수와 실행 비용은 줄어듭니다. 주요 계층은 다음과 같습니다: - **단위 테스트(Unit Tests)**: 가장 아래층으로, 개별 메서드나 클래스를 독립적으로 검증. 빠르고 수가 많음. - **통합 테스트(Integration Tests)**: 중간층으로, 여러 모듈이나 계층 간 상호작용을 테스트. 단위 테스트보다 느림. - **엔드투엔드 테스트(E2E Tests)**: 최상층으로, 사용자 관점에서 전체 시스템을 검증. 가장 느리고 수가 적음. 스프링 부트에서: - 단위 테스트: 서비스 로직, 유틸리티 메서드 (예: `UserService.getUserName()`). - 통합 테스트: 컨트롤러와 리포지토리 연동 (예: REST API 호출). - E2E 테스트: 브라우저나 클라이언트를 통한 전체 워크플로우. 피라미드의 핵심은 **단위 테스트를 기반으로 하되, 필요한 만큼 상위 테스트를 보완**하는 것입니다. 단위 테스트가 70-80%, 통합 테스트가 15-25%, E2E 테스트가 5-10% 정도가 일반적인 비율입니다. ### 9.2 실무에서의 테스트 전략 수립 스프링 부트 프로젝트에서 테스트 전략을 수립하려면 다음 단계를 따릅니다: 1. **프로젝트 요구사항 분석**: - 빠른 피드백이 중요한가? → 단위 테스트 강화. - 외부 시스템 연동이 많나? → 통합 테스트 필요. - UI 중심인가? → E2E 테스트 고려. 2. **테스트 범위 정의**: - 핵심 비즈니스 로직은 단위 테스트로 100% 커버. - API 엔드포인트는 통합 테스트로 검증. - 주요 사용자 시나리오는 E2E로 확인. 3. **도구 선택**: - 단위: JUnit, Mockito, AssertJ. - 통합: `@SpringBootTest`, Testcontainers. - E2E: Selenium, Cypress (웹 애플리케이션용). #### 예제 전략 간단한 사용자 관리 API 프로젝트: - **단위 테스트**: `UserService`의 `createUser()`, `getUserName()` 메서드. - **통합 테스트**: `/users` 엔드포인트 호출 및 DB 저장 확인. - **E2E 테스트**: 사용자 등록 후 이름 조회 시나리오. ```java @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class UserApiIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test void createAndGetUser() { // 통합 테스트: 사용자 생성 ResponseEntity createResponse = restTemplate.postForEntity("/users", new User(null, "Alice"), String.class); assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); // 통합 테스트: 사용자 조회 ResponseEntity getResponse = restTemplate.getForEntity("/users/1/name", String.class); assertThat(getResponse.getBody()).isEqualTo("Alice"); } } ``` ### 9.3 흔한 테스트 실수와 해결 방법 실무에서 자주 발생하는 실수와 대처법을 정리했습니다: - **실수 1: 모든 것을 통합 테스트로 작성** - 문제: 느리고 유지보수가 어려움. - 해결: 단위 테스트로 분리 가능한 로직을 옮기고, 통합 테스트는 핵심 흐름에 집중. - **실수 2: 모킹 과다 사용** - 문제: 실제 동작과 달라 신뢰성 하락. - 해결: 모킹은 외부 의존성에만 사용하고, 내부 로직은 실제 구현으로 테스트. - **실수 3: 테스트 데이터 관리 부족** - 문제: 테스트 간 간섭으로 실패. - 해결: `@Transactional` 사용하거나, 각 테스트마다 고유 데이터 생성. - **실수 4: 커버리지에 집착** - 문제: 의미 없는 테스트 증가. - 해결: 핵심 로직의 품질에 집중하고, 커버리지는 참고 지표로만 활용. #### 예제: 실수 수정 ```java // 잘못된 예: 모든 것을 통합 테스트로 @SpringBootTest class BadTest { @Test void testEverything() { // 너무 많은 검증 } } // 개선된 예: 단위 테스트로 분리 class UserServiceTest { @Mock private UserRepository repository; @InjectMocks private UserService service; @Test void getUserName() { when(repository.findById(1L)).thenReturn(new User(1L, "Alice")); assertThat(service.getUserName(1L)).isEqualTo("Alice"); } } ``` ### 9.4 성공적인 테스트 문화 구축 테스트를 프로젝트의 핵심 요소로 만들려면 팀 차원의 노력이 필요합니다: - **교육과 공유**: 신입 개발자에게 테스트 작성법을 가르치고, 코드 리뷰에서 테스트를 점검. - **자동화**: CI/CD에 테스트를 통합해 빠른 피드백 제공 (7장 참고). - **책임 분담**: 모든 팀원이 테스트 작성에 참여하도록 장려. - **측정과 개선**: 커버리지와 실패율을 모니터링하며 지속적으로 개선. #### 실무 팁 - **테스트 주도 개발(TDD)**: 요구사항을 테스트로 먼저 작성 후 구현. - **페어 프로그래밍**: 테스트 코드를 함께 작성하며 품질 향상. - **리팩토링 시간 확보**: 테스트가 없으면 리팩토링이 두려워지므로, 초기에 투자. --- ### 마무리 테스트 피라미드는 스프링 부트 프로젝트에서 테스트를 체계적으로 설계하는 가이드입니다. 단위 테스트로 기반을 다지고, 통합 테스트로 상호작용을 검증하며, E2E 테스트로 사용자 경험을 보장합니다. 실무에서 흔한 실수를 피하고, 팀 전체가 테스트 문화를 받아들이면 안정적이고 확장 가능한 애플리케이션을 만들 수 있습니다. 다음 장에서는 실제 사례를 통해 배운 교훈을 공유하며 이 책을 마무리하겠습니다! --- 이 글은 테스트 피라미드의 이론과 실무 적용을 균형 있게 다루며, 실습과 팁으로 실질적인 도움을 주고자 했습니다. 추가 요청이나 수정 사항이 있다면 말씀해 주세요!