← Home

느슨한 테스트 더블 (Loose Test Double) 안티패턴

Key Insight

테스트 더블(mock/stub/fake)이 실제 환경보다 관대하면 — 즉, 실제라면 실패했을 상황에서도 조용히 성공을 반환하면 — 테스트는 “통과”를 계속 보고하면서 진짜 버그를 몇 라운드고 은폐할 수 있다. 사용자가 “여전히 안 된다”고 같은 증상을 반복 보고할 때는, 코드 가설을 미세조정하기 전에 테스트 방법론 자체를 의심하는 것이 더 빠른 진단 경로다.


Overview

이 개념은 Google Sheets 연동 실시간 대시보드 구축 가이드 (TAB Dashboard 사례)의 전신 작업(2026-08-17-sales-dashboard-html-build-session-log, TAB_Dashboard.html v6→v7→v8 라운드)에서 실제로 관찰된 패턴을 일반화한 것이다. 대시보드 자체의 버그가 아니라, 그 버그를 잡으려던 테스트 하네스의 설계 결함이 핵심이다.

Details

실제 사례 (구체적 증거)

TAB_Dashboard.html의 Node.js 회귀 테스트는 브라우저 없이 렌더 함수를 검증하기 위해 커스텀 dom_stub.js(가짜 document)를 썼다. 이 스텁의 document.getElementById(id)는 어떤 id를 요청하든 항상 유효한 가짜 엘리먼트를 새로 만들어 반환했다 — 실제 브라우저라면 존재하지 않는 id에 null을 반환했을 상황에서도.

실제 버그: renderCoreKpis('yr') 함수가 yrRebate/yrFreight/yrExtra라는, YrCompare 패널에는 애초에 존재하지 않는 엘리먼트에 .textContent를 대입하려 시도. 실제 브라우저에서는:

document.getElementById('yrRebate').textContent = ...
// getElementById가 null 반환 → TypeError: Cannot set properties of null

이 예외로 함수가 중단되며, 바로 다음 줄의 renderYrCompareCharts() 호출이 전혀 실행되지 못했다 — “탭 진입 시 차트 미표시”와 “필터 변경 시 차트/표 미갱신” 두 증상을 동시에 설명하는 단일 원인이었다.

하지만 관대한 스텁 환경에서는 getElementById('yrRebate')가 항상 가짜 엘리먼트를 반환했으므로 .textContent = ...가 조용히 성공하고, 테스트는 계속 “통과”를 보고했다. 이 때문에 v6 → v7 → v8, 3라운드 연속 같은 증상이 “고쳤다”고 오판된 채 재발했다.

왜 발생하는가

  1. 테스트 더블을 작성할 당시의 기본 성향: 테스트를 빨리 통과시키고 싶은 유인이, 실패 모드를 정확히 재현하기보다 “일단 뭔가 반환하게” 만드는 쪽으로 스텁을 설계하게 만든다.
  2. 실패 모드가 비대칭적으로 안 보인다: null 반환처럼 실제 환경의 흔한 실패 신호를, 관대한 스텁은 애초에 발생시키지 않는다 — 개발자가 “이 실패 모드가 존재한다”는 사실 자체를 잊는다.
  3. 공용 함수의 암묵적 전제: renderCoreKpis(prefix)처럼 여러 페이지가 공유하는 렌더 함수는, 페이지마다 실제로 존재하는 DOM 엘리먼트 집합이 다를 수 있다(Dash 패널=7개 카드 vs YrCompare 패널=4개 카드) — 이 차이를 방어적으로 처리하지 않으면 재사용이 곧 취약점이 된다.

해결 패턴

  • 엄격한 스텁으로 교체: 실제 배포 HTML/DOM에서 존재하는 id 목록을 파싱해, 그 목록에 없는 id를 요청하면 null을 반환하는 스텁을 쓴다. 이렇게 하면 테스트 환경이 브라우저의 진짜 실패 모드를 재현한다.
  • 방어적 접근자 헬퍼: document.getElementById(id).textContent = x 처럼 직접 체이닝하는 대신, 엘리먼트가 없으면 조용히 넘어가는 setText(id, text) 같은 헬퍼를 표준으로 쓴다 — 단, 이건 “버그를 숨기는” 것과 “의도된 페이지별 차이를 정상 처리하는” 것의 경계에 있으므로, 어떤 id가 어떤 페이지에서 정상적으로 없는지는 별도로 문서화해야 한다.
  • 재발 시 진단 순서 전환: 같은 버그가 “고쳤다”는 보고 후에도 반복되면, 다음 가설을 미세조정하기 전에 “내 테스트가 진짜 실패를 재현할 수 있는가?” 를 먼저 검증한다.

Bias Check

Counter-argument: 모든 테스트 더블을 “완벽하게 실제와 동일하게” 만들려는 시도는 오히려 유지보수 부담과 취약성을 키울 수 있다 — 목표는 “완벽한 재현”이 아니라 “실제 환경의 핵심 실패 모드(예: 존재하지 않는 참조)만큼은 재현”하는 최소 충실도다. Data gap: 이 사례는 단일 프로젝트(TAB_Dashboard.html)에서 관찰된 1건이며, 다른 언어/프레임워크(예: React 컴포넌트 테스트, Python mock)에서 동일 패턴이 얼마나 흔한지는 이 문서만으로 일반화할 근거가 부족하다 — 소프트웨어 테스팅 일반 문헌과 교차검증 필요.