Test First Programming

FrontPage|FindPage|TitleIndex|RecentChanges| UserPreferences P RSS
실제 프로그램을 하기 전에 테스트를 먼저하라는 XP의 중요 원칙 중 하나.

장점:
  • 모든 코드에 대해 테스트 가능성(testability)을 획득
  • 의도를 드러내는 코딩
  • 자동 다큐먼팅 (테스트 코드 자체가 다큐먼트 역할을 함)
  • 좋은 디자인 (separation of concerns, interface design, etc)
  • 빠른 코딩 속도 (복잡한 프로그램일 수록)
  • 낮은 에러율
  • 과감한 Refactoring 가능
  • 언제 끝나는지가 분명해 짐 (테스트를 100% 통과하면 퇴근해도 좋다)
  • 코드 관리도(maintainability)가 높아짐
  • 코딩이 몇 배로 즐거워진다.

DIP의 [http]5장UnitTesting 설명이 있긴 한데, 아주 심각한 문제(실행코드와 테스트코드가 함께 점진적으로 성장해 나가지 않고 테스트코드를 먼저 완성하고 나서 하나하나 통과해 나가는 것은 XP가 BUFD라고 부르며 경계하는 것 중 하나이다)가 있는 설명이다. XP와 TFP에 대한 이해가 없는 사람은 주의를 요한다. PyUnit의 사용법을 익히기에는 괜찮다.

이런 TestDrivenDesign에서 중요한 것은 테스트/코딩의 빠른 피드백(많은 개체수)과 적합도(?) 함수(fitness function)이다. 여기서 특정 코드의 fitness는 (중요한 순서대로) 통과하는 테스트 케이스의 개수, 중복되지 않는 코드의 량, 코드가 의도를 얼마나 잘 드러내는가에 비례하고, 클래스와 메써드의 개수에 반비례한다.

see also SOP2.py

먼저 테스트 프로그램을 작성하고 나서, 이 테스트 프로그램을 통과하는 프로그램을 만든다. ProgrammingByIntention이 가능한 것이다.

예를 들어, 1부터 N까지의 합을 구하는 프로그램을 작성한다고 하면, 대부분의 프로그래머들은 구체적 로직부터 생각하는 BottomUp방식으로 접근할 것이다. 하지만 이런 경우, 멍하니 먼 산만 바라보며 뭘 먼저 해야할지 감이 오지 않는 경우도 많고, 언제 이 프로그램이 완성되는지도 알기 힘들고, 더구나 프로그램 작성 중간에 코드를 고치는 것을 꺼리게 된다.

하지만, ProgrammingByIntentionTestFirstProgramming에서는 먼저 "의도"(intention)가 드러나도록 한다. 따라서 훌륭한 디자인도 덤으로 얻는다.

TFP와 PBI를 하는 프로그래머는 먼저 다음의 코드를 작성한다:

...
out=1+2+3+4+5+6+7+8+9+10
self.assertEqual(out,sumFromOne(10))
...

이 때 이미 프로그래머는, 가장 분명하고 단순한 코딩을 하면서 동시에 자신이 작성할 프로그램의 인터페이스를 디자인한 것이다. 목표가 만들어 졌으면 이 목표를 완수하는 프로그램을 만들면 된다.

사실 이 테스트 코드를 먼저 작성하고 바로 테스트 코드를 실행한다. 그러면 당연히 sumFromOne이라는 이름이 정의되어 있지 않다는 에러가 나온다. 이 때 바로, 컴퓨터가 요구하는 것을 들어주면 된다. 즉, sumFromOne이라는 이름을 정의한다.

def sumFromOne(upTo):
    pass

여기까지만 작성하고 다시 테스트 코드를 돌린다. 그러면 이번에는 아까의 이름이 정의되어 있지 않다는 에러는 사라지고 이번에는 1부터 10까지의 합이 None값(sumFromOne의 리턴값)과 다르다는 "실패"(failure) 메세지가 나온다.

그럼, 이제는 다시 컴퓨터가 요구하는, 두 값을 동일하게 해주는 코드를 작성하면 된다. (언제나 실패하는 부분이 한 군데 임에 주의한다. 새로운 기능을 추가할 때도, 테스트 코드에 해당 기능 관련 테스팅을 먼저 추가하고, 다음 그 코드를 패스하는 실행 코드를 작성하는데, 언제나 한 걸음 씩 나가야 한다. 동시에 두개 이상의 기능을 추가하고 테스팅이 실패하면 어느 것이 문제였는지 찾아내는 데에 시간이 허비된다)

def sumFromOne(upTo):
    sum=0
    for i in range(1,upTo+1):
        sum+=i
    return sum

이제 테스트 코드를 돌리면 패스하는 것을 확인할 수 있다.

이로써 우리는 "지속적으로 컴퓨터가 요구하는 한가지"만 해결해 주면서 전체문제를 점진적으로 해결했다. 또 처음 출발 때 가장 분명하게 "의도"를 드러냈기 때문에 인터페이스 디자인(여기서는 함수 이름, 인자 내용 등) 등이 깔끔하고, 로직도 분명하다.

또 100% 패스하는 테스트 코드가 갖춰져 있기 때문에 과감히 실행 코드를 변경/개선할 수 있다. 만약 한 부분을 개선했는데(small step is important!) 기존에 패스하던 테스트 몇 개가 실패를 한다면 방금 고친 부분이 문제를 야기한 것이므로 그 부분을 다시 분석한다.

실생활에의 적용

무슨 일이건 해야할 일이, 풀어야할 문제 상황이 존재한다면 우선 그 문제가 해결된 상황을 상정한다. 그리고 그 상황에서 해결이 되었는지 안되었는지를 확인할 수 있는 방법을 생각한다. (이를 TopDown 방식이라고도 하고, 미로의 목적지에서 반대로 거슬러 내려오는 방법이라고도 볼 수 있다.) 일종의 테스트 방법을 만드는 것이다.

이제는 좀전의 테스트를 100% 통과할 수 있는 방법을 생각한다. 이것이 너무 복잡하다면, 다시 작은 단위의 테스트를 만들고, 이 테스트를 통과할 방법을 생각한다. 주의점은 한번에 꼭 하나의 테스트만 통과할 고민을 해야 한다.

예를 들어, 부산에 가야한다면, 자신이 부산이 도착했다는 것을 확인할 수 있는 간단한 테스트를 생각한다. "부산역"에서 그 글자를 읽는 것처럼 분명하고 단순한 것일수록 좋다. 이제는 부산역 글자를 읽어야 하는 테스트를 통과할 방법을 생각한다. 간단하게는 서울역에서 기차를 타고 부산역으로 가는 것이다. 그러면 또 다시, 서울역에서 기차를 탔다는 것을 확인할 테스트를 생각하고, 또 이 테스트를 통과할 방법을 생각해 낸다.

복잡한 일일수록 이 문제해결법은 효력를 발휘한다.

질문응답

Q : TestFirstProgramming, UnitTest 와 같은 방법들은 초보자들이 어렵더라도 처음 프로그래밍을 배울 때부터 배워나가서 몸에 익히는 것이 좋은 방법인지, 아니면 종래의 프로그래밍 방법들을 충분히 익힌 다음에 시도되어야 하는 기법들인지가 궁금합니다.
A : 처음부터 하면 더 좋습니다. 남들보다 열배 이상 빨리 발전할 것이라 확신합니다. --김창준
사실 그런 것은 그런 것을 배우려는 자세에서 시작한다기 보다는 그런 것을 일단 아는 순간 그렇게 하고 싶게 되고 허접한 코드를 보면 화가나게 되는 등의 현상으로 인해 어쩔 수 없이 그렇게 되더라고 ... ... 머 아닐수도 있구요.

Q : 'XP Installed' 의 UnitTest 부분이나 링크되어있는 몇가지 자료를 읽어봤는데, GUI 프로그램을 구상할경우 잘 감이 오질 않네요. (Installed 에 짧게 설명이 되어 있지만) 다른 분들의 코드라도 봤으면 하는 심정입니다^^ GUI의 경우는 다들 어떻게 하시죠? --영후
자문자답이군요. :) XP에 대한 위키를(링크되어있더군요.) 이리저리 뒤지다가 몇가지를 보았습니다. BillWake[http]XPlorations 가 실질적인 도움이 될것같습니다. 위키의 Gui Unit Testing 페이지에서 KentBeck이 'this is fabulous!' 라고 하더군요 :) 저도 이제 시도해보구 성과가 보이면 GUI 유닛테스팅에 관한 페이지를 만들겠습니다. --영후


"; if (isset($options[timer])) print $menu.$banner."
".$options[timer]->Write()."
"; else print $menu.$banner."
".$timer; ?> # # ?>