TDD란?
Test Driven Development로
프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론

Red-Green-Refactoring
Red
실패하는 테스트 작성
- 구현부가 없이 테스트를 먼저 작성하는 것이기 때문에 실패할 수 밖에 없습니다.
Green
테스트 통과를 위한 최소한의 코딩
- 테스트를 통과하기 위해서라면 대충 코딩해도 괜찮습니다.
Refactoring
구현 코드 개선 및 테스트 통과 유지

예시)
장바구니에 담긴 음료의 총 금액을 계산하는 기능을 TDD로 구현하기
1. Red
// Test Code
@Test
void calculateTotalPrice() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
Latte latte = new Latte();
cafeKiosk.add(americano);
cafeKiosk.add(latte);
int totalPrice = cafeKiosk.calculateTotalPrice();
assertThat(totalPrice).isEqualTo(8500);
}
// Method
public int calculateTotalPrice() {
return 0;
}
위 테스트를 실행시키면 당연히 통과하지 못할 것입니다. 이 코드를 일단 통과시키기 위해, 다음 단계로 넘갑니다.
2. Green
// Test Code
@Test
void calculateTotalPrice() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
Latte latte = new Latte();
cafeKiosk.add(americano);
cafeKiosk.add(latte);
int totalPrice = cafeKiosk.calculateTotalPrice();
assertThat(totalPrice).isEqualTo(8500);
}
// Method
public int calculateTotalPrice() {
return 8500;
}
위 수정한 코드를 보면, calculateTotalPrice
메서드에서 8500을 반환하고 있기 때문에, 테스트 자체는 통과가 될 것입니다. 이제, 이 코드를 실제 로직에 맞게 리팩토링을 진행합니다.
3. Refactoring
// Test Code
@Test
void calculateTotalPrice() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
Latte latte = new Latte();
cafeKiosk.add(americano);
cafeKiosk.add(latte);
int totalPrice = cafeKiosk.calculateTotalPrice();
assertThat(totalPrice).isEqualTo(8500);
}
// Method
public int calculateTotalPrice() {
int totalPrice = 0;
for (Beverage beverage : beverages) {
totalPrice += beverage.getPrice();
}
return totalPrice;
}
구현부가 변경 되었는데 테스트를 통과한다면, 같은 기능을 한다는 것을 보장할 수 있습니다.
이렇게 진행하면, 다음과 같은 과감한 리팩토링도 가능해집니다.
public int calculateTotalPrice() {
return beverages.stream()
.mapToInt(Beverage::getPrice)
.sum();
}
테스트가 통과한다면, 이전 구현부와 리팩토링한 구현부가 동일한 기능을 한다는 것을 확인할 수 있습니다.
피드백
TDD의 가장 큰 가치는 피드백입니다. 즉,
내가 작성하는 구현 코드에 대해서 자주 그리고 빠르게 피드백을 받을 수 있습니다.
선 기능 구현, 후 테스트 작성
- 테스트 자체의 누락 가능성
- 기능만 두고 테스트 케이스를 작성하지 않는다면, 테스트가 보장해주는 기능이 아니기 때문에 후에 유지보수하는데 어려움이 있습니다.
- 특정 테스트 케이스(해피 케이스)만 검증할 가능성
- 기능을 구현할 때는 해피 케이스에 대해서만 고려를 하기 때문에, 예외 케이스에 대한 검증이 누락될 수 있습니다.
- 잘못된 구현을 다소 늦게 발견할 가능성
선 테스트 작성, 후 기능 구현 ⇒ TDD
- 복잡도가 낮은(유연하며 유지보수가 쉬운), 테스트 가능한 코드로 구현할 수 있게 합니다.
- 쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해줍니다.
- 구현에 대한 빠른 피드백을 받을 수 있습니다.
- 과감한 리팩토링이 가능해집니다.
TDD : 관점의 변화
기존
테스트는 구현부 검증을 위한 보조 수단

TDD
테스트와 상호 작용하며 발전하는 구현부

클라이언트 관점에서의 피드백을 주는 Test Driven
참고
인프런 - Practical Testing: 실용적인 테스트 가이드(박우빈)