Contents
테스트 주도 개발 4주차
   Nov 6, 2022     9 min read

스터디 4주차(22.10.28 ~ 22.11.05)

15장. 서로 다른 통화 더하기


할일 목록 :

  • $5 + 10CHF = $10(환율이 2 : 1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)

  • $5 + 10CHF에 대한 테스트 추가
public void testMixedAddition(){
  Expression fiveBusks = Money.dollar(5);
  Expression tenFranc = Money.dollar(10);
  Bank bank = new Bank();
  bank.addRate("CHF", "USD", 2);

  Money result = bank.reduce(fiveBucks.plus(tenFranc), "USD");
  assertEquasl(Money.dollar(10), result);
  }

앞으로 나아가는 두 가지 선택 경로

  1. 좁은 범위의 한정적인 테스트를 빠르게 작성한 후에 일반화하는 방법
  2. 모든 실수를 컴파일러가 잡아줄 거라 믿고 진행하는 방법
public void testMixedAddition(){
    Money fiveBucks = Money.dollar(5);
    Money tenFrancs = Money.franc(10);
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);
    Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");
    assertEquals(Money.dollar(10), result);
}

테스가 실패한다.

  • 나온 값 : 15USD
  • 예측 값 : 10USD

Sum.reduce()가 인자를 축약하지 않는 것 같아 보인다.

//Sum
public Money reduce(Bank bank, String to){
    int amount = augend.amount + addend.amount;
    return new Moeny(amount, to);
}
  • 두 인자를 모도 축약하면 테스트 통과
//Sum
public Money reduce(Bank bank, String to){
    int amount = augend.reduce(bank, to).amount + addend.reduce(bank, to).amount;
    return new Moeny(amount, to);
}

결과는 통과

Expression이어야 하는 Money들을 조금씩 쪼아서 없앨 수 있다.

//Sum
Expression augend;
Expression addend;

Sum 생성자의 인자 역시 Expresion일 수 있다.

//Sum
Sum(Expression augend, Expression addend){
   this.augend = augend;
   this.addend = addend;
}

Sum이 컴포지트(Composite)패턴을 상기 시킨다.

하지만 일반화 시킬만큼 강하지는 않다.

만약 Sum이 둘 이상의 인자를 갖는 순간이 온다면 그땐 바꾸게 될 것이다.

Money 에서 plus()의 인자가 Expression으로 취급될 수 있다.

//Money
Expression plus(Expression addend){
    return new Sum(this, addend);
}

times()의 반환 값도 Expression일 수 있다.

//Money
Expression times(int multiplier){
    return new Money(amount * multiplier, currency);
}

위 코드는 Expression이 plus()와 times() 오퍼레이션을 포함해야 함을 제안하고 있다.

  • plus()의 인자도 바꿀 수 있다. MoneyExpression 으로 변경
public void testMixedAddition(){
    Money fiveBucks = Money.dollar(5);
    Expression tenFrancs = Money.franc(10);
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);
    Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");
    assertEquals(Money.dollar(10), result);
}
  • fiveBucksExpression으로 바꾸고 나면 몇 군데를 같이 수정해야 한다.
public void testMixedAddition(){
    Expression fiveBucks = Money.dollar(5);
    Expression tenFrancs = Money.franc(10);
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);
    Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");
    assertEquals(Money.dollar(10), result);
}
  • 컴파일러가 Expression에 plus()가 정의되지 않았다고 알려준다.
//Expression
Expression plus(Expression addend);
  • Money와 Sum을 추가해야한다.
//Money
public Expression plus(Expression addend){
    return new Sum(this, addend);
}

//Sum : Sum의 구현을 스텁 구현으로 바꾸고, 할일 목록 추가하자.
public Expression plus(Expression addend){
    return null;
}

할일 목록 : Sum.plus, Expression.times 추가

  • $5 + 10CHF = $10(환율이 2 : 1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times

Money를 Expression으로 일반화하는 작업을 마무리할 준비가 되었다.

15장 정리

  • 원하는 테스트를 작성하고, 한 단계에 달성할 수 있도록 뒤로 물렀다.
  • 조금 더 추상적인 선언을 통해 가지에서뿌리로 일반화했다.
  • 변경 후(Expresion fiveBucks), 그 영향을 받은 다른 부분들을 변경하기 위해 컴파일러의 지시를 따랐다.(Expression에 plus()를 추가하기 등..)

16장. 드디어 추상화


할일 목록

  • $5 + 10CHF = $10(환율이 2 : 1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times

Expression.plus를 끝마치려면 Sum.plus()를 구현해야 한다.

그리고

Expresion.times()를 구현하면 전체 예제가 끝난다.

  • Sum.plus() 테스트
public void testSumPlusMoney(){
    Expression fiveBucks = Money.dollar(5);
    Expression tenFrancs = Money.franc(10);
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);
    Expression sum = new Sum(fiveBucks, tenFrancs.plus(fiveBucks);
    Money result = bank.reduce(sum, "USD");
    assertEquals(Money.dollar(15), result);
}
//Sum
public Expression plus(Expression addend){
    return new Sum(this, addend);
}

할일 목록 : Sum.plus 완료

  • $5 + 10CHF = $10(환율이 2 : 1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times

TDD로 구현할 땐 테스트 코드의 줄 수와 모델 코드의 줄 수가 거의 비슷한 상태로 끝난다.

TDD가 경제적이기 위해서는 매일 만들어 내는 코드의 줄 수가 두 배가 되거나 동일한 기능을 구현하되 절반의 줄 수 로 해내야 할 것이다.

TDD가 자신의 방법에 비해 어떻게 다른지 직접 측정해 보아야 할 것이다. 이때 디버깅, 통합 작업, 다른 사람에게 설명하는 데 걸리는 시간 등의 요소를 반드시 포함해야 한다는 것을 기억하라고 한다.

Expression.times()

일단 Sum.times() 가 작동하게 만들 수 있만 있다면 Expression.times()를 선언하는 일이야 어렵지 않을 것이다.

public void testSumTimes() {
    Expression fiveBucks = Money.dollar(5);
    Expression tensFrancs = Money.franc(10);
    Bank bank = new Bank();
    bank.addRate("CHF", "USD", 2);

    Expression sum = new Sum(fiveBucks, tenFrancs).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));
}

15장에서 피가산수(augend)와 가수(addend)를 Expression으로 추상화했기 때문에 코드가 컴파일되게 만들려면 Expression에 times()를 선언해야 한다.

//Expression
Expression times(int multiplier);

Money.times()Sum.times() 의 가시성을 높여줘야 한다.

//Sum
public Expression times(int multiplier){
    return new Sum(augend.times(multiplier), addend.times(multiplier);
}
//Money
public Expression times(int multiplier) {
    return new Money(amount * multiplier, currency);
}

할일 목록 : Expression.times 삭제

  • $5 + 10CHF = $10(환율이 2 : 1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times

테스트 통과

이제 마지막으로 $5 + $5에서 Money반환하기를 하면 끝이난다.

public void testPlusSameCurrencyReturnMoney(){
    Expression sum = Money.dollar(1).plus(Money.dollar(1));
    assertTrue(sum instanceof Money);
}

위 테스트트 외부에서 드러나는 객체의 행위에 대한 것이 아니라 구현 중심이기 때문에 지저분하다.

//Money
public Expression plus(Expression addend){
    return new Sum(this, addend);
}

할일 목록 : $5 + $5에서 Money 반환하기 - 삭제

  • $5 + 10CHF = $10(환율이 2 : 1일 경우)
  • $5 + $5 = $10
  • $5 + $5에서 Money 반환하기
  • Bank.reduce(Money)
  • Money에 대한 통화 변환을 수행하는 Reduce
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times

16장 정리

  • 나중에 코드를 읽는 다른 사람들을 생각하여 테스트를 작성함
  • TDD와 현재 개발 스타일을 비교할 수 있는 실험 방법을 제시했다.