TDD 1.0 Help

8장. 객체 만들기

할일 목록
  • $5 + 10CHF = $10 (환율이 2:1로 가정)

  • $5 x 2 = $10

  • amount를 private으로 만들기

  • Dollar side effect?

  • Money 반올림?

  • equals()

  • hashCode()

  • Equal null

  • Equal object

  • 5CHF x 2 = 10CHF

  • Dollar/Franc 중복

  • 공용 equals

  • 공용 times

  • Franc과 Dollar 비교하기

  • 통화?

현재 상태로는 Dollar와 Franc 두 times 구현이 거의 같다.

class Dollar(amount: Int) : Money(amount) { fun times(multiplier: Int): Dollar { return Dollar(amount * multiplier) } } class Franc(amount: Int) : Money(amount) { fun times(multiplier: Int): Franc { return Franc(amount * multiplier) } }

이 코드를 양쪽 모두 Money를 반환하게 만들면 더 비슷하게 만들 수 있다.

class Dollar(amount: Int) : Money(amount) { fun times(multiplier: Int): Money { return Dollar(amount * multiplier) } } class Franc(amount: Int) : Money(amount) { fun times(multiplier: Int): Money { return Franc(amount * multiplier) } }

다음 단계로 뭘 해야 할지 명확하지 않다. Money의 두 하위 클래스는 별로 하는 일이 없어보여 제거해버리고 싶지만 한 번에 그렇게 큰 단계를 밟는 것은 TDD를 효과적으로 보여주기에 적합하지 않아보인다.

따라서 하위 클래스에 대한 직접적인 참조가 적어진다면 하위 클래스를 제거할 수 있지 않을까? Money에 Dollar를 반환하는 팩토리 메서드를 도입할 수도 있겠다.

@kotlin.test.Test fun testMultiplication() { val five: Money = Money.dollar(5) assertEquals(Dollar(10), five.times(2)) assertEquals(Dollar(15), five.times(3)) }
open class Money(var amount: Int) { override fun equals(other: Any?): Boolean { return amount == (other as Money).amount && javaClass == other.javaClass } companion object { fun dollar(amount: Int): Dollar { return Dollar(amount) } } }

컴파일러가 Money에는 times()가 정의되지 않았다는 사실을 알려준다. 아직 준비가 안되어있기 때문에 Money를 추상클래스로 변경한 후 timse()를 선언하자.

abstract class Money(var amount: Int) { abstract fun times(multiplier: Int): Money }

이제 팩토리 메서드의 선언을 바꿀 수 있다.

fun dollar(amount: Int): Money { return Dollar(amount) }

기존 하위 클래스에 구현해두었던 Dollar와 Franc의 times에 override만 붙여주면 테스트가 정상적으로 작동하는 것을 볼 수 있다. 이제 팩토리 메서드를 테스트 코드의 나머지 모든 곳에서 사용할 수 있다.

Franc도 Dollar와 유사하게 구현하여 테스트코드에 팩토리 메서드를 반영해보자.

@kotlin.test.Test fun testMultiplication() { val five: Money = Money.dollar(5) assertEquals(Money.dollar(10), five.times(2)) assertEquals(Money.dollar(15), five.times(3)) } @kotlin.test.Test fun testFrancMultiplication() { val five: Money = Money.franc(5) assertEquals(Money.franc(10), five.times(2)) assertEquals(Money.franc(15), five.times(3)) } @kotlin.test.Test fun testEquality() { assertEquals(Money.dollar(5), Money.dollar(5)) assertNotEquals(Money.dollar(5), Money.dollar(6)) assertEquals(Money.franc(5), Money.franc(5)) assertNotEquals(Money.franc(5), Money.franc(6)) assertFalse(Money.franc(5) == Money.dollar(5)) }

이제 테스트 어떤 곳에서도 Dollar와 Franc이라는 하위 클래스가 있다는 사실을 알지 못한다. 이렇게 하위 클래스를 테스트에서 분리함으로써 어떤 모델 코드에도 영향을 주지 않고 상속 구조를 마음대로 변경할 수 있게 됐다.

이렇게 변경하니 testFrancMultiplication 테스트가 Dollar 곱하기 테스트에서 다 검증되지 않나? 라는 의문이 생긴다. 일단은 목록에 남겨두고 넘어가자.

할일 목록
  • $5 + 10CHF = $10 (환율이 2:1로 가정)

  • $5 x 2 = $10

  • amount를 private으로 만들기

  • Dollar side effect?

  • Money 반올림?

  • equals()

  • hashCode()

  • Equal null

  • Equal object

  • 5CHF x 2 = 10CHF

  • Dollar/Franc 중복

  • 공용 equals

  • 공용 times

  • Franc과 Dollar 비교하기

  • 통화?

  • testFrancMultiplication을 지워야 할까?

Last modified: 31 January 2024