Eternity's Chit-Chat

aeternum.egloos.com



유연한 설계를 위한 패턴과 원리 - 2.의도를 명확하게 6부 Supple Design

리팩토링 시간이다. Money equals() 메소드에서 환율을 비교하는 부분을 새로 추가한 isCurrencyEquals() 대체할 있다. 전체 단위 테스트를 모두 실행시켜 깨진 부분이 없는지 확인하는 것을 잊지 말자. 변경 회귀 테스트를 실행시켜 전체 프로그램의 품질을 유지하는 것이 중요하다.

 

Money.java

public boolean equals(Object object) {

  ......

  Money other = (Money) object;

  return getAmount().equals(other.getAmount()) &&

    isCurrencyEquals(other.getCurrency());

}

 

이제 USD KRW 환전하는 기능에 대한 테스트 케이스를 추가하자. ChangeBooth 환율을 추가하고 미달러화를 원화로 환전한다.

 

ChangeBoothTest.java 

@Test

public void exchangeUSD_KRW() {

  ChangeBooth changeBooth = new ChangeBooth();

  changeBooth.addExchangeRate(Money.USD, Money.KRW, 1027);

  assertEquals(Money.wons(1027), changeBooth.exchange(Money.dollars(1), Money.KRW));

}

 

테스트를 한번에 통과시키기는 어려울 같다. 일단 테스트 케이스에 @Ignore 어노테이션을 붙여 테스트가 실행되지 않도록 하고 테스트를 세분화하자. 환율을 계산하기 위해서는 통화별로 환율을 저장하고 다시 해당 환율을 얻는 과정이 필요하다. 따라서 다음과 같이 통화에 대해 정확한 환율이 얻어지는 지부터 먼저 확인하도록 하자.

 

ChangeBoothTest.java  

@Test

public void getCorrectExchangeRate() {

  ChangeBooth changeBooth = new ChangeBooth();

  changeBooth.addExchangeRate(Money.USD, Money.KRW, 1027);

  assertEquals(1027, changeBooth.getExchangeRate(Money.USD, Money.KRW), 0);

}

 

ChangeBooth내에 통화 간의 환율이 중복해서 존재해서는 안되기 때문에 ExchangeRate Set 보관하기로 결정했다. 내부 컬렉션을 캡슐화하기 위해 COLLECTION ACCESSOR METHOD addExchangeRate() 추가하자. getExchangeRate() 메소드는 Set 요소를 순환하면서 기준 통화와 대상 통화를 포함하는 ExchangeRate 찾은 환율을 반환한다. getExchangeRate() 메소드는 테스트에서만 사용 가능하도록 public 가시성 대신 기본 가시성을 부여한다.

 

ChangeBooth.java 

private Set<ExchangeRate> rates = new HashSet<ExchangeRate>();

 

public void addExchangeRate(Currency from, Currency to, double rate) {

  rates.add(ExchangeRate.of(from, to, rate));

}

 

double getExchangeRate(Currency from, Currency to) {

  for(ExchangeRate rate : rates) {

    if (rate.isAcceptable(from, to)) {

      return rate.getRate();

    }

  }

 
return 0;

}

 

단지 테스트를 위해 기본 가시성을 가진 메소드를 추가하는 것이 눈에 거슬릴 수도 있다. 여기에서 잠시 Ron Jeffries 견해를 들어 보자.

 

만약 클래스를 테스트 가능하도록하기 위해 메소드를 추가하는 것이 유용하다면, 나는 그렇게 한다. 이런 경우가 가끔씩 발생하는데, 예를 들면 인터페이스는 간단하지만 내부 기능이 복잡한 경우(아마도 EXTRACT CLASS 리팩토링의 필요성을 느끼기 시작할 ) 이에 속한다.

- Ron Jeffries, JUint in Action