회사에서 TDD 스터디를 하게 되어 클린 아키텍처는 잠시 접어두기로 하였다.
TDD 스터디를 하며, 사내 코드의 테스트 코드를 짜며 테스트 코드 짜는 전반적인 느낌을 익힌 뒤
간단한 예제들을 학습하며 TDD를 배워볼 생각이다.
Code Coverage를 적용한 평가를 통해 운영 코드에 반영하기로 하였다.
Code Coverage가 무엇인지, 실제로 적용하고 테스트 코드를 조금 짜봤는데 엄청 재미있었다.
그러나, 그건 테스트 코드를 짠 것이지 테스트 주도 개발을 한 것이 아니여서 TDD를 통해 개발하면 얼마나 재밌을지 기대가 되는 하루였다. 그럼 시작
Dollar만을 지원하던 Money객체에 다른 화폐 단위까지 적용해야 하는 경우의 예제이다.
문제 정의
$5 + 10CHF = $10 (환율이 2:1일 경우)
$5 * 2 = $10 ( 나는 이게 무슨 계산 법인지 모르겠다. 15CHF가 들어올 경우 ?)
public void testMultiplication() {
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
}
할 일 :
class Dollar {
int amount;
Dollar (int amount) {}
void times (int multiplier) {
amount *= multiplier
}
}
사소한 단계별로 문제를 밟을 수 있는 것이 능력이다.
$5 * 2 = $10 과 같이 문제를 간결하게 파악하는 능력이 가장 어렵다고 생각한다.
이러한 문제를 간결하게 정의할 수 있다면, 테스트 코드 작성에는 어려움이 없을 것이라고 생각했다.
초록 막대를 본 뒤, 코드를 리팩토링 한다.
-> 이 부분에서 좋은 점을 느꼈다. 코드를 리팩토링하는 시간을 강제로 갖게 되는 부분.
새로운 요구사항을 위해 Dollar의 인터페이스를 수정해야 하고, 그러기 위해서는 테스트도 수정해야 한다.
문제될 건 없다.
어떤 구현이 올바른가에 대한 우리 추측이 완벽하지 못한 것과 마찬가지로 올바른 인터페이스에 대한 추측 역시 절대 완벽하지 못하다.
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}
지금의 Dollar 객체 같이 객체를 값처럼 쓸 수 있는데 이것을 값 객체 패턴(value object pattern)이라고 한다.
값 객체에 대한 제약사항 중 하나는 객체의 인스턴스 변수가 생성자를 통해서 설정된 후에는 결코 변하지 않는다는 것이다.
객체가 암시하는 것 중 하나는 모든 연산은 새 객체를 반환해야 한다는 것이다.
또다른 암시 값 객체는 equals()를 구현해야 한다는 것.
equals()와 hashcode()를 overriding해야 하는 이유
- equals() : 기본적으로 메모리 주소가 같은지 판단하기 때문에, 동일한 값을 가지고 있어도 False를 리턴한다.
- hashcode() : 실행 중에(Runtime) 객체의 유일한 integer값을 반환한다.
Object 클래스에서는 heap에 저장된 객체의 메모리 주소를 반환하도록 되어있다.
동일한 객체는 동일한 메모리 주소를 갖는다는 것을 의미하므로, 동일한 객체는 동일한 해시코드를 가져야 한다.
그렇기 때문에 만약 우리가 equals() 메소드를 오버라이드 한다면, hashCode() 메소드도 오버라이드 되어야 한다.
amount를 private으로 변경하는 법
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(10, product.amount);
product = five.tiems(3);
assertEquals(15, product.amount);
}
// 1. 첫 번째 단언(assertion)을 Dollar와 Dollar를 비교하는 것으로 재작성
public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(new Dollar(10), product.amount);
product = five.tiems(3);
assertEquals(new Dollar(15), product.amount);
}
// 2. product는 더이상 필요 없다.
public void testMultiplication() {
Dollar five = new Dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}
테스트를 고치고 나니 Dollar의 amount 인스턴스 변수를 private으로 변경할 수 있었다.
처음에 10을 왜 굳이 new Dollar(10)으로 고치나 했는데, amount가 깔끔하게 제거되어 놀랐다!
완벽함을 위해 노력하는 것이 아닌, 모든 것을 두 번 말함으로써 (코드와 테스트로 한 번씩) 자신감을 가지고 전진할 수 있을 만큼만 결함의 정도를 낮추기를 희망할 뿐이다.