콘텐츠로 이동

테스트 주도 개발 실전 강의 교재

13장 — 진짜로 만들기

대상: Java/Spring 백엔드 입문~중급 수강생 형식: 할 일 → RED → GREEN → REFACTOR → 함정 → 체크리스트 → 퀴즈 전제 환경: Java 17+, JUnit 5


0. 이 장을 시작하기 전에

0.1 학습 목표

  • 12장에서 만든 Bank.reduce 골조를 진짜 동작 으로 채운다.
  • Money.reduce환율 1:1 (같은 통화) 일 때부터 시작 — 가장 단순한 케이스.
  • Sum.reduce 가 augend·addend 각각 reduce 후 합산.
  • 삼각측량 으로 일반화 — 같은 통화 + 다른 통화 둘 다 통과.

0.2 큰 그림 — "reduce 는 환율 적용 + 합산"

[ Money.reduce ]                    [ Sum.reduce ]
 amount / rate (환율 1 이면 그대로)   augend.reduce + addend.reduce
                                     (재귀 — 깊은 표현도 처리)

0.3 현업에서 왜 중요한가

  • 같은 인터페이스 (Expression) + 다른 구현 (Money·Sum) 의 다형성 dispatch.
  • 환율 정책이 외부 (Bank) 에 있어 도메인 객체 (Money) 가 환율을 모름 — 관심사 분리.

1. 할 일 목록 갱신

[x] $5 + $5 = $10 (같은 통화)
[ ] $5 + 10 CHF = $10 (환율 2:1)        ← 이번 (Sum.reduce 본격)
[ ] Pair 클래스 (Bank 의 환율 키)        ← 14장
[ ] hashCode()

2. RED — 같은 통화 reduce

@Test
void reduce_같은_통화() {
    Bank bank = new Bank();
    Money result = bank.reduce(Money.dollar(1), "USD");
    assertEquals(Money.dollar(1), result);   // 환율 1:1
}

→ 통과 (12장에서 이미 rate(from, to) 가 같으면 1 반환).


3. RED — 다른 통화 reduce

@Test
void reduce_다른_통화() {
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);   // 2 CHF = 1 USD
    Money result = bank.reduce(Money.franc(2), "USD");
    assertEquals(Money.dollar(1), result);   // 2 / 2 = 1
}

→ 통과 (12장 코드로 충분).


4. RED — Sum reduce (핵심)

@Test
void Sum_reduce() {
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);
    Expression sum = Money.dollar(5).plus(Money.franc(10));
    Money result = bank.reduce(sum, "USD");
    assertEquals(Money.dollar(10), result);   // 5 + 10/2 = 10
}

→ 12장의 Sum.reduce 가 이미 처리 — 통과.


5. REFACTOR — 도메인 의미 명시

Money.reduce 의 의미 보강

public class Money implements Expression {
    @Override
    public Money reduce(Bank bank, String to) {
        if (this.currency.equals(to)) return this;   // 같은 통화면 자기 자신
        int rate = bank.rate(currency, to);
        return new Money(amount / rate, to);
    }
}

→ 같은 통화면 환산 불필요. 의도 명시 + 성능 최적화.

Sum.reduce 의 의미 보강

public class Sum implements Expression {
    @Override
    public Money reduce(Bank bank, String to) {
        int amount = augend.reduce(bank, to).amount() + addend.reduce(bank, to).amount();
        return new Money(amount, to);
    }
}

augend·addend 가 또 Sum 일 수 있어 재귀.


6. 통과한 시점에서의 인사이트

  • Bank 가 도메인의 환율 정책 단일 책임 — 환율 추가·조회·환산 진입점.
  • Money·Sum 이 모두 reduce 구현 — Bank 는 둘을 구분 안 함 (다형성).
  • 표현이 깊어져도 (Sum.augend = Sum) reduce 가 재귀로 처리.

OO 의 보상: 새 표현 추가 (예: Times implements Expression) 시 Bank 코드 변경 0. OCP 충족.


7. 현업 예제 — 정책의 외부 분리

Spring @Value + 환경 설정

@Component
public class CurrencyConverter {
    private final ExchangeRateProvider provider;   // 외부에서 환율 가져오기
    public Money convert(Money source, String to) {
        BigDecimal rate = provider.rate(source.currency(), to);
        return source.times(rate);
    }
}

// 테스트 시 가짜 provider 주입
new CurrencyConverter(new FixedRateProvider(Map.of(
    Pair.of("USD", "KRW"), new BigDecimal("1300")
)));

→ 환율 정책이 외부 (provider) 에 있고 도메인 (Money) 은 모름. 13장 Bank 와 같은 분리.


8. 함정 / 주의

  • amount / rate 정수 나눗셈 — 책 예제는 정수라 OK, 실무는 BigDecimal + RoundingMode 신중.
  • 환율이 1 인 경우 (같은 통화) 처리 안 하면 환산 비용 낭비 — 위 REFACTOR 단계.
  • Bank 가 환율을 메모리에만 보관 — 운영은 외부 API + 캐싱 + TTL 필요.

9. 체크리스트 (13장 완료 기준)

  • Money.reduce 가 같은 통화 빠른 경로 처리
  • Sum.reduce 가 augend·addend 재귀 reduce 후 합산
  • Bank 가 환율 추가·조회·환산 진입점
  • 모든 테스트 (같은 통화 + 다른 통화 + Sum) 초록

10. 퀴즈

  1. Money.reduce 가 환율 1 인 경우 빠른 경로를 두는 이유?
  2. Sum.reduce 가 재귀인 이유?
  3. Bank 의 단일 책임은 무엇인가?
  4. 새 표현 (Times) 추가 시 Bank 변경이 필요한가?
  5. 정수 나눗셈의 함정과 실무 처방?

정답·해설

  1. 의도 명시 + 성능. 같은 통화면 환산 불필요 → 객체 자기 자신 반환. 환율 조회·계산·새 객체 생성 모두 생략. 도메인 의미 ("같은 통화는 환산 X") 도 코드에 보임.
  2. 표현이 깊어질 수 있음. Sum.augend 가 또 Sum 일 가능성 (예: (a + b) + c). 재귀로 트리 끝까지 reduce. 컴포지트 패턴의 표준 처리.
  3. 환율 정책. 환율 보관·조회·환산 진입점. Money·Sum 같은 도메인 객체는 환율을 모름 — Bank 에 위임. 관심사 분리.
  4. 변경 0. Times 가 Expression 구현 + reduce 만 정의하면 Bank 의 source.reduce(this, to) 가 다형성 dispatch. OCP 충족. 새 표현 = 새 클래스 1개.
  5. 정수 손실5/2 = 2 (소수점 버림). 실무는 BigDecimal + RoundingMode.HALF_UP 등 명시. 책 예제는 학습 단순화. Effective Java Item 60 (BigDecimal 권장) 직결.

다음 장 예고 — 14장: 바꾸기

Bank 가 환율을 Map<Pair, Integer> 로 보관. Pair 클래스 가 필요 (from·to 쌍). equals·hashCode 가 HashMap 키로 작동하게. Effective Java Item 10·11 의 살아있는 사례.