3장에서 equals()를 만든 덕에 뜻밖의 이득이 생겼다. 이제 곱셈 테스트가 결과의 amount를 꺼내 볼 필요가 없다. 두 Dollar를 값으로 통째로 비교하면 된다. 테스트를 이렇게 고치고 나면 amount를 바깥에 열어둘 이유가 사라진다 — private으로 감출 수 있다.

새로 만든 기능이 테스트를 개선한다

TDD의 재미있는 순환이다. equals라는 새 코드를 쓰기 위해 테스트를 썼고, 그렇게 만들어진 equals가 이번엔 곱셈 테스트를 더 깔끔하게 만든다. 테스트가 구현 세부(amount 필드)에 덜 기대게 되어, 나중에 내부 구조를 바꿀 자유가 커진다.

Refactoring Step 리팩터링 — 곱셈 테스트가 equals로 비교하게 바꾼다
1/2
@Testpublic void testMultiplication() {    Dollar five = new Dollar(5);    assertEquals(new Dollar(10), five.times(2));    assertEquals(new Dollar(15), five.times(3));   // product 변수도 amount 접근도 사라졌다}@Testpublic void testEquality() {    assertTrue(new Dollar(5).equals(new Dollar(5)));    assertFalse(new Dollar(5).equals(new Dollar(6)));}class Dollar {    int amount;    Dollar(int amount) {        this.amount = amount;    }    Dollar times(int multiplier) {        return new Dollar(amount * multiplier);    }    public boolean equals(Object object) {        Dollar dollar = (Dollar) object;        return amount == dollar.amount;    }}

assertEquals(new Dollar(10), five.times(2))는 amount를 꺼내지 않는다. 테스트가 amount에 손대지 않게 되자 그제서야 private int amount로 바꿀 수 있다. equals는 같은 Dollar 클래스 안이므로 다른 인스턴스의 private 필드에 여전히 접근한다.

판단 기준: 캡슐화를 먼저 하려 서두르지 않는다. 테스트가 그 필드를 안 읽게 만든 다음에 감추면, 테스트를 깨지 않고 자연스럽게 닫힌다. 함정: 여기서 equals와 times가 서로 의존하기 시작했다. equals가 틀리면 곱셈 테스트도 덩달아 무너진다. 이 결합은 지금은 감수하되 목록에 적어 둔다.

이 장이 남긴 할 일 목록

  • amount를 public으로 노출 중
  • equals(null), equals(다른 객체) 처리
  • hashCode()
  • times가 Dollar를 반환 — 통화가 섞이면? → Franc이 필요하다 → 5장
  • 10 (환율 2:1)

다음장으로 5장