- Published on
[테스트 주도 개발] 다중통화를 지원하는 Money 객체
- Authors
- Name
- Hyojeong Kim
테스트 주도 개발(TDD)의 대략적인 리듬
- 재빨리 테스트를 하나 추가한다.
- 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다.
- 코드를 조금 바꾼다.
- 모든 테스트를 실행하고 전부 성공하는지 확인한다.
- 리팩토링을 통해 중복을 제거한다.
1장. 다중 통화를 지원하는 Money 객체
해결해야 할 문제
- 통화가 다른 두 금액을 더해서 주어진 환율에 맞게 변한 금액을 결과로 얻을 수 있어야 한다.
- 어떤 금액(주가)을 어떤 수(주식의 수)에 곱한 금액을 결과로 얻을 수 있어야 한다.
작업 진행 방식
- 할일 목록을 작성한다.
- 앞으로 어떤 일을 해야 하는지 알려주고,
- 지금 하는 일에 집중할 수 있도록 도와주며,
- 언제 일이 다 끝나는지 알려줄 수 있다.
- 작업을 시작한다.
- 앞으로 할일 목록에 있는 한 항목에 대한 작업을 시작하면 그 항목을 이런 식으로 굵은 글씨체로 나타낼 것이다.
- 작업을 마친다.
- 작업을 끝낸 항목에는
이런 식으로줄을 긋도록 한다.
- 작업을 끝낸 항목에는
- 새로운 테스트를 할일 목록에 추가한다.
- 할일 목록
-> 두 번째 할일 목록부터 진행하겠다는 의미$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에 대한 상세한 사항들은 잠시 무시하기로 했다.
- 스텁 구현을 통해 테스트를 컴파일했다.
- 끔찍한 죄악을 범하여 테스트를 통과시켰다.
- 돌아가는 코드에서 상수를 변수로 변경하여 점진적으로 일반화했다.
- 새로운 할일들을 한번에 처리하는 대신 할일 목록에 추가하고 넘어갔다.