단위 테스트, 통합 테스트, E2E 테스트의 차이점과 테스트 피라미드를 설명해주세요.
답변 포인트
테스트 범위, 실행 속도, 신뢰도, 유지보수 비용을 기준으로 비교해보세요.
정답 및 해설
빠른 요약
단위 테스트는 함수나 클래스 같은 작은 단위를 독립적으로 검증하며 빠르고 실패 원인을 찾기 쉽습니다. 통합 테스트는 여러 모듈이나 계층이 함께 동작하는지 검증합니다.
테스트는 범위와 목적에 따라 단위 테스트, 통합 테스트, E2E 테스트로 나눌 수 있습니다. 좋은 테스트 전략은 모든 테스트를 똑같이 많이 작성하는 것이 아니라, 비용과 신뢰도의 균형을 맞추는 것입니다.
단위 테스트(Unit Test)
가장 작은 단위의 로직을 독립적으로 검증합니다. 보통 함수, 클래스, 컴포넌트의 특정 동작을 테스트합니다.
function add(a: number, b: number) {
return a + b;
}
test("두 숫자를 더한다", () => {
expect(add(1, 2)).toBe(3);
});장점:
- 빠르게 실행됨
- 실패 원인을 찾기 쉬움
- 리팩터링 시 작은 로직을 보호
단점:
- 실제 시스템 연동 문제는 놓칠 수 있음
- 구현 세부사항에 너무 밀착하면 유지보수가 어려움
통합 테스트(Integration Test)
여러 모듈이 함께 동작하는지 검증합니다. 예를 들어 API 핸들러와 DB, 서비스와 저장소 계층을 함께 테스트할 수 있습니다.
test("회원가입 API가 사용자를 생성한다", async () => {
const response = await request(app)
.post("/users")
.send({ email: "a@example.com", password: "12345678" });
expect(response.status).toBe(201);
expect(await userRepository.findByEmail("a@example.com")).toBeTruthy();
});장점:
- 모듈 간 연결 문제를 발견
- 실제 동작에 더 가까움
단점:
- 단위 테스트보다 느림
- 테스트 데이터와 환경 준비가 필요
E2E 테스트(End-to-End Test)
사용자의 실제 흐름을 브라우저나 앱 수준에서 검증합니다.
test("사용자가 로그인할 수 있다", async ({ page }) => {
await page.goto("/login");
await page.fill("[name=email]", "user@example.com");
await page.fill("[name=password]", "password");
await page.click("button[type=submit]");
await expect(page).toHaveURL("/dashboard");
});장점:
- 실제 사용자 관점의 신뢰도가 높음
- 프론트엔드, 백엔드, 라우팅, 인증 문제를 함께 발견
단점:
- 느리고 불안정할 수 있음
- 실패 원인 분석이 어렵고 비용이 큼
테스트 피라미드
E2E
Integration
Unit일반적으로 단위 테스트를 가장 많이, 통합 테스트를 그보다 적게, E2E 테스트는 핵심 사용자 흐름 위주로 작성합니다.
좋은 테스트의 기준
- 중요한 비즈니스 규칙을 검증합니다.
- 구현 방식보다 관찰 가능한 동작을 테스트합니다.
- 실패했을 때 원인을 빠르게 알 수 있습니다.
- 너무 많은 mocking으로 현실과 멀어지지 않습니다.
- 느린 테스트와 빠른 테스트를 분리해 실행합니다.
정리
단위 테스트는 빠른 피드백, 통합 테스트는 모듈 간 연결 검증, E2E 테스트는 실제 사용자 흐름 검증에 강합니다. 테스트 피라미드는 테스트의 양과 비용을 균형 있게 배치하기 위한 가이드입니다.
테스트 피라미드의 의도
테스트 피라미드는 빠르고 안정적인 단위 테스트를 많이 두고, 느리고 깨지기 쉬운 E2E 테스트는 핵심 흐름 위주로 적게 두자는 경험칙입니다.
| 테스트 | 검증 범위 | 장점 | 단점 |
|---|---|---|---|
| Unit | 함수/컴포넌트 단위 | 빠름, 원인 파악 쉬움 | 통합 문제를 놓침 |
| Integration | DB/API/모듈 연결 | 실제 결합 검증 | 환경 준비 필요 |
| E2E | 사용자 시나리오 전체 | 신뢰도 높음 | 느리고 flaky 가능 |
// 단위 테스트 예시
it('할인 금액을 계산한다', () => {
expect(calculateDiscount(10000, 0.1)).toBe(1000);
});실무 기준
- 비즈니스 규칙은 단위 테스트로 촘촘히 검증합니다.
- DB 쿼리, 외부 API adapter는 통합 테스트로 확인합니다.
- 로그인, 결제, 주문 같은 핵심 경로만 E2E로 보호합니다.
- 테스트가 자주 실패한다면 제품 버그인지 테스트 불안정성인지 먼저 분리합니다.