[Test] 테스트 주도 개발 (들어가는 글 ~ 4장)

DaeHoon·2022년 10월 11일
0

TDD

목록 보기
1/9

빨강/초록/리팩토링

  • 빨강: 실패하는 작은 테스트를 작성한다.
  • 초록: 빨리 테스트가 통과하게만든다.
  • 리팩토링: 테스트를 통과시키기 위해 생긴 중복을 제거한다.

들어가는 글

요구사항

  • 달러로 명명된 채권을 다른 화폐의 채권을 다루도록 요구

상황

  • 기존의 미국 달러로만 되어있는 로직을 다른 화폐에서도 적용하려고 하니 쉽지 않은 상황

해결 방법

  • 가중평균 로직이 다른 통화에서도 적용되게 바꾸면 나머지 부분도 다중으로 바꿀 수 있을 것 같음

TDD를 위한 두 가지 단순한 법칙

  • 어떤 코드건 작성하기 전에 실패하는 자동화된 테스트를 작성해라
  • 중복을 제거해라

1. 다중 통화를 지원하는 Money 객체

보고서

종목가격합계
IBM10002525000USD
GE40010040000CHF
합계65000USD
  • 다중 통화를 지원하는 보고서를 위와 같이 정의하자
기준변환환율
CHFUSD1.5
  • 위의 환율을 통해 5USD == 10CHF 라는 것을 알 수 있다.

테스트 초안

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class MultipleCurrenciesTest {

 @Test
 public void testMultiplication(){
   Dollar five = new Dollar(5);
   five.times(2);
   assertEquals(10, five.amount);
 }
}
  • 보고서를 토대로 위와 같이 테스트 코드를 작성할 수 있다. 하지만 컴파일 자체가 되지 않는 빨강 상태이다. (Dollar 객체 없음, 생성자 없음, times 메서드 없음, amount 필드 없음)
public class Dollar {
  int amount = 5*2;
  
  Dollar(int amount){
    
  }
  void times(int multiplier){
    
  }
}
  • 컴파일을 성공시키기 위해 달러 객체를 추가하고 amount에 10으로 초기화를 했다.
  • 테스트는 빨강에서 초록 상태로 변경

중복 제거

  • Dollar 객체의 amount 필드가가 10으로 초기화 되어있고, 테스트에서도 5와 2 인자로 받고 있어 중복이 있는 상태다.
  • times 안에 5 * 2를 넣어 중복을 제거해보자
  int amount;
  
  void times(int multiplier){
    amount = 5*2;
  }

생성자와 times 인자로 중복 제거

public class Dollar {
  int amount;
  Dollar(int amount){
    this.amount = amount;
  }
  void times(int multiplier){
    amount *= multiplier;
  }
}
  • 여전히 amount는 10으로 하드코딩이 되어 있는 상태
  • times와 생성자로 넘어온 인자로 amount의 값을 정하게 수정했다.

2. 타락한 객체

일반적인 TDD 주기

  • 테스트를 작성한다.
  • 실행 가능하게 만든다. (빨리 초록 막대를 보는 것이 중요)
  • 올바르게 만든다. (중복 제거 등)

Dollar 객체 부작용

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class MultipleCurrenciesTest {

  @Test
  public void testMultiplication(){
    Dollar five = new Dollar(5);
    five.times(2);
    assertEquals(10, five.amount);
    five.times(3);
    assertEquals(15, five.amount);
  }
}
  • 위와 같은 테스트를 통과할 수가 없다. five.amount는 15가 아니라 30이 나오므로 테스트 실패
  • 위와 같은 상황을 별칭 문제라고 한다. (3장에서 자세히 나온다.) 이를 해결하기 위해 times의 리턴 값을 새로운 Dollar 객체를 만들게 수정한다.
  • 그 전에 아래와 같이 테스트 코드를 다시 작성한다. 현재는 컴파일조차 되지 않는다.
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class MultipleCurrenciesTest {

  @Test
  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);
  }
}

  • 그 다음, 컴파일을 하기 위해서 times의 리턴 값에 null을 반환하게 한다.
public class Dollar {
  int amount;
  Dollar(int amount){
    this.amount = amount;
  }
  Dollar times(int multiplier){
    amount *= multiplier;
    return null;
  }
}
  • 지금 상황에서 테스트를 돌리면 컴파일은 되지만 실행되진 않는다. 테스트를 통과하기 위해 Dollar 객체를 리턴하도록 수정했다.
public class Dollar {
  int amount;
  Dollar(int amount){
    this.amount = amount;
  }
  Dollar times(int multiplier){
    return new Dollar(amount*multiplier);
  }
}

초록색을 보기 위한 두 가지 전략

  • 가짜로 구현하기 : 상수로 반환하게 만들고 진짜 코드를 얻을 때까지 단계적으로 상수를 변수로 바꿈
  • 명백한 구현 사용하기: 살제 구현을 입력한다.

3. 모두를 위한 평균

  • 위의 Dollar 객체 같이 객체를 값처럼 쓸 수 있는데, 이를 값 객체 패턴 (value object pattern) 이라고 한다.
  • 이를 사용하면 별칭 문제에 대해 걱정할 필요가 없다는 장점이 있다. 예를 들면 수표가 하나 있는데 여기에 $5를 설정하고 또 다른 수표에도 아까 설정했던 $5를 설정했다고 치자. 두 번째 수표의 값을 설정함으로써 첫 번째 수표의 값까지 변하게 되는 문제가 생길 수 있다. 이를 별칭 문제라고 한다.
  • 즉, 값 객체 패턴을 통해 새로운 객체 값을 만들어 반환하면 별칭 문제를 해결할 수 있다.

삼각측량 전략

  • 만약 라디오 신호를 두 수신국이 감지하고 있을 때, 수신국 사이의 거리가 알려져 있고 각 수신국이 신호의 방향을 알고 있다면, 이 정보들만으로 충분히 신호의 거리와 방위를 알 수 있다. 이 계산법을 삼각측량이라고 한다.
  • 즉 삼각 측량을 하기 위해서는 예제 코드가 최소 2개 이상은 있어야만 코드를 일반화 할 수 있다.

  @Test
  public void testEquality(){
    assertTrue(new Dollar(5).equals(new Dollar(5)));
    assertFalse(new Dollar(5).equals(new Dollar(6)));
  }
// Dollar
  public boolean equals(Object object){
    Dollar dollar = (Dollar) object;
    return amount == dollar.amount;
  }
  • 저자는 코드를 어떻게 리팩토링을 해야 하는지 전혀 감이 안 올 때만 삼각측량을 사용한다.
  • 삼각측량은 코드와 테스트 사이에서 중복되는 테스트를 사용하기 때문에 비효율적

4. 프라이버시

  • Dollar.times()는 호출 받은 객체의 값에 인자로 받은 곱수만큼 곱한 값을 갖는 Dollar를 반환하고 있다. 하지만 테스트는 Dollar를 비교하지 않고 Dollar의 field 값을 비교하고 있다.

Dollar 객체를 비교하게 테스트 변경

  @Test
  public void testMultiplication(){
    Dollar five = new Dollar(5);
    assertEquals(new Dollar(10), five.times(2));
    assertEquals(new Dollar(15), five.times(3));
  }
  • 외부에서 amount 필드를 사용하지 않으니 접근 제어자를 private로 바꿀 수 있다.
  • 테스트 내에서 직접 amount 필드에 접근하지 않으므로 테스트와 코드 사이의 결합도를 낮출 수있다.

// Dollar
private int amount;
profile
평범한 백엔드 개발자

0개의 댓글