16.1 TDD 방법론 (Red-Green-Refactor)과 테스트 피라미드
"기능이 잘 돌아가니 배포합시다!" 는 아마추어의 마인드입니다. 수정할 때마다 레거시 코드가 어디서 터질지 몰라 덜덜 떠는 공포에서 벗어나려면, 애플리케이션의 방패구축 이론인 TDD(Test-Driven Development, 테스트 주도 개발) 를 뼛속까지 이해해야 합니다.
🏆 1. 테스트 피라미드 (Test Pyramid) 원칙
테스트 코드를 짤 때 가장 많이 참조하는 전 세계적인 아키텍처 원칙입니다. 어떤 테스트를 비중 있게 짜야 할지 피라미드 형태로 계층을 정의합니다.
- 단위 테스트 (Unit Test - 밑동, 80% 비중)
- 가장 빠르고 가장 가성비가 높은 테스트의 뼈대입니다.
- 스프링이고 DB고 아무것도 띄우지 않고 0.01초 만에 순수 자바 클래스 모듈의 로직(수학적 계산 등)만 검증합니다.
- 무조건 Mocking(가짜 객체)을 써서 격리 시킵니다.
- 통합 테스트 (Integration Test - 중간, 15% 비중)
- 스프링 컨테이너(
@SpringBootTest)와 물리적 연결 DB까지 다 띄우고(약 3~4초 걸림), 각 레이어가 맞물려 잘 돌아가는지 확인합니다. - 무거우므로 중요 구간에만 파이프라인 확인용으로 씁니다.
- 스프링 컨테이너(
- E2E 테스트 (UI / End-to-End Test - 꼭대기, 5% 비중)
- 가짜 브라우저 창(Selenium 등)을 띄우고 진짜 사용자가 버튼을 클릭해 결제될 때까지의 전 구간 시나리오를 폅니다. 제일 느리고 비쌉니다.
🚦 2. TDD 3단계 호흡법: Red - Green - Refactor 흐름 코딩
TDD는 "코드를 짜고 -> 다 짜면 나중에 테스트 코드를 잘 짜야지(근데 귀찮네 패스)" 가 절대 아닙니다. "무조건 실패하는 테스트를 가장 먼저 짠 뒤 ➡️ 그걸 억지로라도 통과시키기 위해 운영 코드를 짜고 ➡️ 그 뒤에 마음 편히 튜닝(리팩토링)한다" 는 역방향 프레임워크입니다.
실전 회원가입 쿠폰 지급 요구사항 TDD 따라하기
상황: "신규 회원이 가입하면 무조건 웰컴 단골 쿠폰 객체(10% 할인)를 지갑에 소지한 상태가 되게 로직을 구현해라."
🍅 1단계: RED (실패하는 테스트부터 눈 딱 감고 작성)
아직 Member 클래스 자체도 안 만든 구상 단계지만, 그냥 "있다고 치고" 테스트 모듈 컴파일 에러가 나든 말든 이상적인 검증문을 씁니다.
class MemberDomainTest {
@Test
@DisplayName("신규 유저는 회원가입 즉시 10% 웰컴 쿠폰을 1장 갖고 있어야 한다")
void new_user_has_welcome_coupon() {
// [Given] 아몰랑 Member 클래스 객체 생성해!
Member newMember = new Member("Alice");
// [When] 가입 처리
newMember.join();
// [Then] 쿠폰 박스 꺼내서 확인!
assertThat(newMember.getCoupons()).hasSize(1);
assertThat(newMember.getCoupons().get(0).getRatio()).isEqualTo(0.1); // 10% 할인
}
}
결과:
Member클래스 자체가 존재하지를 않으니 당연히 시뻘건 컴파일 폭발 실패(RED) 가 뜹니다!
🍏 2단계: GREEN (가장 무식하고 빠른 방법으로 초록불 만들기)
시뻘건 컴파일 에러를 잠재우기 위해, src/main/java 쪽으로 이동해 진짜 운영 코드 뼈대를 만듭니다. 아웃풋 설계가 딱 맞아떨어지는지 확인하려고 일단 억지 하드코딩 값(new Coupon(0.1))을 고정으로 넣어버립니다.
// 프로덕션 코드 (진짜 서비스)
public class Member {
private String name;
private List<Coupon> coupons = new ArrayList<>();
public Member(String name) { this.name = name; }
public void join() {
// 어쨌든 통과를 위해 가장 무식하게 냅다 10% 쿠폰 때려박기
this.coupons.add(new Coupon(0.1));
}
public List<Coupon> getCoupons() { return this.coupons; }
}
결과: 테스트 돌림 -> 10% 통과 우와!! 초록불(GREEN) 이 떴습니다!! 방어막이 쳐졌습니다.
♻️ 3단계: REFACTOR (우아한 설계로 리팩토링 다듬기)
초록불(방패)이 켜졌다는 건 이제 뭔 짓을 하든 이 로직 결과값이 10%가 안 나오면 곧바로 모니터가 알려준다는 무적의 상태를 의미합니다. 하드코딩 된 무식한 값을 상수로 분리하고, 쿠폰 생성 정책 클래스를 적용하는 등 내부 코드를 세련되게 변형합니다. 코드를 어떻게 박살내도 한 번만 실행 돌리면 성공 유무가 뜨니 마음이 극도로 편안합니다.
public void join() {
// 억지 하드코딩에서 우아한 정책 객체 주입으로 변경 다듬기
Coupon welcomeCoupon = CouponPolicyFactory.createWelcome();
this.coupons.add(welcomeCoupon);
}
// 리팩토링 후 테스트 다시 실행 -> 여전히 GREEN 유지! (안도감)
이 3가지 톱니바퀴 리듬을 끊임없이 반복하며 도메인 성성을 확장해 나가는 것이 TDD 세계 철학입니다.