테스트가 주는 안정감

최근에 하고 있는 프로젝트(대충 12월?)에 사용하고 있는 서버 객체 중의 하나를 작성하고 있는데, 이 부분이 실제로 동작할 때 통신해야하는 다른 서버 객체(이하 객체랑 마구 혼용)가 4종류나 되서 아예 작정하고 가짜 객체(mock-object)를 사용한 TDD를 시험해보고 있다.

가짜 네트웍 레이어를 만들었다

네트웍 데이터를 큐잉해주고, 이걸 해당하는 서버 객체랑 연결해서 메시지를 전달하게 했다. 네트웍 특유의 비동기적인 상황이 싹 정리되고 나니

“내 네트웍 코드도 이랬으면 좋겠어!”

싶은 단순한 소켓 레이어가 나오더라. 여튼 그 녀석을 통해서 서버 객체를 연결하는 작업을 했다.

가짜 서버 객체들을 만들었다

다행히 연결해야할 객체들 중에 2종류는 이미 만들어진 mock-object가 존재해서, 쉽게쉽게 연결이 되고 있다. 반면에 나머지 두 개는…

두 개 중 하나 (아마 통신량이 가장 많을?)을 요즘 수정하면서 _진짜 코드_를 수정하는 방식으로 TDD를 진행하고 있다. 미리 특정 형식의 메시지에 어떤 반응을 할지만 정해놓고, 원래 서버에 메시지를 주고 가짜 네트웍 레이어의 큐를 처리해서 내가 짠 코드들이 제대로 도는지 검사.

대충 이 두가지 과정이 끝나고 나서부터는 — 물론 가짜 서버 객체들도 본래 코드가 변화함에 따라 같이 변화한다.1 계속 테스트 묶음을 추가하고, 코드를 고치고, 테스트를 통과하고를 반복하고 있다.

예전에 내가 해왔던 테스트 추가가 늦은 방식에서는 버그처리하는게 좀 귀찮았던 것 같은데, 요즘은

  • 리포팅된 버그를 (혹은 내가 찾은 버그를) 재현하는 테스트를 짜고
  • 디버깅을 거쳐2
  • 테스트를 통과하면 성공

물론 테스트 돌리는거야 자동으로 해주니(VUTPP덕분에 개별 테스트만 돌리는 것도 쉽고), 테스트 묶음은 계속 유지하면서 진행 중. 테스트하려면 띄우는게 많다고 시작한 방식인데 mock-object들이 모양새가 좀 나기시작하니 프로그래밍 작업 자체가 안정성있고 — 직전에 고친게 이전 가정들을 안깼다는걸 확인해주니 — 일정한 사이클덕에 리듬을 타게되는 효과도 있는 듯 하다.

이제 최종적으로 남은 부분은 연동 테스트할 때 dll 두 번 로드하면 사망하는 녀석의 처리인데, 해당하는 부분은 test-fixture3에 들어가있어야하는데, 정작 dll load/unload가 내가 볼 수 없는 코드 영역에 있어서 참 난감하다. 이거 어떻게 피해가지…


  1. TDD에선

    1. 테스트를 고치고
    2. 테스트에 따라 동작하지 않는(심지어 컴파일도 안될 수 있는) 코드를 고치고
    3. 테스트를 통과하면 다시 1로

    가는 과정을 반복하니까, 이걸 mock-object랑 생각하면 저렇게 되겠다. ↩︎

  2. 테스트 만드는 방식도 그렇고, 디버깅하는 과정도 그렇고 Zeller의 “프로그램은 왜 실패하는가?; Why Program Fails"를 요즘 다시 보고 있다. 이건 리뷰도 제대로 안 썼으니 읽고나면 쓸지도. ↩︎

  3. 유닛 테스트 프레임웍등에서 말하는 어떤 테스트 시작시에 초기화/테스트 종료 후에 소멸되는 도우미 객체. 각종 전역 초기화/뒷정리를 진행한다. ↩︎