Files
spring-boot-examples/docs/test/09_테스트 피라미드와 실무 적용.md
2025-04-08 19:56:24 +09:00

137 lines
6.8 KiB
Markdown

아래는 **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<String> createResponse = restTemplate.postForEntity("/users", new User(null, "Alice"), String.class);
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
// 통합 테스트: 사용자 조회
ResponseEntity<String> 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 테스트로 사용자 경험을 보장합니다. 실무에서 흔한 실수를 피하고, 팀 전체가 테스트 문화를 받아들이면 안정적이고 확장 가능한 애플리케이션을 만들 수 있습니다. 다음 장에서는 실제 사례를 통해 배운 교훈을 공유하며 이 책을 마무리하겠습니다!
---
이 글은 테스트 피라미드의 이론과 실무 적용을 균형 있게 다루며, 실습과 팁으로 실질적인 도움을 주고자 했습니다. 추가 요청이나 수정 사항이 있다면 말씀해 주세요!