Published on

[테스트 주도 개발] 다중통화를 지원하는 Money 객체

Authors
  • avatar
    Name
    Hyojeong Kim
    Twitter

테스트 주도 개발(TDD)의 대략적인 리듬

  1. 재빨리 테스트를 하나 추가한다.
  2. 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다.
  3. 코드를 조금 바꾼다.
  4. 모든 테스트를 실행하고 전부 성공하는지 확인한다.
  5. 리팩토링을 통해 중복을 제거한다.

1장. 다중 통화를 지원하는 Money 객체

해결해야 할 문제

  • 통화가 다른 두 금액을 더해서 주어진 환율에 맞게 변한 금액을 결과로 얻을 수 있어야 한다.
  • 어떤 금액(주가)을 어떤 수(주식의 수)에 곱한 금액을 결과로 얻을 수 있어야 한다.

작업 진행 방식

  1. 할일 목록을 작성한다.
    • 앞으로 어떤 일을 해야 하는지 알려주고,
    • 지금 하는 일에 집중할 수 있도록 도와주며,
    • 언제 일이 다 끝나는지 알려줄 수 있다.
  2. 작업을 시작한다.
    • 앞으로 할일 목록에 있는 한 항목에 대한 작업을 시작하면 그 항목을 이런 식으로 굵은 글씨체로 나타낼 것이다.
  3. 작업을 마친다.
    • 작업을 끝낸 항목에는 이런 식으로 줄을 긋도록 한다.
  4. 새로운 테스트를 할일 목록에 추가한다.
    • 할일 목록
    $5 + 10CHT = $10(환율이 2:1일 경우)
    **$5 X 2 = $10**
    
    -> 두 번째 할일 목록부터 진행하겠다는 의미
    • 테스트 후 객체를 만든다.
    • 테스트는 작은 것부터 시작한다.

TDD 주기 1 - 재빨리 테스트를 하나 추가한다.

  • 테스트를 작성할 때는 오퍼레이션의 완벽한 인터페이스에 대해 상상해보는 것이 좋다.
  • 가능한 최선의 API에서 시작해서 거꾸로 작업하는 것이 낫다.

위 문제를 해결하고자 '테스트'를 먼저 작성해보자.

public void testMultiplication(){
    Dollar five= new Dollar(5);
    five.times(2);
    assertEquals(10, five.amount);
}

/* 문제
- 공용 필드(public field)
- 금액을 계산하는 데 정수형을 사용하고 있음
- 컴파일 조차 되지 않음 등 등
*/
  • 할일목록 갱신
$5 + 10CH = $10(환율이 2:1일 경우)
**$5 X 2 = $10**
amount를 private로 만들기
Dollar 부작용(side effect)?
Money 반올림?

TDD 주기 2 - 모든 테스트를 실행해서 테스트가 실패하는 것을 확인한다.

  • 한 번에 하나씩 에러를 정복하자
  • 원인들을 목록화하여 진행하고 있는 업무의 진척율을 계산하자
- Dollar 클래스가 없음.
- 생성자가 없음.
- times(int) 메서드가 없음.
- amount 필드가 없음.

위의 문제들을 해결한 아래의 코드. 결론적으로 실패하는 테스트 코드.

func testMultiplication(){
    le five = Dollar(5)
    five.times(2);
    XCTAssertEqual(10, five.amount);
}

class Dollar {
    var amount: Int!
    init(_ amount : Int){}
    func times(_ multiplier: Int){}
}
  • 10을 기대했지만 결과는 0
  • 하지만 우리 문제는 '다중 통화 구현'에서 '이 테스트를 통과시킨 후 나머지 테스트들도 통과시키기'로 변형됨
    • 실패에 대한 구체적인 척도를 갖게 된 것.

TDD 주기 3 - 조금 수정한다.

class Dollar {
    var amount: Int! = 10 //수정부분
    init(_ amount: Int) {}
    func times(_ multiplier: Int) {}
}

TDD 주기 4 - 모든 테스트를 실행해서 테스트가 성공하는 것을 확인한다.

위와 같이 amount를 수정하면 XCTAssertEqual(10, five.amount)테스트는 성공한다.

TDD 주기 5 - 중복을 제거하기 위해 리팩토링을한다.

의존성과 중복

  • 스티브 프리만은 테스트와 코드 간의 문제는 중복이 아님을 지적한 바 있다.
  • 문제는 테스와 코드 사이에 존재하는 의존성(dependency).
    • 코드나 테스트 중 한쪽을 수정하면 다른 한쪽도 수정해야만 한다는 것.
    • 우리의 목표는 코드를 바꾸지 않으면서도 의미 있는 테스트를 하나 더 작성하는 것인데, 현재의 구현으로는 불가능.
  • 의존성이 문제 그 자체라면 중복(duplication)은 문제의 징후.
    • 중복의 가장 흔한 예는 로직의 중복. (동일한 문장이 코드의 여러 장소에 나타나는 것)
    • 중복된 로직을 하나로 끄집어내는 일엔 객체를 이용하는 것이 최고!
  • 중복만 제거해주면 의존성도 제거된다.
    • 다음 테스트로 진행하기 전에 중복을 제거함으로써, 오직 한 가지(one and only one)의 코드 수정을 통해 다음 테스트도 통과되게 만들 기능성을 최대화 하기!
  • 이제 중복을 제거할 차례!
    • 이번 경우엔 중복이 테스트에 있는 데이터와 코드에 있는 데이터 사이에 존재한다.
int amount =  5 * 2;

위와 관련한 중복된 로직을 모두 제거해야 한다.

class Dollar {
    var amount: Int!
    init(_ amount: Int) {
			self.amount = amount
		}
    func times(_ multiplier: Int) {
        amount *= multiplier //수정부분
    }
}

지금까지 한일 정리

  • 우리는 알고 있는 작업해야 할 테스트 목록을 만들었다.
  • 오퍼레이션이 외부에서 어떻게 보이길 원하는지 말해주는 이야기를 코드로 표현했다.
  • JUnit에 대한 상세한 사항들은 잠시 무시하기로 했다.
  • 스텁 구현을 통해 테스트를 컴파일했다.
  • 끔찍한 죄악을 범하여 테스트를 통과시켰다.
  • 돌아가는 코드에서 상수를 변수로 변경하여 점진적으로 일반화했다.
  • 새로운 할일들을 한번에 처리하는 대신 할일 목록에 추가하고 넘어갔다.