알라딘MGG와이드바


단위테스트(UnitTest, xUnit) 관련 질문 있으시면 해 주세요 #3 개발 이야기

안녕하세요. 9월 초에 있을 KGC(Korea Games Conference) 발표 준비 할겸해서 이것저것 알아보고 있습니다.
마침 올해 제가 번역한 '[http]xUnit 테스트 패턴' 과 '[http]Debug It! 실용주의 디버깅' 도 나온터라 주제를 '낡은 코드와 단위테스트'로 잡아보았습니다.
2007년 KGC 에서 제가 '[http]TDD, UnitTest for games' 를 발표할 당시에만 해도 단위테스트를 모르는 사람도 꽤 있었지만, 지금은 단위테스트가 많이 알려졌고 실제로 단위테스트를 하고 있는 조직도 많이 늘었습니다. 하지만, 많은 분들이 레거시 코드에 단위테스트를 도입하면서 어려움을 겪고 있고, 그러다보니 단위테스트가 별로 도움이 안 되는 거 아니냐는 의견도 많이 들었습니다. 그래서 이번에는 오래된 코드 기반에 단위테스트를 도입하는 기법과 팁을 주로 다뤄볼 생각입니다.

이메일을 통해서 단위테스트에 대해 물어보시는 분들이 있어서 허락을 받고 여기에 질문과 답변을 올립니다. 다른 분들도 단위테스트를 만드면서 궁금했던 점 있으시면 댓글이나 이메일, [http]위키 등으로 질문을 올려주시면 정리해서 블로그에 올려놓도록 하겠습니다.

단위테스트를 만드는 철학은 사람마다 호불호가 다릅니다. 제가 개인적으로 경험하고 느낀 점에 기초해서 답변해 드리겠습니다.


ehei 님 질문 :
팀내에서 단위테스트를 효과적으로 시행하기 위해 도움이 되었던 정책이나 규칙 같은것이 있는지요?

답변
단위테스트가 팀에게 도움이 된다는 점에 대한 공감대를 먼저 얻는게 중요하다고 생각합니다. 처음에는 단위테스트에 관심이 있는 1,2 명 규모의 TF 를 조직해서 팀에 단위테스트를 도입합니다. 그 후에는 다른 팀원들에게 단위테스트를 작성하지는 않더라도 적어도 초록막대가 뜨는 것을 확인한 후 check-in 하게 했습니다. 그리고 단위테스트가 실패하면 빨간막대와 함께 전체 팀원에게 이메일을 보내서, TF 가 왜 단위테스트에서 실패하는지를 살펴봐 주었습니다. 테스트가 일정이상 성숙되면 마치 단위테스트 코드를 DSL(Domain Specific Language) 처럼 표현할 수 있게 됩니다. 이러면 다른 팀원과 페어프로그래밍을 할 때나, 기획자와 얘기할 때도 테스트를 작성해 가면서 코딩을 해서 단위테스트가 작업에 좋다는 점을 보여줄 수 있습니다. 이 정도 되면 다른 분들도 테스트 작성에 동참하기 시작하더군요. 그리고, 새로 팀에 투입되는 신입들은 처음부터 단위테스트를 보게 되기 때문에 오히려 시니어 프로그래머보다 단위테스트에 빨리 적응하는 모습을 볼 수 있었습니다. 정리하자면, 팀내에서 최소한의 공감대를 형성한 후 실제로 단위테스트가 작업에 도움이 될 수 있도록 꾸준히 gardening 을 하다보면 자연히 정착될 거라고 생각합니다.

질문 :
시간이나 날짜 같은 context 에 민감한 테스트는 어떻게 작성해야 할까요?

답변 :
'xUnit 테스트 패턴' p.350 에서 얘기하는 '가상 시계(Virtual Clock)' 를 이용해서 테스트 픽스처에서 원하는 시간을 설정할 수 있어야 합니다. 예를 들어 공성이 토요일 오후 4시에 시작되고 2시간 후에 끝난다고 할 때 테스트 시작할 때 시각(time)이라는 환경을 토요일 오후 4시로 만들 수 있어야 합니다. 이러기 위해서 ?GetTickCount 나 time, localtime 같은 API 를 호출하는 부분을 전부 wrapping 함수로 바꿔줍니다. 예를 들어 30초 후에 버프가 사라지는지를 검사하는 테스트를 GoogleTest 로 만든다면 다음과 같습니다.


#include <gtest/gtest.h>
 
static DWORD g_TickCountAdd = 0;    // virtual clock
 
DWORD MyGetTickCount() {
    return g_TickCountAdd + GetTickCount();
}
 
class BaseFixture : public testing::Test {
    virtual void SetUp() {
        // g_TickCountAdd 는 일종의 공유 Fixture.
        // SetUp 이나 TearDown 중 적당한 곳에서 정리할 것
        g_TickCountAdd = 0;
    }
};
 
bool IsBuffTimeOver(DWORD buffEndTick) {
    return buffEndTick <= MyGetTickCount();
}
 
// Test Helper(MyGetTickCount) 를 테스트
TEST_F(BaseFixture, MyGetTickCount) {
    DWORD old = MyGetTickCount();
    g_TickCountAdd = 10000;        // 10초 지났다.
    EXPECT_LE(old + 10000, MyGetTickCount());
}
 
TEST_F(BaseFixture, IsBuffTimeOver) {
    const DWORD buffEndTime = 10000;
    DWORD myBuffTime = MyGetTickCount() + buffEndTime;
    g_TickCountAdd = buffEndTime;    // 10초 지났다
    EXPECT_TRUE(IsBuffTimeOver(myBuffTime));    // 버프가 사라졌는가?
}
 
int _tmain(int argc, _TCHAR* argv[]) {
    testing::InitGoogleTest(&argc, argv);
    RUN_ALL_TESTS();
    return 0;
}

질문 :
단위테스트가 늘어나면서 오히려 생산성이 줄어드는 걸 느끼는 데 이럴때는 어떻게 해야 하나요?

답변 :
단위테스트는 제품코드가 아니기 때문에 금방 더러워지기 쉽습니다. 그래서 더욱 extract method 리팩토링으로 코드를 정리해 줘야 합니다. 그리고, 테스트를 작성, 유지보수 하는데 드는 비용이 테스트를 통해서 얻을 수 있는 효용보다 적다고 생각된다면 테스트를 작성하지 않는게 좋습니다. 하지만, 초반에 비용이 많이 든다고 해서 너무 빨리 포기하지는 마세요. 테스트 환경이 만들어질 수록 점점 빠르고 쉽게 테스트를 작성, 유지보수 할 수 있습니다. 아래 'xUnit 테스트 패턴' p.97 의 그림 3.1 과 그림 3.2 를 참고해 주세요.
xUnitTimeline.PNG

아래 동영상은 [http]PROJECT SIKULI 데모 영상입니다.
일종은 기록 테스트(Recorded Test) 툴인데, 이미지 인식을 하는 것 처럼 보이네요. 재미있습니다.


그 외에도 [http]Selenium 이라는 web application testing system 도 있고, [http]hamcrest 라는 객체 matcher 라이브러리도 있네요.

덧글

  • nzin4x 2010/08/09 11:50 # 답글

    sikuli 는 일단 한글이 안되고 windows 에서는 거의 작동을 안 하는거 같습니다.
    저런 툴이 완성되면 GUI 도 단위테스트도 가능할 것으로 예상됩니다.
  • 박PD 2010/08/09 13:51 #

    아쉽긴 하지만, 곧 비슷하면서 더 좋은 툴이 나오겠지요 :)
  • 지나가는이 2010/09/02 18:41 # 삭제 답글

    sikuli가 아직 multi-language 환경을 지원하지 않긴 하지만 윈도우 제어판에서 국가 및 언어 형식을 영문(미국)으로 설정하면 사용할 수 있습니다.
    그리고 찾아보면 아주 다양한 자동화 도구들이 있습니다. (각 도구마다 지원 가능한 분야가 다릅니다)
    Selenium, SWAT, STAF, Fitnesse, NTAF, FlexMonkey, AutoIt, QTP, WebAii, ...
    그런데 이런 도구들은 일반적으로 통합 테스트 도구라고 부르고 단위 테스트 도구라 부르지는 않습니다..^^
  • 박PD 2010/09/02 22:00 #

    좋은 댓글 감사합니다.
댓글 입력 영역


Yes24위대한게임의탄생3

위대한 게임의 탄생 3
예스24 | 애드온2