Test, Fixture and Persistency

이번 주에 프로그래밍 하면서 제일 괴로웠던 부분.

일종의 비동기 작업을 실행하는 class 가 있는데, 이 녀석을 test-framework 이랑 같이 쓰는 부분에서 문제가 산적 Orz.

DLL과 TLS 문제

사실 이건 비동기라서 터진건 아니지만, 내부적으로 사용된 Windows의 __declspec(thread) 선언이 테스트가 DLL로 링크되서 실행된다는 면에서 터져서 (원본이야 그냥 실행 바이너리지만…) 일단 테스트 방법 자체를 뒤집어 엎었고..

비동기 문제

연초에 테스트를 어떻게 할지 생각하면서 내린 결정 중에, “동기화 객체들은 테스트 프레임웍이 관리하고 / 싱글 스레드로 동작해도 옳은 결과가 나온다"가 있다.

비동기 실행(asynchronus execution)이란 것 자체가 이 가정을 거의 깨버린다. 지금은 일단 비동기 모듈과 닿아있는 부분이 매우 작아서, 테스트 프레임웍에서 수동으로 비동기 실행을 동기 실행으로 바꿔서 호출해버리고 있긴한데… 상당히 불편하다. “직접 호출해 줘야 한다"라는게 더더욱.

Persistent Fixture

xUnit 류의 모든 테스트들은 기본적으로 다음과 같은 순서로 동작한다.

  1. setUp() – 테스트 환경에 대한 초기화로 fixture 를 생성한다
  2. Test() – 테스트 실행
  3. tearDown() – 테스트 환경을 제거(?)한다. 즉 fixture 를 지운다.1

Fixture는 테스트가 끝나고, 다시 그 다음 테스트가 시작되면 완전히 새로운 초기 상태 로 돌입하는 걸 가정하는데, 이게 persistent 하면 깨지기 쉬워진다. 예를 들어 단순히 초기화만 테스트하고 다시 안만드는 식이라면, 이전 테스트가 비정상적으로 끝나면 그 이후의 테스트가 다 실패한다거나 (이번 주 수목 동안 겪은 문제) 해서 무진장 괴로워진다 -_-;; — 테스트가 오류를 특정 영역에서 난걸 잘 찾아줘야한다는 TDD의 목적 중 하나가 사라지니…

Fixture가 지속적(persistent) 하면 여러가지로 문제가 되는데, 특히 tearDown()을 정의할 수 없는 경우엔 상당히 문제가 크다. 테스트가 프로그램 자체를 손대는건 여러모로 좋지 않다 — 불확정성 원리와 비슷하게 관찰이 행동을 변경 시킬 수 있다.

이에 대해 xUnit Test Patterns에서 제시하는 해결책은 Test Double Pattern이란 것인데,

  • 테스트마다 (각 테스트에 맞는) 반응(?)을 돌려주는 객체(test stubs)을 만들거나,
  • 아예 해당 fixture가 만드는 부분을 mock-object로 만들어내거나,
  • persistent 해보이는 부분을 재생성이 가능하게 고쳐라

라는 정돈데… 일단 다 건너뛰고(…) 캐야매로 tearDown()에서 일일이 제거하는 코드를 넣고, setUp()에선 생성되어 있으면 재 초기화는 안한다… 정도의 느낌으로 바꿔놨다.

이건 UnitTest 레벨이니까 그렇다치고, component level 테스트들에선 슬슬 비동기성 있는 일부 모듈들 — 다만 해당하는 코드 패스가 적어서 아직은 괜찮지만 — 의 문제는 고개를 내밀고 있는듯하다. 다음 주 초에는 이 문제를 잘 생각해봐야겠는데 -_-;;


  1. 사실 1과 3은 합쳐서 하나의 fixture 객채로 생각하고, 그에 대한 생성자/소멸자로 생각하면 쉽다. UnitTest++은 아예 그렇게 구현되어있다. 그러면 예외 같은 문제에서도 상당히 자유로워지기도 하고… ↩︎