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

Gooreum·2021년 12월 11일
0

TDD

목록 보기
2/3
post-thumbnail

💡화폐 예제


  • 1부에서는 완전히 테스트에 의해 주도되는 전형적 모델 코드를 개발할 것이다.
  • 1부의 목표는 테스트 주도 개발(TDD)의 주기를 보도록 하는 것이다.
    1. 재빨리 테스트를 하나 추가한다.
    2. 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다.
    3. 코드를 조금 바꾼다.
    4. 모든 테스트를 실행하고 전부 성공하는지 확인한다.
    5. 리팩토링을 통해 중복을 제거한다.
  • 이 리듬을 느끼면 다음 내용들을 확인할 수 있다.
    • 각각의 테스트가 기능의 작은 증가분을 어떻게 커버하는지
    • 새 테스트를 돌아가게 하기 위해 얼마나 작고 못생긴 변화가 가능한지
    • 얼마나 자주 테스트를 실행하는지
    • 얼마나 수 없이 작은 단계를 통해 리팩토링이 되어가는지

💡Money 객체 구성하기


  • 다중 통화를 지원하는 보고서
    종목가격합계
    IBM100025USD25000USD
    Novartis400150CHF60000CHF
    합계65000USD
  • 환율
    기준변환환율
    CHFUSD1.5
  • 문제
    • 다중 통화를 지원하는 보고서를 만들어야 하는데, 어떤 테스트들이 있어야 보고서에 제대로 계산 되도록 하는 코드가 완성되었는지 확실할 수 있는가.
      • 통화가 다른 두 금액을 더해서 주어진 환율에 맞게 변한 금액을 결과로 얻을 수 있어야 한다.
      • 어떤 금액(주가)을 어떤 수(주식의 수)에 곱한 금액을 결과로 얻을 수 있어야 한다.

💡작업진행 방식


1.할일 목록을 작성한다.

  • 앞으로 어떤 일을 해야 하는지 알려주고,
  • 지금 하는 일에 집중할 수 있도록 도와주며,
  • 언제 일이 다 끝나는지 알려줄 수 있다.

2.작업을 시작한다.

  • 앞으로 할일 목록에 있는 한 항목에 대한 작업을 시작하면 그 항목을 이런 식으로 굵은 글씨체로 나타낼 것이다.

3.작업을 마친다.

  • 작업을 끝낸 항목에는 이런 식으로 줄을 긋도록 한다.

4.새로운 테스트를 할일 목록에 추가한다.

  • 할일 목록

    $5 + 10CHF = $10(환율이 2:1일 경우)
    **$5 X 2 = $10****

    → 두번째 할일 목록부터 진행하겠다는 의미

  • 테스트후 객체를 만든다.

    • 테스트는 객체를 만들면서 시작하는게 아니라 테스트를 먼저 만들어야 한다!
  • 테스트는 작은 것부터 시작한다.

    • 두번째 할일 목록부터 시작해보자.

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


  • 테스트를 작성할 때는 오퍼레이션의 완벽한 인터페이스에 대해 상상해보는 것이 좋다.

  • 우리는 지금 오퍼레이션이 외부에서 어떤 식으로 보일지에 대한 이야기를 테스트 코드에 적고 있는 것이다.

    • ViewController가 ViewModel 메서드를 실제 사용하는 상황을 그대로 적용해야 한다는 의미인듯.
  • 가능한 최선의 API에서 시작해서 거꾸로 작업하는 것이 애초부터 일을 복잡하고 보기 흉하며 '현실적'이게 하는 것 보다 낫다.

  • 간단한 곱셈 예

    public func testMultiplication() { 
    	let five = Dollar(5)
    	five.times(2)
    	XCTAssertEqual(10, five.amount)
    }
    • 컴파일 조차 되지 않는다.
    • 공용 필드(public field)에데가, 예기치 못한 부작용이 있을 수 있으며, 금액을 계산하는 데 정수형을 사용한다.
    • 하지만 중요한 것은 이렇게 작은 단계로 시작하는 것이다.
    • 위의 문제점들을 할일 목록에 적어놓다.
    • 지금 우리에겐 실패하는 테스트가 주어진 상태고 최대한 빨리 초록 막대를 보고 싶을 뿐이다.
  • 할일목록 갱신

$5 + 10CHF = $10(환율이 2:1일 경우)
**$5 X 2 = $10**
amount를 private으로 만들기
Dollar 부작용
Money 반올림?

💡TDD 주기 2 - 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다.


  • 실패 정복하기
    • 위의 테스트 코드에서 컴파일 되지 않는 원인들을 작성하고 하나씩 정복해 나가자.
      • 원인들을 리스트업 해서 하나씩 정복해나가면 진행하고 있는 업무의 진척율을 계산할 수 있다.
- Dollar 클래스가 없음.
- 생성자가 없음.
- times(int) 메서드가 없음.
- amount 필드가 없음.
  • Dollar 클래스를 정의하여 에러 하나를 없애자
  • 생성자를 만들자
  • times()의 스텁 구현
  • amount 필드 추가
Dollar
class Dollar { 
	var amount: Int!
	init(_ amount: Int) { }
	func times(_ multiplier: Int) { }
}
  • 실패에 대한 구체적인 척도
    • 위 테스트 코드 결과는 실패!
      • 10을 기대했지만 결과는 nil이다.
      • 그러나 중요한 것은 이제 실패에 대한 구체적인 척도를 갖게 되었다는 점이다.
        • 막연히 실패했다는 사실만 아는 것보다 나아진 것이다.
      • 우리 문제는 '다중 통화 구현'에서 '이 테스트를 통과시킨 후 나머지 테스트들도 통과시키기'로 변형된 것이다.
        • 훨씬 간단해졌으며, 범위도 훨씬 적어서 걱정이 줄었다.

💡TDD 주기 3 - 코드를 조금 바꾼다.


  • 조금 수정한다.
    class Dollar {
        var amount: Int! = 10 //수정부분
        init(_ amount: Int) {}
        func times(_ multiplier: Int) {}
    }

💡TDD 주기 4 - 모든 테스트를 실행하고 전부 성공하는지 확인한다.


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

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


💡 의존성과 중복
스티브 프리만(Steve Freeman)은 테스트와 코드 간의 문제는 중복이 아님을 지적하였다. 문제는 테스트와 코드 사이에 존재하는 의존성이다. 즉, 코드나 테스트 중 한쪽을 수정하면 반드시 다른 한쪽도 수정해야만 한다는 것이다.
우리의 목표는 코드를 바꾸지 않으면서도 뭔가 의미 있는 테스트를 하나 더 작성하는 것인데, 현재의 구현으로는 불가능하다.

의존성은 소프트웨어 개발의 모든 부분에서 핵심적인 문제다. 만약 특정 데이터베이스 벤더가 제공하는 세세한 기능들을 코드 여기저기에서 사용하는 상황에서 다른 벤더의 제품으로 변경하고자 한다면 코드가 해당 벤더에 대해 의존성을 갖는다는 사실을 알게 될 것이다.

의존성이 문제 그 자체라면 중복(duplication)은 문제의 징후다. 중복의 가장 흔한 예는 로직의 중복이다. 중복된 로직이란 동일한 문장이 코드의 여러 장소에 나타나는 것을 의미한다. 중복된 로직을 하나로 끄집어내는 일엔 객체를 이용하는 것이 최고다.

문제 자체는 남겨둔 채로 징후만을 제거하면 다른 어딘가에서 최악의 형태로 문제가 드러나곤 하는 현실 세계의 일반적인 양상과는 달리, 프로그램에서는 중복만 제거해 주면 의존성도 제거된다. 이게 바로 TDD의 두번째 규칙이 존재하는 이유다. 다음 테스트로 진행하기 전에 중복을 제거함으로써, 오직 한 가지 (one and only one)의 코드 수정을 통해 다음 테스트도 통과하게 만들 가능성을 최대화하는 것이다.

  • 지금까지 TDD 주기의 1번부터 4번 항목까지를 수행했으므로, 이제 중복을 제거할 차례다.

  • 데이터도 중복으로 간주한다.

    • 보통 중복을 찾기 위해 코드를 비교할 테지만, 이번 경우엔 중복이 '테스트에 있는 데이터'와 '코드에 있는 데이터' 사이에 존재한다.

      class Dollar {
      	var amount = 5 * 2
      }
    • amount = 10amount = 5 * 2로 수정하였다. (원래 10은 5와 2의 곱이니..)

    • 테스트 코드에 보면 5와 2가 있고, Dollar 클래스에도 5와 2가 있으므로 중복되는 데이터가 된다.

  • 중복을 제거해보자

    ```swift
    class Dollar {
        var amount: Int!
        init(_ amount: Int) {}
        func times(_ multiplier: Int) {
            amount = 5 * 2 //수정부분
        }
    }
    ```
    
    - 5와 2를 한 번에 제거할 수 있는 방법은 없지만, 객체의 초기화 단계에 있는 설정 코드를 `times()` 메서드 안으로 옮겨보자.
    - 테스트는 통과될 것이고, 테스트 막대 역시 초록색이다.
    - 이러한 단계가 작아보이지만 TDD의 핵심은 이런 작은 단계를 밟아야 한다는 것이 아니라, **이런 작은 단계를 밟을 능력을 갖추어야 한다는 것**이다.
    
    ```swift
    class Dollar {
        var amount: Int!
        init(_ amount: Int) {
    			self.amount = amount	//수정부분
    		}
        func times(_ multiplier: Int) {
            amount = amount * 2 //수정부분
        }
    }
    ```
    
    - 5는 테스트 코드의 생성자에서 넘어오는 값이니 amount 변수에 저장해보자.
    
    ```swift
    class Dollar {
        var amount: Int!
        init(_ amount: Int) {
    			self.amount = amount	
    		}
        func times(_ multiplier: Int) {
            amount = amount * multiplier //수정부분
        }
    }
    ```
    
    - 테스트 코드에서 인자 multiplier의 값이 2이므로, 상수를 이 인자로 대체할 수 있다.
    
    ```swift
    class Dollar {
        var amount: Int!
        init(_ amount: Int) {
    			self.amount = amount	
    		}
        func times(_ multiplier: Int) {
            amount *= multiplier //수정부분
        }
    }
    ```
    
    - Dollar 클래스의 중복 제거를 위해 `amount = amount * multiplier`의 중복되는 부분도 제거하자

💡할일목록 완료 표시하기


$5 + 10CHF = $10(환율이 2:1일 경우)
$5 X 2 = $10(완료)
amount를 private으로 만들기
Dollar 부작용
Money 반올림?

💡지금까지 한일 정리하기


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

0개의 댓글