티스토리 뷰

반응형

유닛테스트 코드를 짜다 보면 어느덧 검증 로직보다 '테스트를 위한 준비 과정'이 더 길어지는 순간을 마주한다. 특히 특정 비즈니스 상태를 재현하기 위해 여러 도메인 모델을 조합하고 상태를 주입한 픽스처(Fixture) 객체를 공용으로 만들어 쓰기 시작할 때, 불행은 시작된다.

 

흔히 픽스처라고 하면 데이터베이스 연결 상태나 환경 변수 같은 인프라적 설정도 있지만 내가 경계하는 것은 비즈니스 상태를 미리 정의해둔 객체 덩어리다. 편리할 것 같지만 사실 이것은 테스트 코드의 건강을 해치는 독이 될 때가 많다.

 

1. 픽스처 객체는 테스트 코드의 의도를 알기 어렵게 만든다.

공용 픽스처 객체를 사용하는 테스트는 불투명하다. 테스트 코드를 읽을 때, `setupUser()`가 반환하는 픽스처 객체의 어떤 필드가 이 테스트의 성공과 실패를 결정하는지 한눈에 들어오지 않는다. 유저는 VIP 등급인가? 연령이 만 19세 미만인가? 아니면 단순히 이름이 비어 있는 상태인가?

테스트는 그 자체로 명세서가 되어야 하지만 상세한 상태가 픽스처 객체 내부에 숨겨지는 순간 테스트 코드는 해석이 필요한 암호문으로 변질된다.

 

2. 픽스처 객체는 필연적으로 여러 테스트가 의존한다.

비즈니스 요구사항은 끊임없이 변한다. 공용 픽스처 객체에 의존하는 테스트가 100개라고 가정해 보자. 비즈니스 규칙이 바뀌어 픽스처 객체의 기본 생성 로직이나 상태 값을 하나 수정하는 순간, 관련 없는 100개의 테스트가 도미노처럼 무너진다.

이때부터 개발자는 테스트 코드를 '신뢰'하는 것이 아니라 '수발'들게 된다. 테스트를 고치는 시간이 프로덕션 코드를 짜는 시간보다 길어진다면 그 테스트는 이미 자산을 넘어 부채가 된 것이다.

 

픽스처 객체는 생성되는 순간부터 엄연한 관리의 대상이다. 프로덕션 코드의 도메인 모델이 변경될 때마다 픽스처도 동기화해줘야 한다. 다양한 비즈니스 케이스를 대응하기 위해 FixtureA, FixtureB, ComplexFixtureC가 늘어나기 시작하면, 정작 중요한 비즈니스 로직보다 픽스처의 계보를 파악하는 데 더 많은 에너지를 쏟게 된다. 관리 비용이 기하급수적으로 비싸지는 기법인 셈이다.

 

3. 그럼에도 불구하고 유닛테스트에 픽스처를 써야한다면.. 프로덕션 코드를 의심해보자

복잡한 비즈니스 상태를 굳이 객체로 만들어 주입할 필요가 있을까? 유닛 테스트의 본질은 '격리'에 있다. 복잡한 연관 관계나 비즈니스 상태를 가진 객체가 필요하다면, 실제 객체를 생성할 것이 아니라 모킹(Mocking)을 활용해야 한다.

모킹을 사용하면 테스트가 검증하고자 하는 특정 상태만 명시적으로 드러낼 수 있다. 테스트 가독성은 올라가고, 실제 객체의 내부 구현 변경으로부터 테스트를 보호할 수 있다.

 

만약 모킹을 하기에 너무 힘들다라는 생각이 든다면, 그것은 픽스처 객체가 필요한 게 아니라 프로덕션 코드가 설계적으로 실패했다는 신호다.

 

  • 지나친 역할 분담 실패: 하나의 클래스가 너무 많은 비즈니스 규칙을 품고 있어 테스트 준비가 복잡한 것일 수 있다.
  • 낮은 격리 수준: 코드가 유닛 테스트를 할 수 없을 정도로 외부 의존성과 강하게 결합되어 있다는 뜻이다.

반복적인 설정과 복잡한 픽스처가 필요하다는 사실은 역설적으로 코드를 분리하라는 시스템의 신호이다. 픽스처 객체로 이 신호을 덮지 말고, 프로덕션 코드를 격리하고 분리하는 리팩토링을 시작해야 한다.

 

결론

픽스처 객체는 편리해 보이지만 장기적으로는 테스트의 가독성을 해치고 관리 비용을 폭증시킨다. 통합 테스트처럼 반드시 실제 객체 간의 흐름을 봐야 하는 경우가 아니라면 비즈니스 상태는 모킹으로 대체하자. 그리고 모킹조차 힘들 만큼 코드가 꼬여 있다면 픽스처를 만들지 말고 코드를 쪼개자. 그것이 테스트 코드와 프로덕션 코드 모두를 살리는 길이다.

반응형
댓글