15. 서로 다른 통화 더하기
$5 + 10CHF = $10
@Test
public void testMixedAddition(){
Money fiveBucks = Money.dollar(5);
Money tenFranc = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(fiveBucks.plus(tenFranc), "USD");
assertEquals(Money.dollar(10), result);
}
- $5 + 10CHF = $10를 수행하기 위한 테스트를 작성한다.
- 테스트는 실패한다. reduce에서 환율을 적용하지 않아 값이 15가 나오기 때문
Sum
public Money reduce(Bank bank, String to){
int amount = augend.reduce(bank, to).amount + addend.reduce(bank, to).amount;
return new Money(amount, to);
}
- Sum의 reduce에서 환율을 적용하게 만들어주자.
- 초록 막대
Money -> Expression
Sum
public class Sum implements Expression{
Expression augend;
Expression addend;
Sum(Expression augend, Expression addend){
this.augend = augend;
this.addend = addend;
}
...
}
Money
Expression plus(Expression addend){
return new Sum(this, addend);
}
Expression times(int multiplier){
return new Money(amount* multiplier, currency);
}
- Money에서도 마찬가지로 Expression으로 변경된다.
@Test
public void testMixedAddition(){
Expression fiveBucks = Money.dollar(5);
Expression tenFranc = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(fiveBucks.plus(tenFranc), "USD");
assertEquals(Money.dollar(10), result);
}
- Money를 Expression으로 변경해준다.
- Expression에 plus가 정의되어있지 않아 컴파일이 안된다.
Expression
public interface Expression {
Expression plus(Expression addend);
}
Money
public Expression plus(Expression addend){
return new Sum(this, addend);
}
Sum
public Expression plus(Expression addend) {
return null;
}
- plus 함수를 정의
- Money의 접근자를 public으로 변경해준다.
- Sum의 plus를 스텁 구현으로 구현해준다.
- 테스트는 다시 초록상태
우리는
- 원하는 테스트를 작성하고, 한 단계에 달성할 수 있도록 뒤로 물렀다.
- 좀더 추상적인 선언을 통해 가지에서 뿌리(애초의 테스트 케이스)로 일반화했다.
- 변경 후(Expression fiveBucks), 그 영향을 받은 다른 부분들의 변경하기 위해 컴파일러의 지시를 따랐다. (Expression에 plus()를 추가하기 등등)
16. 드디어, 추상화
Sum.plus()
@Test
public void testSumPlusMoney(){
Expression fiveBucks = Money.dollar(5);
Expression tenFranc = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum = new Sum(fiveBucks, tenFranc).plus(fiveBucks);
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(15), result);
}
- expression의 reduce가 스텁으로 되어있어 nullpointerException 발생
Sum
@Override
public Expression plus(Expression addend) {
return new Sum(this, addend);
}
TDD로 구현할 때?
- TDD로 구현할 때 테스트 코드의 줄 수와 모델 코드의 줄 수가 거의 비슷한 상태로 끝난다.
- TDD가 경제적이기 위해서는 매일 만들어내는 코드의 줄 수가 두 배가 되거나 동일한 기능을 해내야한다.
- TDD가 자신의 방법에 비해 어떻게 다른지 직접 측정해 보아야 한다. 이 때 디버깅, 통합 작업, 다른 살마에게 설명하는데 걸리는 시간 등의 요소를 반드시 포함해야 한다.
Expression.times()
@Test
public void testSumTimes(){
Expression fiveBucks = Money.dollar(5);
Expression tenFranc = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum = new Sum(fiveBucks, tenFranc).times(2);
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(20), result);
}
Sum
Expression times(int multiplier){
return new Sum(augend.times(multiplier), addend.times(multiplier));
}
- Sum에 times을 구현해보자.
- Expression 타입으로 지정된 augend, addend에 times 메서드가 없으므로 컴파일이 되지 않는다.
public interface Expression {
Expression times(int multiplier);
- Expression에 times 메서드 선언
- 이후 times를 구현한 메서드 접근자를 public으로 바꿔준다.
- 테스트는 통과된다
$5 +$5도 통과할까?
@Test
public void testPlusSameCurrencyReturnsMoney(){
Expression sum = Money.dollar(1).plus(Money.dollar(1));
assertTrue(sum instanceof Money);
}
- 실패한다.
- 인자가 Money일 경우에 인자의 통화를 확인하는 깔끔한 방법이 없다. 테스트를 삭제해주자.
우리는
- 미래에 코드를 읽을 다른 사람들을 염두에 둔 테스트를 작성했다.
- TDD와 여러분의 현재 개발 스타일을 비교해 볼 수 있는 실험 방법을 제시했다.
- 또 한 번 선언부에 대한 수정이 시스템 나머지 부분으로 번져갔고, 문제를 고치기 위해 역시 컴파일러의 조언을 따랐다.
- 잠시 실험을 시도했는데, 제대로 되지 않아서 버렸다.