Test Driven Development (Ch.9 ~ 10)

manx·2022년 12월 11일
0

TDD

목록 보기
4/4

Test Driven Development


Ch.9 우리가 사는 시간

할 일 목록

  • $5 + 10CHF = $10
  • Money 반올림?
  • hashCode()
  • Equal null
  • Equal object
  • Dollar/Franc 중복
  • 공용 times
  • 통화?
  • testFrancMultiplication 제거

할 일 목록에서 어떤 걸 하면 귀찮고 불필요한 하위 클래스를 제거하는데 도움이 될지 생각해보자.
통화를 표현하기 위한 복잡한 객체들을 원할 수도 있다. 그리고 그 객체들이 필요한 만큼만 만들어지도록 하기 위해 경량 팩토리(flyweight factories)를 사용할 수 있다.
하지만 당분간은 그런 것들 대신 문자를 쓰자.
(단계 씩 밟아야 하니? -> 단계를 건너 뛸 수 있어도 이 단계를 밟는 것이 좋은가?)

public void testCurrency() {
	assertEquals("USD", Money.dollar(1).currency());
    assertEquals("CHF", Money.franc(1).currency());
}

currency() 메서드가 없는 것을 나는 알았다! 그리고 다음에 바로 currency()를 구현할 것도 알았다!

Money에 currency() 메서드 선언

// Money Class
abstract String currency();

두 클래스를 모두 포함할 수 있는 동일한 구현을 원한다.
통화를 인스턴스 변수에 저장하고, 메서드에서는 그냥 그걸 반환하게 만든다.

// Franc Class
private String currency;
Franc(int amount) {
	this.amount = amount;
    currency = "CHF";
}

String currency() {
	return currency;
}

// Dollar Class
private String currency;
Dollar(int amount) {
	this.amount = amount;
    currency = "USD";
}

String currency() {
	return currency;
}

-> final이 왜 아닌지는 의문이다.

이제 두 currency()가 동일하므로 변수 선언과 currency() 구현을 둘 다 위로 올릴 수 있게 됐다.

// Money class
protected String currency;
String currency() {
	return currency;
}

-> Money에서 팩토리 메서드로 초기화 해서 쓰려면 final일 수 없군!

문자열 "USD"와 "CHF"를 정적 팩토리 메서드로 옮긴다면 두 생성자가 동일해질 것이고, 그렇다면 공통 구현을 만들 수 있다.

// Franc Class
Franc(int amount, String Currenct) {
	this.amount = amount;
    this.currency = "CHF";
}

// 문제 코드

// Money Class
static Money franc(int amount) {
	return new Franc(amount, null);
}

# Franc Class
Money times(int multiplier) {
	return new Franc(amount * multiplier, null);
}

Franc.times()가 팩토리 메서드를 호출하지 않고 생성자를 호출하는 오류가 있다. 지금 이걸 고쳐야 하나, 아니면 기다리는 것이 맞을까?
교리상으로 기다리는 것이 맞다. 지금 하는 일을 중단하지 않아야 하니까.
짧은 중단이 필요할 경우에는 받아들여도 된다. 물론 짧은 것만.

단, 하던 일을 중단하고 다른 일을 하는 상태에서 그 일을 또 중단하지 않는다.

진행하기 전 times() 정리

// Franc Class
Money times(int multiplier) {
	return Money.franc(amount * multiplier);
}

// Money Class
static Money franc(int amount) {
	return new Franc(amount, "CHF");
}

// Franc Class
Franc(int amount, String currency) {
	this.amount = amount;
    this.currency = currency;
}

지금과 같은 일은 TDD를 하는 동안 계속 해주어야 하는 일종의 조율이다. 종종걸음으로 진행하는 것이 답답한가? 그러면 보폭을 조금 넓혀라.

Franc와 Dollar의 생성자가 동일해졌다. 이제 구현을 상위 클래스에 올리자.

// Money
Money(int amount, String currency) {
	this.amount = amount;
    this.currency = currency;
}

// Franc
Franc(int amount, String currency) {
	super(amount, currency);
}

// Dollar
Dollar(int amount, String currency) {
	super(amount, currency);
}
  • 큰 설계 아이디어를 다루다가 조금 곤경에 빠졌다. 그래서 좀 전에 주목했던 더 작은 작업을 수행했다.
  • 다른 부분들을 호출자(팩토리 메서드)로 옮김으로써 두 생성자를 일치시켰다.
  • 동일한 생성자들을 상위 클래스로 올렸다.

Ch. 10 흥미로운 시간

Franc / Money 의 times() 구현이 거의 비슷하긴 하지만 완전히 동일하진 않다.

// Franc
Money times(int multiplier) {
	return Money.franc(amount * multiplier, currency);
}

// Dollar
Money times(int multiplier) {
	return Money.dollar(amount * multiplier, currency);
}

Franc를 가질지 Money를 가질지가 정말로 중요한 사실인가?
시스템에 대해 아는 지식을 기반으로 조심스럽게 생각해 보아야 할 문제다.
하지만, 우리에겐 깔끔한 코드와, 그 코드가 잘 작동할 거라는 믿음을 줄 수 있는 테스트 코드들이 있다.

몇 분 동안 고민하는 대신 그냥 수정하고 테스트를 돌려서 컴퓨터에게 직접 물어보자.


에러 메세지 : "expected:<10 CHF> but was:<10 CHF>"
답은 맞았는데 클래스가 다르다. Franc 대신 Money가 왔다. 문제는 equals() 구현에 있다.

// Money
public boolean equlas(Object object) {
	Money money = (Money) object
    return amount == money.amount
    	&& getClass().eqauls(money.getClass());
}

정말로 검사해야 할 것은 클래스가 같은지가 아니라 currency가 같은지 여부다.

빨간 막대인 상황에서는 테스트를 추가로 작성하고 싶지 않다. 하지만 지금은 실제 모델 코드를 수정하려고 하는 중이고 테스트 없이는 모델 코드를 수정할 수 없다.
보수적인 방법을 따르자면 변경된 코드를 되돌려서 다시 초록 막대 상태로 돌아가야 한다. 그러고 나서 equlas()를 위해 테스트를 고치고 구현 코드를 고칠 수 있게 되고, 그 후에야 원래 하던 일을 다시 할 수 있다.

//
public void testDifferntClassEquality() {
	assertTrue(new Money(10, "CHF").equals(new Franc(10, "CHF")));
}

이 테스트 코드는 예상대로 실패한다. equals()는 클래스가 아니라 currency를 비교해야 한다.

// Money
public boolean equals(Object object) {
	Money money = (Money) object;
    return amount == money.amount
    	&& currency().equals(money.currency());
}

이제 Franc.times()에서 Money를 바나환해도 테스트가 여전히 통과하게 할 수 있다.

  • 두 times()를 일치시키기 위해 그 메서드들이 호출하는 다른 메서드들을 인라인시킨 후 상수를 변수로 바꿔주었다.
  • 실험해본 걸 뒤로 물리고 또 다른 테스트를 작성했다. 테스트를 작동했더니 실험도 제대로 작동했다.

inline했다 ?

profile
BackEnd Developer

0개의 댓글