한 줄 정의

더 큰 테스트(larger test)란 단위 테스트의 제약(단일 스레드·프로세스·기기)을 풀어 여러 바이너리·프로세스·기기가 얽힌 시스템 전체가 의도대로 동작하는지 검증하는 테스트입니다. 단위 테스트가 손대지 못하는 충실성·설정·규모·창발적 행위의 위험을 책임지지만, 느리고 비결정적이고 소유권이 모호해지므로 충실성을 유지하면서도 되도록 작게 만들어 개발자 워크플로에 녹여야 합니다.

쉽게 말하면

단위 테스트가 부품 하나하나를 작업대에서 점검하는 일이라면, 더 큰 테스트는 그 부품들을 다 끼운 자동차를 실제 도로에 끌고 나가 시운전하는 일입니다.

엔진도 멀쩡하고 브레이크도 멀쩡하다고 부품 검사서에 다 찍혀 있어도, 정작 엔진과 변속기를 연결하는 볼트 규격이 안 맞거나, 추운 날 시동이 안 걸리거나, 운전자가 매뉴얼에 없는 버튼을 눌렀을 때 차가 멈춰버리는 일은 도로에 나가봐야 압니다. 부품 검사(단위 테스트)는 빠르고 결정적이지만 “조립된 전체가 현실에서 굴러가는가” 는 끝내 말해주지 못합니다.

이 장 전체는 결국 “부품 검사만으로는 출고할 수 없다. 하지만 매번 도로에 끌고 나가기엔 시간·비용·위험이 크니, 시운전을 영리하게 설계하라” 는 하나의 균형 잡기입니다.

왜 중요한가?

내가 짠 코드가 정확히 동작하는지 확인할 때, 단위 테스트만 돌리기보다 실제 바이너리를 직접 띄워 조작해보는 순간이 분명히 있습니다. 코드를 남에게 넘길 때도 상대는 내 단위 테스트를 돌리기보다 바이너리를 직접 써봅니다. 의존하던 외부 API가 새 버전을 내놓았을 때 호환성을 확인하는 일도 단위 테스트로는 거의 도움이 안 됩니다 — 직접 사이트에 들어가 동작하지 않는 기능을 하나씩 찾아봐야 합니다.

단위 테스트는 개별 함수·객체·모듈에 대한 확신을 줍니다. 반면 더 큰 테스트는 시스템 ‘전체’가 의도대로 동작한다는 확신을 더해주고, 수동 테스트와 달리 자동화하면 반복 가능하게 확장됩니다.

핵심은 충실성(fidelity)입니다. 마이크로서비스로 쪼갠 시스템에서 의존 서비스를 충실성 낮은 대역으로 대체하면, 버그가 빠져나갈 확률이 곱셈으로 치솟습니다. 노드가 14개인 SUT에서 사용자 서버와 광고 서버 단 둘만 충실성 10%짜리 대역으로 바꿔도, 버그가 새어 나갈 가능성은 1 - (0.1 × 0.1) = 99% 가 됩니다. 시스템이 커질수록 단위 테스트만으로 메우지 못하는 충실성 공백이 기하급수로 벌어지고, 더 큰 테스트가 그 공백을 책임집니다.

핵심 내용

크기·범위·충실성 — 더 큰 테스트의 좌표축

구글은 테스트를 크기(size) 로 구분합니다. 작은 테스트는 단일 스레드·프로세스, 중간 크기 테스트는 단일 기기로 제한되고, 테스트가 커질수록 이 제약이 하나씩 풀립니다. 여기에 범위(scope) 라는 축이 더해집니다 — 단위 테스트는 통합 테스트보다 범위가 작고, 시스템 테스트는 외부 모듈을 직접 이용하며 대역을 거의 쓰지 않습니다.

더 큰 테스트는 작은 테스트와 따라야 할 제약이 다릅니다.

  • 느릴 수 있습니다. 구글의 대규모 테스트 기본 타임아웃은 15분 또는 1시간이며, 며칠씩 걸리는 테스트도 운용합니다.
  • 밀폐되지 않을 수 있습니다. 다른 테스트나 최종 사용자와 자원·트래픽을 공유하기도 합니다.
  • 비결정적일 수 있습니다. 공유 상태에 영향받아 완벽한 결정성을 보장하기 어렵습니다.

이 모든 특성의 뿌리에는 충실성이 있습니다. 충실성(fidelity) 은 테스트가 대상의 실제 행위를 얼마나 충실하게 반영하느냐를 나타내는 속성입니다. 환경 관점에서 보면 충실성은 하나의 스펙트럼입니다.

flowchart LR
    A["단위 테스트"] --> B["단일 프로세스<br/>SUT"] --> C["격리된<br/>SUT"] --> D["스테이징"] --> E["프로덕션"]
    A -.->|"충실성 낮음<br/>비용 낮음"| A
    E -.->|"충실성 높음<br/>실패 시 손해 큼"| E

왼쪽 끝의 단위 테스트는 대상 코드의 작은 일부만 실행 가능한 단위로 묶은 것이라 프로덕션과 매우 다르게 동작합니다. 오른쪽 끝 프로덕션은 충실성이 가장 높지만 실패 시 입는 손해도 가장 큽니다. 더 큰 테스트의 핵심은 이 양극단 사이에서 가장 적합한 지점을 찾아내는 것입니다.

충실성은 환경뿐 아니라 데이터에도 적용됩니다. 공들여 만든 대규모 테스트라도 테스트 데이터가 현실과 동떨어지면 외면받습니다. 단위 테스트용 데이터는 대부분 수작업이라 다루는 사례가 적고 제작자의 편견이 반영되기 쉬운데, 이렇게 누락된 시나리오가 곧 충실성 하락으로 이어집니다.

단위 테스트가 손대기 어려운 다섯 영역

더 큰 테스트가 존재하는 근본 이유는, 단위 테스트로는 위험을 충분히 해소하지 못하는 영역이 있기 때문입니다.

영역단위 테스트의 맹점
부정확한 테스트 대역대역은 테스트 작성자가 만들지만, 작성자가 실제 의존 대상까지 담당하는 경우는 드뭅니다. 합의된 약속을 오해하면 대역과 실제가 따로 놀고, 모의 객체는 실제 구현이 바뀌어도 갱신 신호를 주지 못합니다
설정 문제바이너리는 배포 설정·시작 스크립트·설정 DB 없이 단독 실행되지 않습니다. 설정은 코드보다 빠르게 프로덕션에 배포되는데 단위 테스트는 이 호환성을 검증하지 못합니다 — 구글의 가장 심각한 장애 원인이 바로 설정 변경이었습니다(2013년 미검증 네트워크 설정으로 전 세계 장애)
과부하 시 문제성능·부하·스트레스 테스트는 초당 수천~수백만 쿼리 수준의 트래픽을 일으키므로, 작고 빨라야 하는 단위 테스트 모델에 녹이기 어렵습니다
예기치 못한 동작·입력·부작용단위 테스트의 범위는 작성자의 상상력에 갇힙니다. 하이럼의 법칙대로, 약속된 기능을 100% 테스트해도 사용자는 명시되지 않은 모든 것을 자유롭게 이용합니다
창발적 행위와 ‘진공 효과’단위 테스트는 빠르고 안정적이려 현실의 혼돈(실제 네트워크·데이터)을 의도적으로 배제합니다 — 진공 상태를 가정한 물리 문제처럼, 속도와 안정성은 뛰어나지만 특정 범주의 결함을 놓칩니다

하이럼의 법칙

어떤 API에 충분히 많은 사용자가 있다면, 명세에 무엇을 약속했든 상관없이 시스템의 관찰 가능한 모든 동작에 누군가는 의존하게 됩니다. 단위 테스트만으로는 공개 API에 명시되지 않은 이 ‘관찰 가능한 동작’까지 확인할 가능성이 극히 낮습니다.

더 큰 테스트를 꺼리는 이유 — 잃어버리는 세 가지 미덕

앞 장들이 강조한 개발자 친화적 테스트의 미덕을 더 큰 테스트는 하나도 갖추지 못하곤 합니다.

  • 높은 신뢰성 — 결과가 불규칙하면 안 되고 유용한 성공/실패 신호를 줘야 하는데, 더 큰 테스트는 인프라를 많이 쓰는 만큼 비결정적입니다.
  • 빠른 속도 — 개발 워크플로를 방해하지 않을 만큼 빨라야 하는데, 설정·실행 모두 훨씬 오래 걸립니다.
  • 높은 확장성 — 변경에 영향받는 모든 테스트를 서브밋 전후로 효율적으로 실행해야 하는데, 자원을 많이 쓰고 격리되지 않아 서로 충돌합니다.

여기에 더 큰 테스트만의 과제 둘이 따라붙습니다.

소유권

단위 테스트는 검증하는 단위를 소유한 엔지니어와 팀이 테스트도 소유하므로 누가 주인인지 명확합니다. 반면 더 큰 테스트는 다수의 단위에 걸쳐 있어 관련 소유자가 많고, 시간이 흐를수록 소유권이 모호해집니다. 유지보수 책임과 실패 진단 책임이 불분명하면 테스트는 서서히 부패합니다.

표준화 부족

단위 테스트는 언어 차원에서 작성·실행·디버깅이 표준화돼 있습니다. 반면 더 큰 테스트는 시스템 아키텍처에 따라 실행 방식이 제각각입니다 — 구글 광고·검색 백엔드·드라이브의 A/B 회귀 테스트는 플랫폼·언어·인프라·프레임워크가 모두 다릅니다. 표준이 없으니 인프라 지원을 받기 어렵고, 대규모 변경 중에는 아예 수행하지 않는 일이 빈번하며, 신규·숙련 엔지니어 모두에게 단일한 방식을 가르칠 수 없습니다.

수명과 아이스크림 콘 안티패턴

어떤 테스트가 적합한지는 코드의 기대 수명에 따라 달라집니다. 수명이 몇 분짜리 일회성 스크립트라면 수동 테스트가 가장 합리적이고, 수명이 길어질수록 자동화된 테스트의 가치가 커지되 관심사는 점점 유지보수로 옮겨갑니다.

문제는 초기 선택이 굳어버린다는 점입니다. “이 코드는 금방 쓰고 버릴 것”이라 판단해 수동 테스트에 의존하면, 수동 테스트가 누적되어 초기 포트폴리오 전체를 지배하는 아이스크림 콘 안티패턴이 생깁니다.

flowchart TD
    subgraph 안티패턴["아이스크림 콘 (나쁨)"]
        I1["수동 테스트 (최다)"] --> I2["자동화된 GUI 테스트"] --> I3["통합 테스트"] --> I4["단위 테스트 (최소)"]
    end
    subgraph 피라미드["테스트 피라미드 (좋음)"]
        P1["단위 테스트 (최다)"] --> P2["통합 테스트"] --> P3["종단간 테스트 (최소)"]
    end

더 나쁜 경우, 처음 구현 방식 때문에 코드가 단위 테스트하기 어렵게 짜여 있으면 자동화할 수 있는 건 오직 종단간 테스트뿐입니다 — 이를 두고 “단 며칠 만에 실수로 ‘레거시 코드’를 만들었다” 고 표현합니다.

건강한 상태를 오래 유지하는 핵심

개발 시작 후 며칠 안에 단위 테스트를 만들어 피라미드를 쌓기 시작하고, 수동 종단간 테스트를 자동화된 통합 테스트로 대체해 위층으로 올립니다. 구글은 코드를 서브밋하려면 ‘반드시’ 단위 테스트를 포함하도록 규정해 이를 강제했습니다.

큰 테스트의 구조 — SUT · 데이터 · 검증

큰 테스트는 작은 테스트의 제약이 없어 어떤 형태로든 만들 수 있지만, 대부분 공통 흐름을 따릅니다: ①테스트 대상 시스템 확보 → ②필요한 데이터 준비 → ③대상 시스템으로 동작 수행 → ④행위 검증.

테스트 대상 시스템(SUT)

큰 테스트의 핵심은 테스트 대상 시스템(system under test, SUT) 입니다. SUT의 형태는 두 요소가 충돌하며 결정됩니다.

  • 밀폐성(hermeticity) — SUT가 테스트 대상과 무관한 구성요소를 사용하거나 상호작용하지 않는 정도. 밀폐성이 높으면 동시성 문제나 불규칙한 인프라의 영향을 적게 받습니다.
  • 충실성 — SUT가 프로덕션 시스템을 충실히 반영하는 정도. 비슷한 설정·인프라·토폴로지로 구성될수록 높습니다.

이 둘은 자주 충돌하며, 그 절충에 따라 SUT 형태가 갈립니다.

SUT 형태구성밀폐성 / 충실성
단일 프로세스 SUTSUT 전체 + 테스트 코드까지 하나의 바이너리로 패키징밀폐성 최고 / 충실성 최저 (프로덕션 토폴로지와 가장 멂)
단일 머신 SUT독립 바이너리 여럿 + 별도 테스트 바이너리, 모두 한 기기에서 구동 (= 중간 크기 테스트)균형 / 프로덕션 실행 설정을 그대로 써 충실성 보강
다중 머신 SUTSUT를 여러 머신에 분산 (클라우드 프로덕션과 유사)충실성 높음 / 머신 간 네트워크가 불안정성을 키움
공유 환경 (스테이징·프로덕션)SUT를 따로 띄우지 않고 운용 중인 공유 환경을 직접 사용추가 비용 최저 / 충돌·최종 사용자 영향 위험
하이브리드일부 구성요소는 직접 실행, 일부는 공유 환경의 서비스와 상호작용빠르게 확장되는 회사에서는 사실상 불가피

가능한 한 작은 테스트 — 연쇄 테스트

통합 테스트라도 작을수록 좋습니다. 테스트 범위는 SUT 범위와 직결되므로 SUT를 작게 만드는 게 곧 테스트를 작게 만드는 길입니다. 여러 내부 시스템이 얽힌 기능이라면 연쇄 테스트(chain test) 가 유용합니다 — 기능 전체를 한 번에 아우르는 대신 작은 통합 테스트로 나눠 연결하고, 앞 단계 결과를 리포지터리에 저장해 다음 단계 입력으로 씁니다.

밀폐된 SUT가 이상적이지만 늦게 준비된다는 약점이 있습니다. 프로덕션 환경 테스트는 환경 구축 부담이 없는 대신 릴리스 시점을 직접 통제하지 못하고, 거대한 공유 스테이징 환경은 테스트 코드가 반영된 후에야 테스트할 수 있습니다. 결국 다음 단계는 클라우드에서 격리된 영역이나 밀폐 가능한 환경을 만들어 그 안에 SUT를 배포하는 것입니다 — 그래야 충돌 걱정이나 시간 예약 없이 코드를 릴리스할 수 있습니다.

프로덕션 테스트의 위험 — Webdriver Torso 사건

구글은 유튜브 영상 렌더링을 확인하려 테스트 영상을 업로드·검증하는 스크립트를 Webdriver Torso라는 채널에서 돌렸습니다. 그런데 이 채널이 다른 채널처럼 공개돼 있었던 탓에, 수수께끼 채널로 화제가 되어 미디어가 달려들었습니다. 잘 마무리됐지만 프로덕션에 심은 테스트 데이터를 최종 사용자가 발견할 수 있음을 항상 염두에 둬야 한다는 교훈을 남겼습니다.

SUT 크기 줄이기 — 경계에서 자르기

테스트하다 보면 피해야 할 고통스러운 경계가 있습니다.

  • 프런트엔드/백엔드 경계 — UI 테스트는 신뢰하기 어렵고 비용도 큽니다. 룩앤필이 자주 바뀌어 실제 동작이 그대로여도 깨지기 쉽고, 비동기 반응이라 테스트하기 어렵습니다. 백엔드가 공개 API를 제공한다면 테스트를 UI/API 경계에서 나누고, 종단간 테스트는 공개 API로 수행하는 편이 훨씬 쉽습니다.
  • 서드파티 의존성 — 서드파티는 테스트용 공유 환경을 제공하지 않고, 호출당 과금되기도 합니다. 실제 서드파티 API를 직접 쓰는 자동 테스트는 권장하지 않으며, 여기가 테스트를 분할하는 중요한 경계가 됩니다.
기록/재생 프록시

보조적 의존 서비스라면 테스트 대역으로 대체할 수 있지만, 그 대역이 원래 대상과 똑같이 동작한다는 보장은 없습니다. 구글 외부에서는 고객 주도 계약(consumer-driven contract) 테스트(Pact, Spring Cloud Contracts)가 늘고 있지만, 구글은 프로토콜 버퍼를 광범위하게 써 이를 쓰지 않습니다.

대신 가장 많이 쓰는 방식이 기록/재생(record/replay) 입니다. 공개 API가 있다면 더 큰 테스트를 실행해 외부 서비스와의 트래픽을 기록(record)해뒀다가, 작은 테스트에서 재생(replay)합니다.

  • 기록 모드 — 포스트서브밋(서브밋 직후) 테스트로 항시 수행해 트래픽 로그를 생성합니다.
  • 재생 모드 — 개발 중 또는 프리서브밋(서브밋 직전) 테스트로 활용합니다.

비결정성을 없애기 위해 매칭기(matcher) 로 요청을 기대 응답과 연결하는데, 스텁이 인수를 보고 결과를 결정하는 방식과 닮았습니다. 클라이언트 행위가 크게 바뀌면 기록한 트래픽과 더 이상 맞지 않아 재생 모드가 실패하므로, 엔지니어가 기록 모드를 다시 수행해 새 트래픽을 생성해야 합니다. 그래서 기록 모드를 쉽고 빠르고 안정적으로 만드는 일도 중요합니다.

테스트 데이터

큰 테스트에는 두 가지 데이터가 필요합니다 — 테스트 개시 시점의 SUT 상태를 잡아주는 시드 데이터(seed data) 와, 수행 과정에서 SUT로 보내는 테스트 트래픽입니다. SUT는 독립 실행되고 크기 때문에 테스트 전 상태 초기화(setup)가 단위 테스트의 셋업보다 훨씬 복잡합니다(도메인 데이터 사전 적재, 현실적 기준선, 데이터 기록 API 등).

데이터는 다음과 같이 만듭니다.

생성 방식특징
손수 가공작은 테스트처럼 직접 제작. SUT 규모가 커지면 작업량이 가파르게 증가
복사프로덕션 데이터를 복사해 기준으로 삼음. 너무 거대할 수 있음
샘플링 (smart sampling)복사본이 거대할 때 표본만 추출. 커버리지를 최대화하는 최소 데이터를 뽑는 기술
A/B 비교두 SUT에 똑같은 데이터를 보내고 결과 차이를 사람이 살펴 의도된 변경인지 확인

검증

SUT가 구동되고 트래픽이 보내졌다면 제대로 작동했는지 검증해야 합니다.

  • 수동 검증 — 사람이 직접 상호작용. 정의된 회귀 절차를 일관되게 수행하거나, 색다른 시나리오로 잠재 결함을 찾는 탐색적 테스팅에 씁니다. 단, 선형으로 확장되지 않습니다(시스템이 커지면 시나리오는 더 빠르게 늘어남).
  • 단정문(assertion) — 단위 테스트처럼 시스템이 의도대로 동작하는지 명확히 검사. 예: 구글 검색에서 'xyzzy'를 검색하는 통합 테스트라면 assertThat(response.Contains("Colossal Cave")).
  • A/B 비교 — 두 SUT의 결과를 비교해 사람이 의도된 차이인지 확인.

더 큰 테스트 유형

SUT·데이터·검증을 조합하면 다양한 큰 테스트가 나옵니다. 구글에서 쓰는 종류는 위험 요소를 무엇으로 완화하느냐에 따라 갈립니다.

유형SUT데이터검증핵심 역할
기능 테스트 (둘 이상 바이너리)밀폐 단일 머신 / 격리 클라우드수동 생성단정문마이크로서비스처럼 여러 바이너리의 실제 상호작용을 공개 API로 검증
브라우저·기기 테스트위와 동일수동 생성단정문프런트엔드를 통해 앱을 쓰는 서드파티 시점 검증
성능·부하·스트레스격리 클라우드수동 또는 프로덕션 복사A/B 차이(성능 지표)버전업 시 성능 저하 없는지, 목표 트래픽 감당하는지. 프로덕션과 비슷한 자원·동일 컴퓨터에서 수행해야 노이즈 제거
배포 설정 테스트밀폐 단일 머신 / 격리 클라우드없음단정문SUT가 설정 파일과 통합돼 제대로 구동되는지 보는 스모크 테스트
탐색적 테스팅프로덕션 / 공유 스테이징프로덕션 수집·알려진 시나리오수동새 실행 경로로 의문스러운 동작·보안 취약점 발굴. 구글은 버그 파티(bug bash) 로 운영. 발견한 결함은 자동 테스트로 만들어 재발 방지
A/B 차이(회귀) 테스트격리 클라우드 둘프로덕션 복사·샘플링A/B 차이구·신버전 공개 API로 트래픽을 보내 반응 차이를 기대/회귀로 분류. 구글에서 가장 즐겨 쓰는 저렴·자동화 가능 테스트
사용자 인수 테스트(UAT)밀폐 단일 머신 / 격리 클라우드수동 생성단정문공개 API로 사용자 여정이 의도대로 이뤄지는지 보장(Cucumber, RSpec). 단, 구글은 잘 안 씀
프로버·카나리 분석프로덕션프로덕션 수집단정문 + A/B 차이프로덕션 자체의 건강 보장
재해 복구·카오스 엔지니어링프로덕션프로덕션 수집·결함 주입수동 + A/B 차이예기치 못한 실패에도 시스템이 굳건한지
사용자 평가프로덕션프로덕션 수집수동 + A/B 차이사용자가 서비스를 어떻게 쓰는지 데이터 수집

몇 가지는 따로 짚어둘 만합니다.

A/B 차이 테스트 — 구글의 주력

단위 테스트는 작은 코드 영역의 예상 경로만 검사하지만, 하이럼의 법칙대로 공개 API는 명세대로만 쓰이지 않습니다. A/B 차이 테스트는 기준이 되는 바이너리와 변경한 바이너리를 모두 띄우고, 제3의 바이너리가 동일한 트래픽을 보내 두 반응의 차이를 기대/회귀로 구분합니다. 자기 자신과 비교하는 A-A 테스트(비결정성·노이즈 식별), 대기 중 변경까지 반영한 A-B-C 테스트(누적 영향 관찰) 같은 변형도 있습니다.

다만 발견한 차이가 좋은지 나쁜지가 자명하지 않아 사람의 인가(authorization)가 끼어들고, 예상 못 한 노이즈가 차이를 오염시키며, 의미 있는 차이를 끌어낼 커버리지 확보가 어렵다는 한계가 있습니다.

프로버와 카나리 분석

프로버(prober) 는 프로덕션 환경에 단정문을 수행하는 기능 테스트입니다 — www.google.com에 접속해 검색이 결과를 반환하는지만 확인하는 식의 잘 알려진 읽기 전용 동작 검증입니다. 카나리 분석(canary analysis) 은 신버전을 프로덕션 일부에만 배포(카나리아)하고 신·구 버전의 상태 지표를 비교해 문제 발생을 조기에 잡습니다. 둘 다 프로덕션에서 이뤄지므로 문제가 포착됐다는 건 이미 사용자에게 영향을 줬다는 뜻이라는 공통 한계가 있습니다.

재해 복구와 카오스 엔지니어링

구글은 매년 DiRT(Disaster Recovery Testing) 워 게임으로 거의 전 지구적 재난 규모의 결함을 인프라에 주입합니다(데이터센터 화재, 마운틴 뷰 본사 완전 격리 등). 기술적 단점뿐 아니라 핵심 의사결정권자와 연락이 끊긴 상황까지 점검합니다. 비용이 커 드물게 수행합니다. 반면 넷플릭스가 유명하게 만든 카오스 엔지니어링은 시스템에 꾸준히 결함을 심어 관찰하는 ‘지속적 테스트’에 가깝고, 구글은 Catzilla로 매주 수천 번 수행합니다. 둘 다 시스템이 내결함성(fault tolerance) 을 갖췄다는 전제에서만 안전합니다.

사용자 평가 — dogfooding·실험·평가자 감정
  • 개밥 주기(dogfooding) — 직원 등 일부 사용자가 프로덕션에서 새 기능을 미리 맛보게 해 값진 피드백을 얻습니다.
  • 실험(experimentation) — 새 기능을 일부 사용자에게 알리지 않고 제공한 뒤 지표로 실험집단/통제집단을 비교합니다. 유튜브 ‘싫어요’ 수 비표시 실험, 애드워즈 광고 배경 변경 실험이 대표 사례입니다.
  • 평가자 감정 — 변화된 결과를 인간 평가자에게 보여주고 선호를 고르게 합니다. 명백한 정답 없이 더 좋고 나쁨만 존재하는 머신러닝 시스템에 특히 중요합니다.

개발자 워크플로에 녹이기

큰 테스트도 워크플로에 통합돼야 가치가 있습니다. TAP 같은 표준 지속적 빌드를 활용하지 못하더라도, 포스트서브밋 단계에서 큰 테스트를 자동 수행하는 별도 지속적 빌드를 갖추고 프리서브밋에서도 수행하도록 독려합니다(작성자에게 직접 피드백이 가도록). 프리서브밋을 포기하면 결함이 모노리포까지 배포돼 롤백 비용이 커지므로, ‘개발자의 고통’과 ‘지속적 빌드 신뢰성’ 사이에서 절충해야 합니다.

속도 개선하기

엔지니어는 느린 테스트를 기다려주지 않습니다. 가장 좋은 방법은 범위를 줄이거나 작은 테스트로 나눠 병렬 수행하는 것입니다. 안이한 테스트는 sleep()으로 비결정적 결과를 기다리지만, 더 큰 테스트에선 마이크로초 단위 폴링 + 타임아웃, 이벤트 핸들러 구현, 완료 알림 구독으로 바꾸는 게 낫습니다.

sleep()과 타임아웃의 함정

sleep()에 의존하는 테스트는 시스템에 과부하가 걸리면 줄줄이 실패하고, 그러면 더 자주 수행해야 해 부하가 더 커지는 악순환이 시작됩니다. 내부 시스템 타임아웃은 더 큰 문제도 일으킵니다 — 프로덕션은 내부 오류를 매끄럽게 처리해 사용자에게 영향을 안 주지만(예: 광고 시스템이 제때 응답 못 하면 500 대신 광고를 게시하지 않음), 테스트 수행자에겐 그저 불규칙한 동작으로만 비칩니다. 타임아웃 때문에 발생한 일시적 문제인지 진짜 코드 결함인지 명확히 구분해 알려주고, 내부 타임아웃 값을 쉽게 조정할 수 있게 해야 합니다.

이해되는 테스트 만들기

실패 메시지가 ‘단정문 실패’와 스택 추적만 달랑 출력하는 게 최악입니다. test_ReturnsOneFullPageOfSearchResultsForAPopularQuery처럼 무엇을 기대했는지를 이름과 메시지로 명확히 알려야 합니다. 호출 체인이 여러 프로세스를 넘나들어 스택 추적이 무력하므로, 구글은 Dapper로 각 호출에 ID를 부여해 RPC 체인 전체를 추적합니다. 또한 테스트 소유자·지원 책임자 연락처를 제공해 수행자가 도움을 청할 수 있게 합니다.

소유권

큰 테스트에는 반드시 소유자가 문서로 기록돼야 합니다. 통합 테스트는 프로젝트 리드가, 기능 테스트는 기능 소유자(feature owner)가 소유합니다. 소유권 정보가 잘 구조화돼 있으면 자동화 시스템을 구축할 수 있습니다 — 독립 실행형 테스트는 모노리포의 OWNERS 정보로 소유자를 자동 파악하고, 한 클래스에 메서드별 소유자가 다르면 언어별 애너테이션으로 메서드마다 소유자를 명시합니다.

비교 / 트레이드오프

단위 테스트 vs 더 큰 테스트
관점단위 테스트더 큰 테스트
검증 대상개별 함수·객체·모듈시스템 ‘전체’의 동작
속도·결정성빠르고 결정적느리고 비결정적일 수 있음
충실성낮음 (현실 혼돈 배제)높음 (프로덕션에 근접)
소유권명확 (단위 소유자 = 테스트 소유자)모호 (여러 단위에 걸침)
표준화언어 차원에서 표준아키텍처마다 제각각
주된 가치빠른 피드백·회귀 방지설정·규모·창발적 행위의 위험 완화
충실성 ↔ 비용·밀폐성의 균형

스펙트럼의 양극단(단위 테스트 / 프로덕션) 사이에서 충실성을 올리면 비용·비결정성이 오르고, 밀폐성을 올리면 충실성이 떨어집니다. 더 큰 테스트 설계는 이 두 축 사이에서 합리적인 경계를 찾는 일입니다 — Ch13의 “되도록 실제 구현을 써라”와 같은 정신으로, 충실성을 지키되 SUT를 되도록 작게 만드는 게 관건입니다.

내 생각

  • 마이크로서비스 통합 테스트의 충실성 곱셈이 핵심 교훈입니다. 의존 서비스를 목으로 대충 세울수록 (1-ε) 곱이 무너져 버그 통과율이 치솟습니다. 테스트컨테이너로 실제 DB·메시지 브로커를 띄우는 통합 테스트가 비싸 보여도, 목 떡칠보다 충실성 면에서 압도적으로 남는 장사입니다.

  • sleep() 대신 폴링+타임아웃은 통합 테스트의 기본기입니다. 비동기 처리·이벤트 결과를 Thread.sleep(2000)으로 기다리면 CI 부하 따라 줄줄이 깨집니다. Awaitility 같은 폴링 유틸로 “조건 충족까지 최대 N초”로 바꾸면 빠르면서 안 깨집니다.

  • A/B 차이 테스트는 마이그레이션의 안전벨트입니다. 레거시 API를 신규 구현으로 바꿀 때, 같은 트래픽을 두 버전에 흘려 응답 diff를 보는 섀도잉이 단정문 테스트보다 회귀를 훨씬 잘 잡습니다. 단, 노이즈(타임스탬프·정렬 순서) 정규화가 선행돼야 합니다.

  • 소유권 명시가 큰 테스트 부패를 막는 실전 장치입니다. 누가 고칠지 모르는 통합 테스트는 결국 @Disabled로 꺼집니다. CODEOWNERS·애너테이션으로 실패 시 연락처를 박아두는 게 테스트 수명을 좌우합니다.

관련 개념

  • Ch13 테스트 대역 — 대역의 충실성 절충. 이 장은 대역으로 못 채우는 충실성 공백을 더 큰 테스트로 메운다는 흐름의 연장
  • Ch12 단위 테스트 — ‘상태를 테스트하라’, ‘행위를 테스트하라’. A/B 차이·상호작용 검증의 토대
  • Ch11 테스트 개요 — 테스트 크기(작은/중간/큰)와 범위, 테스트 피라미드의 정의