타락한 객체

Gooreum·2021년 12월 11일
0

TDD

목록 보기
3/3

💡일반적인 TDD 주기


  1. 테스트를 작성한다.
    • 마음속에 있는 오퍼레이션이 코드에 어떤 식으로 나타나길 원하는지 생각해보라.
    • 이야기를 써내려가는 것이다.
    • 원하는 인터페이스를 개발하라.
    • 올바른 답을 얻기 위해 필요한 이야기의 모든 요소를 포함시켜라.
  2. 실행 가능하게 만든다.
    • 다른 무엇보다도 중요한 것은 빨리 초록 막대를 보는 것이다.
    • 깔끔하고 단순한 해법이 명백히 보인다면 그것을 입력하라.
    • 만약 깔끔하고 단순한 해법이 있지만 구현하는 데 몇 분 정도 걸릴 것 같으면 일단 적어 놓은 뒤에 원래 문제(초록 막대를 보는 것)로 돌아오자.
  3. 올바르게 만든다.
    • 이제 시스템이 작동하므로 직전에 저질렀던 죄악을 수습하자.
    • 좁고 올곧은 소프트웨어 정의의 길로 되돌아와서 중복을 제거하고 초록 막대로 되돌리자.

💡깔끔한 코드를 얻는 것이 목적


  • 나누어 정복하기(divide and conquer)
    • 처음부터 '작동하는 깔금한 코드'를 얻을 순 없다.
    • 처음엔 '작동하는'에 해당하는 부분을 먼저 해결하고 나서 '깔끔한 코드' 부분을 해결한다.
    • 이러한 접근 방식은 '깔끔한 코드' 부분을 먼저 해결한 후에, '작동하는' 부분을 해결해 가면서 배운 것들을 설계에 반영하는 '아키텍처 주도 개발(architecture-driven development)'과 정반대다.

💡할일목록 - Dollar 부작용 해결하기


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

    • 1장에서 2번째 할일목록 테스트를 통과하긴 했지만, Dollar 부작용 문제가 있다.
    • Dollar에 대한 연산을 수행한 후에 해당 Dollar 값이 바뀐다.
  • 테스트 코드를 추가해보자.

    func testMultiplication() {
            let five = Dollar(5)
            five.times(2)
            XCTAssertEqual(10, five.amount)
            five.times(3)
            XCTAssertEqual(15, five.amount) -> 테스트 실패
    }
    • times() 를 처음 호출한 이후에 five는 더 이상 5가 아니다.

    • times() 에서 새로운 객체를 반환하는 방식으로 문제를 해결해보자.
      - Dollar 인터페이스를 수정해야 하고, 테스트도 수정해야 한다.
      - 그러나 문제될 것은 없다. 어떤 구현이 올바른가에 대한 우리 추측이 완벽하지 못한 것과 마찬가지로 올바른 인터페이스에 대한 추측 역시 절대 완벽하지 못하다.

      func testMultiplication() {
              let five = Dollar(5)
              var product = five.times(2)
              XCTAssertEqual(10, product.amount)
              product = five.times(3)
              XCTAssertEqual(15, product.amount)
      }
    • Dollar의 times() 타입이 Dollar가 아니므로 컴파일이 되지 않는다.

      class Dollar {
          var amount: Int!
          init(_ amount: Int) {
              self.amount = amount
          }
          func times(_ multiplier: Int) -> Dollar? {
      				amount *= multiplier
              return nil
          }
      }
    • 컴파일 되지 않는 문제를 해결하기 위해 Dollar 인터페이스를 수정했다.

    • 하지만 실행되지 않는다. 그래도 한 걸음 나아간 것이다!

    • 테스트를 통과하기 위해서는 올바른 금액을 갖는 새 Dollar를 반환해야 한다.

      class Dollar {
          var amount: Int!
          init(_ amount: Int) {
              self.amount = amount
          }
          func times(_ multiplier: Int) -> Dollar {
              return Dollar(amount * multiplier)
          }
      }
  • 할일 목록 삭제하기

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

💡빠른 초록 막대 보기 위한 세 가지 전략


  1. 가짜로 구현하기
    • 상수를 반환하게 만들고 진짜 코드를 얻을 때까지 단계적으로 상수를 변수로 바꾸어 간다.
    • 1장에서는 테스트를 통과하기 위해 일단 가짜 구현으로 시작해서 점차 실제 구현을 만들어갔다.
  2. 명백한 구현 사용하기
    • 실제 구현을 입력한다.
    • 2장에서는 올바른 구현이라고 생각한 내용을 입력한 후 테스트를 실행했다.
  3. 삼각측량(triangulation)
    • 3장에서 보고 다시 정리하자.
  • 위 전략을 실무에서 TDD 적용
    • 저자는 1,2 번째 방법을 실무에서 번걸아가며 사용한다고 한다고 한다.
    • 모든 일이 자연스럽게 잘 진행되고 내가 뭘 입력해야 할지 알 때는 명백한 구현을 계속 더해 나간다.
    • 예상치 못한 빨간 막대를 만나게 되면 가짜로 구현하기 방법을 사용하면서 올바른 코드로 리팩토링한다.
    • 그러다 자신감을 찾으면 명백한 구현 사용하기 모드로 돌아온다.

💡지금까지 배운 3가지 내용 정리


  1. 설계상의 결함(Dollar 부작용)을 그 결함으로 인해 실패하는 테스트로 변환했다.
  2. 스텁 구현으로 빠르게 컴파일을 통과하도록 만들었다.
  3. 올바르다고 생각하는 코드를 입력하여 테스트를 통과했다.
  • 느낌(부작용에 대한 혐오감)을 테스트(하나의 Dollar 객체에 곱하기를 두 번수행하는 것)로 변환하는 것은 TDD의 일반적 주제다.
  • 이런 작업을 오래 할수록 미적 판단을 테스트로 담아내는 것에 점점 익숙해지게 된다.
  • 이걸 할 수 있을 때, 설계 논의는 훨씬 더 흥미로워진다.
  • 우선 시스템이 이런 식으로 동작해야 하는지 저런 식으로 동작해야 하는지 논의할 수 있다.
  • 일단 올바른 행위에 대해 결정을 내린 후에, 그 행위를 얻어낼 수 있는 최상의 방법에 대해 이야기할 수 있다.
profile
하루하루 꾸준히

0개의 댓글