테스트 주도 개발 책을 읽으면서 그 책에서 제일 괜찮다고 생각하는
부분중에 한 부분입니다. TDD 개념을 가장 쉽게 접근할 수 있도록 만든
예제인데 무단으로 퍼서 소개할까 합니다. - 사실 본 책 안에 있는 내용은
좀 난해한 편이고 , 이 예제가 더 이해하기 쉽습니다. -

부록 B . 피보나치

이 책의 검토자 중 한 명의 질문에 대한 답으로 나는 피보나치 수열을
테스트 주도로 개발해 올렸다. 몇 명의 검토자들이 이 예를 보고 TDD가
어떻게 작동하는지 이해하는 데 큰 도움이 되었다고 했다. 하지만 이 책에
사용된 예제를 피보나치 예제로 바꾸기에는 피보나치 예제가 너무 짧고,
다양한 TDD 기술을 충분히 보여주지도 못한다. 이 책의 주 예제를 읽은
후에도 여전히 번쩍이는 깨달음을 얻지 못했다면 여기를 잠깐 들여다보고
머릿속 전등이 켜지는지 살펴보자.
 
첫 번째 테스트는 fib(0) = 0 이라는 걸 보여준다. 구현은 상수를
 반환한다.

 - 참고로 TDD는 실패하는 Test Code 부터 작성합니다.
 - 그리고 그것을 제대로 돌리기 위한 코드를 작성합니다.

public void testFibonacci () {
       assertEquals(0 , fib(0));
}

int fib(int n) {
    return 0;
}

(함수 하나밖에 안되기 때문에 나는 TestCase 클래스에 직접 해당 코드를
박아 넣구 있다)
 두 번째 테스트는 fib(1) = 1 이라는 걸 보여준다.

pulic void testFibonacci () {
      assertEquals (0 , fib(0));
      assertEquals (1, fib(1));
}

testFibonacciOfOneIsOne 이라는 테스트 메서드를 따로 작성하는 것에 큰
커뮤니케이션 가치가 있어 보이지 않아서 , 두 번째 단언을 같은 메서드
내에 집어 넣어 버렸다.
 이게 돌아가도록 하는 덴 몇 가지 방법이 있다. 나는 0을 특별한 경우로
 다루는 방법을 쓰겠다.

 int fib (int n) {
 if (n == 0) return 0;
 return 1;
}

테스트 케이스에 있는 중복이 점점 성가시게 느껴지기 시작하는데, 새
캐이스를 추가하면 더 악화되기만 할 것이다. 입력과 예상값으로 구성된
테이블을 통해 테스트가 돌아가게 하면 단언의 공통 구조를 추출할 수
있겠다.

public void testFibonacci () {
       int cases[][] = {{0,0} , {1,1}};
       for (int i= 0; i < cases.length ; i++)
           assertEquals(cases[i][1] , fib (cases[i][0]));
}

 이제 다음 케이스를 추가하려면 키보드를 여섯 번만 치면 되고, 줄을 새로
 추가할 필요는 없다.

 public void testFibonacci () {
  int case[][] = {{0,0} , {1,1} , {2,1}};
   for (int i=0 ; i < cases.length ; i++)
       assertEquals(cases[i][1] , fib(cases[i][0]));
}

당황스럽게도 테스트가 제대로 돌아간다. 우리가 고른 상수 1이 이
케이스에도 들어맞는 값이기 때문에 그런 일이 벌어졌다. 다음 테스트로
넘어가면

public void testFibonacci () {
  int case[][] = {{0,0} , {1,1} , {2,1}, {3,2}};
   for (int i=0 ; i < cases.length ; i++)
       assertEquals(cases[i][1] , fib(cases[i][0]));

}

야호, 이제 실패한다. 이전의 전략(더 작은 입력값을 특별한 경우로 다루는
것)을 똑같이 적용해서 다음과 같이 작성한다.

int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return 2;
}

 이제 일반화할 준비가 되었다. 우리가 2라고 쓰긴 했지만 정말 2를
 뜻한것은 아니고 , 1+1 을 의미한다.

 int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return 1+1;
}

첫 번째 1은 fin(n-1)로 볼 수 있다.

int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return fib(n-1) + 1;
}

두 번째 1은 fib(n-2)로 볼 수 있다.

int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return fib(n-1) + fib(n-2);
}

이제 좀 정리를 하면, 동일한 구조가 fib(2) 에서도 작동하기  때문에 결국
두 번째 조건을 강화할 수 있다.

int fib(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fib(n-1) + fib(n-2);
}

이렇게 해서 우리는 완전히 테스트로부터 유도된 피보나치를 완성하게
되었다.



제가 어디서 본 이야기인데 , TDD 를 단지 단위테스트 용 기법으로
생각하시는 분들이 많다고 합니다. 그러나 TDD는 '분석기법이고,
설계기법' 입니다. 그런것을 염두에 두시고 다시 한번 피보나치 구현을
보시면 TDD가 어떻게 설계에 쓰였는지 감을 잡으실 수 있을 것입니다.

+ Recent posts