알라딘MGG와이드바


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

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

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

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

A 님 :
번역하신 책을 보면서 요즘 테스팅을 하고 있는 사람입니다.
Mock Object에 대한 질문이 있습니다. 참고로 전 [http]easymock 을 사용하고 있습니다.

질문 1 : 테스팅을 할 때 get 함수들에 대해서는 테스팅을 수행할 수 있지만 set 함수들에 대해서는 어떻게 해야 하는지 잘 모르겠네요. 단순히 호출 여부만 확인하는 건가요? 혹시 set을 한 후에 get을 할 수 있는 방법이나 뭐 이런건 없나요?

답변 1 : 저는 단순한 set 함수에 대해서는 테스팅을 하지 않습니다. 하지만, set 함수에서 인자값으로 어떤 계산을 한다거나 다른 함수를 호출하는 경우에는 getter 나 동작 검증 등을 통해서 테스팅을 합니다. 테스트를 만드는 비용 대비 효과를 고려하는 게 좋습니다.

질문2 :
Mock Object를 사용하다 보면 실제 객체 생성이 되는 것이 아니다 보니 단순히 expect를 이용해서 get 함수에 대한 반환값을 정한 후 그냥 그걸 테스팅하는 단순한 경우가 대부분인거 같습니다. 이렇게 하는 것이 Unit test 인가요? 직접 함수에 대해 반환 값을 정한 후 테스팅을 함수를 수행했을 때 단순히 그 값이 반환되는가를 확인한다면 이것이 의미있는 테스트인가요?

아님 실제 객체들을 다 만들어서 해야하는건가요? 이건 정말 배보다 배꼽이 더 큰 경우일거 같은데….

답변 2 :
저는 주로(거의) C++ 만 다뤄왔습니다. 그래서 easymock 같은 mock framework 는 거의 쓰지 않았고, 대신 SUT 클래스나 인터페이스를 상속받는 mock class 를 이용했습니다. 또는 #define 을 이용한 테스트 훅을 쓰기도 했습니다. mock framework 이 구축되어 있지 않은 상태에서는 간단한 테스트를 만드는 데에도 손이 많이 갑니다만, 한 번 구축하고 나면 DIY(do it yourself) framework 가 그렇듯이 활용도가 높은 프레임워크를 만들 수 있습니다. 그리고 가능하면 mock 이 필요없도록 정문 테스팅을 먼저 하려고 합니다.

질문 3 :
제가 제일 궁굼한 건 이미 코드가 만들어진 경우 즉, 레거시 코드가 존재하는 경우 그 코드에 대한 유닛 테스트를 하고 싶은데... 사실 전 이번에 투입되서 기존 코드에 대해서는 잘 모르는 상황에서 테스트를 하다보니... 특히 다른 클래스에 의존성을 많이 갖는 코드를 테스트 할 때, 기존에 존재하는 정보에 대해서 잘 모르다보니 자꾸 Mock Object를 사용하게 되고... 그러다 보니 단순 테스트 형태가 되고.. 과연 이런 테스트가 필요할까? 의미가 있을까? 라는 의문이 들어서요.
내가 모르는 것에 대해 expect로 설정해두고 그 값을 가져와서 쓰는 것이 유닛 테스트는 아닐거란 생각이드는데...
저 처럼 기존 코드에 대해 혹은 의존성이 많은 코드에 대해 테스트할 때 Mock Object를 사용하게 되면 의미 없는 테스트가 되지는 않는지 궁굼하네요. DB나 네트워크에 대해서는 이해가 되지만 저처럼 기존에 이미 존재하는 코드에 대해서도 설정되야 하는 정보를 잘 모른다는 이유로 Mock Object를 사용해도 되는건가지 가장 궁굼합니다.
이런 경우 무조건 이전 정보를 이용해야 한다면 유닛 테스트를 위해 설정해야 하는 정보가 너무 많아질 것이고, Mock을 사용하는 경우가되면 의미없는 테스트가 되지 않나싶어서요.

답변 3 :
저도 거대한 레거시 코드에 단위테스트를 도입하면서 여러가지 어려움이 많았습니다.
작성하신 테스트 코드를 직접 볼 수 있다면 질문 의도를 가장 정확하게 알 수 있을 거 같은데, 회사 코드를 그대로 보내주실 수는 없을테니 toy project 로 비슷하게 만들어서 보내주실 수 없을지 모르겠네요.
우선 제가 이해한 대로만 설명을 드리겠습니다.

단위 테스트 중에 [http]Characterization Test (특성화 테스트. Working Effectively with Legacy Code 책 13장) 라는 게 있습니다. 잘 이해 안 되는 모듈이나 클래스에 테스트를 추가하거나 리팩토링을 하기 위해 코드를 수정해야 하는데 레거시 코드다보니 테스트가 없어서 고친 것 때문에 Regression(회귀) 가 발생하지 않을까 걱정될 수 있습니다. 이럴 때 메서드를 호출한 결과를 무조건적으로 테스트케이스로 만들어서 현재 상태를 기록해 두는 테스트를 Characterization Test 라고 합니다.
단위테스트를 처음 도입할 때는 복잡한 의존관계 때문에 Mock Object 를 써야하는 경우가 많습니다. 하지만, Mock Object 는 SUT(테스트 대상 시스템) 을 대신 하는 객체이기 때문에 Mock 을 너무 과하게 사용하면 실제로 테스트해야 하는 시스템이 아닌 일부분만 테스트하게 됩니다. 가장 의존관계가 없는 부분부터 하나씩 테스트를 붙여나가면서 Mock 을 사용하는 부분을 조금씩 줄여나가는 게 좋겠네요.

void CPlayer::BeAttacked(CActor* pAttacker) {
    int damage = GetDamageFromAttacker(pAttacker);
 
// 먼저 이렇게 함수를 호출해 본 뒤
TEST_FIXTURE(FixtureAttack, GetDamageFromAttacker) {
    pPlayer->m_Level = 40;
    CHECK_EQUAL(0, pPlayer->GetDamageFromAttacker(pMonster);
    pPlayer->m_Level = 50;
    CHECK_EQUAL(0, pPlayer->GetDamageFromAttacker(pMonster);
    pPlayer->m_Level = 60;
    CHECK_EQUAL(0, pPlayer->GetDamageFromAttacker(pMonster);
    pPlayer->m_Level = 70;
    CHECK_EQUAL(0, pPlayer->GetDamageFromAttacker(pMonster);
}
 
// 그 결과를 테스트에 반영해 현재 상태를 기록하는 테스트를
// Characterization Test 라고 합니다.
TEST_FIXTURE(FixtureAttack, GetDamageFromAttacker) {
    pPlayer->m_Level = 40;
    CHECK_EQUAL(760, pPlayer->GetDamageFromAttacker(pMonster);
    pPlayer->m_Level = 50;
    CHECK_EQUAL(400, pPlayer->GetDamageFromAttacker(pMonster);
    pPlayer->m_Level = 60;
    CHECK_EQUAL(223, pPlayer->GetDamageFromAttacker(pMonster);
    pPlayer->m_Level = 70;
    CHECK_EQUAL(0, pPlayer->GetDamageFromAttacker(pMonster);
}

덧글

  • SiroTan。◕‿‿◕。 2010/08/05 12:29 # 답글

    아.. 궁금한점 있으면 물어보겠습니다.
  • 박PD 2010/08/05 13:33 #

    네, 언제든지 환영합니다.
  • 중원 2010/08/06 13:11 # 삭제 답글

    단위테스트를 조금씩 실무에서 적용중인데요. 효과를 많이 보곤 있습니다만,
    테스트가 점점 많아지다보니, 테스트들 관리도 일이 되더군요.
    여기 관련 노하우가 있으시면 공유 부탁드립니다. (__)
  • 박PD 2010/08/06 17:57 #

    http://parkpd.egloos.com/3394408 에 적어보았습니다.
    더 궁금한 점 있으면 알려주세요.
  • ehei 2010/08/07 11:55 # 삭제 답글

    팀내에서 단위테스트를 효과적으로 시행하기 위해 도움이 되었던 정책이나 규칙 같은것이 있는지요? 그리고 좋은 글 잘 보고 있습니다 ^^
  • 박PD 2010/08/07 22:13 #

    http://parkpd.egloos.com/3395808 에 올렸습니다. 댓글 감사합니다.
댓글 입력 영역


Yes24위대한게임의탄생3

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