Recent Posts
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Value too long for column
- pytest
- 코루틴 빌더
- JanusWebRTC
- JanusWebRTCServer
- 깡돼후
- kotlin
- 개성국밥
- preemption #
- JanusWebRTCGateway
- tolerated
- PersistenceContext
- terminal
- 겨울 부산
- python
- 오블완
- mp4fpsmod
- 자원부족
- 헥사고날아키텍처 #육각형아키텍처 #유스케이스
- 티스토리챌린지
- k8s #kubernetes #쿠버네티스
- JanusGateway
- 달인막창
- PytestPluginManager
- 코루틴 컨텍스트
- table not found
- vfr video
- taint
- Spring Batch
- VARCHAR (1)
Archives
너와 나의 스토리
TDD(Test-Driven Development) 연습해보기 - 예제 1: Money (2) 9~11장 하위 클래스 제거하기 본문
개발/TDD(Test-Driven Development)
TDD(Test-Driven Development) 연습해보기 - 예제 1: Money (2) 9~11장 하위 클래스 제거하기
노는게제일좋아! 2021. 2. 25. 10:55반응형
"테스트 주도 개발 Test-Driven Development: By Example" 책에 나오는 예제를 실제로 구현해보자.
<현재까지 작성한 코드>
- Money.java
package com.example.tdd.money;
public abstract class Money {
protected int amount;
abstract Money times(int multiplier);
static Dollar dollar(int amount) {
return new Dollar(amount);
}
static Franc franc(int amount){
return new Franc(amount);
}
public boolean equals(Object object) {
Money money = (Money) object;
return this.amount == money.amount &&
getClass().equals(money.getClass());
}
}
- Dollar.java
package com.example.tdd.money;
public class Dollar extends Money {
Dollar(int amount){
this.amount = amount;
}
Money times(int multiplier){
return new Dollar(this.amount*multiplier);
}
}
- Franc.java
package com.example.tdd.money;
public class Franc extends Money{
Franc(int amount){
this.amount = amount;
}
Money times(int multiplier){
return new Franc(amount* multiplier);
}
}
- MoneyTest.java [test]
package com.example.tdd.money;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class MoneyTest {
@Test
public void testMultiplication(){
Money five = Money.dollar(5);
assertEquals(Money.dollar(10),five.times(2));
assertEquals(Money.dollar(15), five.times(3));
}
@Test
public void testEquality(){
assertTrue(Money.dollar(5).equals(Money.dollar(5)));
assertFalse(Money.dollar(5).equals(Money.dollar(6)));
assertTrue(Money.franc(5).equals(Money.franc(5)));
assertFalse(Money.franc(5).equals(Money.franc(6)));
assertFalse(Money.franc(5).equals(Money.dollar(5)));
}
}
"어떻게 하면 불필요한 하위 클래스를 제거하는데 도움이 될까?"
- 통화 개념을 도입해보는 건 어떨까?
- 일단은 통화를 문자열을 이용해 표현해보자.
- 두 클래스의 currency()가 동일하므로 변수 선언과 currency() 메서드를 둘 다 부모 클래스로 올릴 수 있다.
- Money에 currency 변수를 protected로 선언하고, currency() 메서드를 생성해주자.
- 이제 Dollar와 Franc 클래스에서는 해당 변수와 메서드를 지워도 된다.
- 문자열 'USD'와 'CHF'를 정적 팩토리 메서드로 옮긴다면 두 생성자가 동일해질 것이고, 그렇다면 공통 구현을 만들 수 있을 것이다.
- 먼저 두 클래스의 생성자에 인자를 추가해 보자.
- 이렇게 하면, 생성자를 호출하는 코드들이 깨진다.
- 문제가 발생하는 곳: Money 클래스의 dollar(), franc() 메서드와 Dollar, Franc 클래스의 times() 메서드.
- times() 메서드의 경우 팩토리 메서드를 호출하지 않고 생성자를 호출하고 있다. 하지만 지금 하는 일을 중단하고 이것부터 수정할지 고민이 된다. 금방 고칠 수 있을 것으로 보이니 짧게 중단하고 times()부터 수정해보자.
- But, 하던 일을 중단하고 다른 일을 하는 상태에서 그 일을 또 중단하지는 않는다 -Jim Coplien-
- Dollar 클래스에서 times() 메서드를 수정하자.
// before
Money times(int multiplier) {
return new Dollar(this.amount * multiplier);
}
// after
Money times(int multiplier) {
return Money.dollar(amount*multiplier);
}
- Franc 클래스에서도 똑같이 수정해주자.
// before
Money times(int multiplier) {
return new Franc(amount * multiplier);
}
// after
Money times(int multiplier) {
return Money.franc(amount*multiplier);
}
- 이제 팩토리 메서드에서 currency를 전달할 수 있다.
- 마지막으로 인자를 인스턴스 변수에 할당할 수 있다.
- 이제 두 생성자가 동일해졌다
- 생성자 구현을 상위 클래스로 올리자.
"단 하나만의 클래스로 Money를 나타내기"
- 두 times() 구현이 거의 비슷하긴 하지만 아직 완전히 동일하지는 않다.
- 이 둘을 동일하게 만들기 위한 명백한 방법이 없다.
- 팩토리 메서드를 인라인시키면 어떨까?
- ?? 앞에서 팩토리 메서드를 호출하도록 바꿨으면서...???
- 때로는 물러서야 할 때도 있다고 한다...
- Franc과 Dollar의 currency는 각각 "CHF", "USD"로 항상 유지되기 때문에 다음과 같이 수정해줄 수 있다.
- Franc의 times에서 Franc을 리턴해야 할까? 아니면 Money를 리턴해야 할까?
- 오래 고민하지 말고 컴퓨터에게 물어보자.
- Money를 반환하도록 고쳐보니 다음과 같이 에러가 발생한다
- 에러: Money가 abstract 클래스이니, concrete class로 바꿔야 한다.
- 컴파일 에러를 해결하기 위해, abstract였던 Money 클래스를 콘크리트 클래스로 변경하고 times() 메서드를 다음과 같이 수정해주자.
- 그렇다면 아래의 테스트 코드는 잘 돌아갈까?
- 테스트는 실패하고 다음과 같은 에러 메시지가 뜬다.
- 무슨 소리인지 모르겠다. 더 나은 메시지를 보기 위해 toString()을 정의해보자.
- toString()은 디버그 출력에서만 사용할 것이라 잘못 구현된다고 해도 얻게 될 리스크가 적다. 그러니 이에 대한 테스트 코드는 작성하지 않고 바로 코드를 작성해보자.
- 그리고 테스트를 다시 돌려보면 다음과 같이 에러 메시지가 나온다.
- 둘 다 <10 CHF>인 건 맞지만 틀리단다.
- equals() 구현에 문제가 있나 보다.
- 자, 그럼 여기서 바로 이에 대해 새로 테스트 코드를 작성하고 equals()를 고치러 갈 것인가?
- 놉! 보수적인 방법에 따라, 일단 테스트가 초록색(통과)이 되도록 돌려놓고, 위의 오류에 해당하는 테스트(값과 클레스가 같을 때, 동일하다고 판단하는지 테스트) 코드를 작성하고 equals()를 수정하도록 하자.
- 다시 테스트가 정상적으로 돌아간다.
- 이제 테스트 코드를 작성하자.
- 우리는 Franc(10, "CHF")와 Money(10, "CHF")가 같다고 판단되기를 바란다.
- 현재 실패가 뜨는 이 테스트 코드가 정상적으로 돌아가도록 equals()를 수정해보자.
- 정말로 검사해야 할 것은 클래스가 같은지가 아니라 currency가 같은지 여부다.
- equals()를 아래와 같이 수정하자.
- 이제 테스트 코드가 잘 돌아갈 것이다.
- 그렇다면, Dollar도 Franc처럼 times()를 수정하자.
- 이제 두 구현이 동일해졌으니, 상위 클래스로 끌어올릴 수 있게 되었다.
- Money 클래스의 times()를 이와 똑같이 만들어주고, 하위 클래스들에서는 times() 메서드를 모두 삭제하자.
- 해당 메서드를 지워도 테스트가 잘 돌아간다 :)
<지금까지 한 일 정리>
- 두 times()를 일치시키기 위해 그 메서드들이 호출하는 다른 메서드들을 인라인시킨 후 상수를 변수로 바꿔주었다
- Franc 대신 Money를 반환하는 변경을 시도한 뒤, 그것이 잘 작동할지를 테스트가 말하도록 했다.
- 실험해본 걸 뒤로 물리고(테스트가 성공하는 환경으로 돌아가기 위해) 또 다른 테스트를 작성했다.
- 테스트를 작동했더니 실험도 제대로 작동했다.
"하위 클래스를 제거하자!"
- 이제 두 하위 클래스에는 생성자밖에 없다. 단지 생성자 때문에 하나 때문에 하위 클래스가 있을 필요는 없기 때문에 하위 클래스를 제거하자.
- 코드의 의미를 변경하지 않으면서도 하위 클래스에 대한 참조를 상위 클래스에 대한 참조로 변경할 수 있다.
- Money 클래스에서 하위 클래스를 참조하던 부분을 아래와 같이 수정하자.
- 이제 Dollar와 Franc 클래스에 대한 참조가 남아있지 않으므로 이 두 클래스를 지울 수 있게 됐다.
- 하지만 우리가 전에 작성한 동치성 테스트 코드에서 Franc을 참조하고 있다.
- 이 테스트를 지워도 될 정도로 다른 곳에서 동치성 테스트를 충분히 하고 있는가?
- 작성된 다른 테스트 중 아래의 테스트만으로도 충분히 동치성 테스트를 할 수 있을 것 같다.
- 오히려 1/2줄 중복, 3/4줄 중복되므로 지워주자.
- 클래스 대신 currency를 비교하는 테스트 코드는 여러 클래스가 존재할 때만 의미 있다.
- Franc(과 Dollar) 클래스를 제거하려는 중이기 때문에 Franc이 있을 경우에 시스템이 작동하는지 확인하는 테스트는 도움이 안 되고 오히려 짐만 된다.
- 그러니 testDifferentclaassEquality() 테스트를 없애자!
- 진행한 사항:
- 하위 클래스 제거함.
- 제거하는 클래스가 제대로 작동하는지 테스트하는 코드는 필요가 없어졌으므로 제거함.
출처:
- [테스트 주도 개발 Test-Driven Development: By Example"]
반응형
'개발 > TDD(Test-Driven Development)' 카테고리의 다른 글
TDD(Test-Driven Development) 테스트 주도 개발 패턴 정리 (2) | 2021.03.31 |
---|---|
TDD(Test-Driven Development) 연습해보기 - 예제 1: Money (3) 12~17장 (0) | 2021.03.11 |
TDD(Test-Driven Development) 연습해보기 - 예제 1: Money (1) 1~8장 (0) | 2021.02.25 |
Comments