한 줄 정의

자동 테스트는 버그를 잡는 도구이기 이전에 소프트웨어를 자신 있게 변경할 수 있게 해주는 토대이며, 모두가 작성·공유하고 실패하면 즉시 조치하는 문화로 정착시키되, 속도와 결정성을 위해 작고 좁은 범위의 테스트를 중심으로 균형 잡힌 스위트를 설계해야 합니다.

쉽게 말하면

자동 테스트는 암벽 등반의 확보줄(빌레이) 과 같습니다. 확보줄을 거는 진짜 이유는 떨어졌을 때 죽지 않으려는 것(버그 잡기)도 있지만, 더 중요한 건 떨어질까 봐 손이 굳지 않고 다음 홀드로 과감하게 손을 뻗을 수 있다는 데 있습니다. 확보줄이 없으면 한 동작 한 동작이 두려워 결국 벽에 붙어 얼어버립니다. 테스트가 없는 코드베이스가 정확히 그렇습니다 — 리팩터링도, 대규모 재설계도, 의존성 업그레이드도 “건드리면 뭔가 깨질 텐데”라는 공포 때문에 멈춰 섭니다.

다만 확보줄도 종류가 있습니다. 발끝마다 거는 가볍고 단단한 퀵드로(작은 테스트)가 있고, 벽 꼭대기에 한 번 거는 무겁고 출렁이는 톱로프(큰 테스트)가 있습니다. 톱로프만 잔뜩 걸면 무겁고 느리고, 떨어졌을 때 어디서 미끄러졌는지 알기 어렵습니다. 그래서 등반의 기본은 가볍고 단단한 확보 지점을 촘촘히, 무거운 건 꼭 필요한 곳에만 거는 것입니다. 이 장이 말하는 테스트 피라미드가 바로 그 등반 전략입니다.

왜 중요한가?

테스트의 1차 효용은 ‘버그 잡기’로 알려져 있지만, 그건 여러 이유 중 하나일 뿐입니다. 개발 주기에서 버그를 늦게 발견할수록 고치는 비용은 기하급수적으로 커지므로 조기 발견은 분명 가치가 있습니다. 하지만 더 본질적인 효용은 소프트웨어가 변화할 수 있도록 지원하는 것입니다. 새 기능 추가, 리팩터링, 대규모 재설계 같은 변경에서 자동 테스트는 실수를 빠르게 잡아주므로 안심하고 코드를 바꿀 수 있게 합니다.

이게 없으면 어떻게 되는지를 구글은 직접 겪었습니다. 2005년 구글 웹 서버(GWS) 는 프로젝트 규모와 복잡성이 커지면서 릴리스마다 버그가 넘쳐났고, 한때는 프로덕션에 추가된 기능의 80% 이상이 사용자에게 영향을 주는 버그를 담고 있어 롤백해야 했습니다. 팀원들은 코드를 수정할 때마다 불안에 떨었습니다. 자동 테스트 도입 후 1년 만에 긴급 수정 배포 건수가 절반으로 줄었고, GWS는 정상 궤도로 복귀했습니다.

여기서 배운 핵심은 제품 결함 해결을 개별 프로그래머의 능력에만 의존해서는 안 된다는 사실입니다. 뛰어난 엔지니어 100명으로 구성된 팀도 매일 5개의 버그를 양산합니다. 게다가 엔지니어의 시야는 자기가 고친 버그와 주변 코드에 집중되므로, 버그를 고치다 다른 버그를 심곤 합니다. 자동 테스트는 개별 엔지니어의 집단 지성을 팀 전체의 공유 자원 풀로 환원합니다 — 한 명이 작성한 테스트를 팀 전원이 수행하고 결함을 찾아낼 수 있게 됩니다. 디버깅(버그가 터질 때마다 한 명이 분석)에 의존하는 방식과는 엔지니어링 비용 차이가 하늘과 땅입니다.

규모의 관점에서도 자동화는 선택이 아닙니다. 오늘날 소프트웨어는 수천~수백만 줄에 달하고 하루에도 여러 번 릴리스됩니다. 구글 검색의 모든 기능(항공편, 영화 시간표, 유사 이미지 등)을 지원하는 모든 언어·국가·기기 조합에서 사람이 수동으로 검증하는 건 확장이 불가능합니다. 그래서 테스트에서의 해법은 단 하나, ‘자동화’뿐입니다.

핵심 내용

자동 테스트의 기본 구조 — 작성하고, 수행하고, 조치하라

가장 순수한 형태의 자동 테스트는 세 가지 활동으로 이루어집니다.

  1. 테스트 작성 — 보통 테스트 대상 시스템의 일부를 호출하는 함수나 메서드 하나로 만듭니다. 환경을 설정하고, (이미 아는 데이터를 입력해) 시스템을 호출하고, 결과를 검증합니다.
  2. 테스트 수행 — 수시로, 끊임없이 반복합니다. 사람과 달리 기계는 지치거나 지루해하지 않으므로 하루에 수천 번도 돌릴 수 있습니다.
  3. 실패한 테스트에 대한 조치 — 가장 중요한 단계입니다. 사람은 무언가 잘못됐을 때만 개입합니다.

가장 단순한 테스트는 다음 요소로 정의됩니다 — 테스트하려는 단 하나의 행위(주로 메서드나 API), 특정한 입력, 관측 가능한 출력 혹은 동작, 통제된 조건(하나의 격리된 프로세스 등). 간단한 테스트가 수백에서 수천 개 모이면 테스트 스위트(test suite) 가 되어 제품이 전체적으로 의도한 설계대로 작동하는지(혹은 그렇지 못한지 — 후자가 더 중요합니다)를 이야기할 수 있게 됩니다.

// Calculator 클래스가 결과가 음수인 계산을 제대로 처리하는지 확인한다.
public void main(String[] args) {
    Calculator calculator = new Calculator();
    int expectedResult = -3;
    int actualResult = calculator.subtract(2, 5); // 2에서 5를 뺀다.
    assert(expectedResult == actualResult);
}

실전의 테스트 스위트는 프레임워크와 라이브러리를 쓰므로 이보다 정교하지만, 모든 자동 테스트의 핵심은 이 단순한 예시와 똑같습니다.

세 활동 중 조치가 결정적입니다. 실패하는 테스트가 해결되지 못하고 빠르게 쌓여간다면 테스트에 투자한 노력이 허사가 됩니다. 테스트가 실패하면 수 분 내로 해결하는 팀이라야 제품과 테스트를 더 신뢰하며 오류를 빠르게 퇴치할 수 있습니다.

나쁜 테스트 스위트는 테스트가 아예 없는 것만 못하다

테스트는 엔지니어에게 신뢰를 줄 때만 가치가 있습니다. 테스트가 생산성을 떨어뜨리고 고칠 게 계속 나오거나 결과를 믿을 수 없다면, 엔지니어들은 더 이상 테스트를 신뢰하지 않고 우회 방법을 찾으려 합니다. 그렇게 되면 테스트가 만들어내는 가치는 0이 아니라 음수입니다.

테스트 코드가 주는 혜택

테스트 문화를 경험해보지 못한 개발자는 테스트가 생산성을 높인다는 걸 받아들이기 어렵습니다. 처음에는 기능 구현 시간만큼, 혹은 그 이상을 테스트 작성에 써야 하니까요. 하지만 그 투자가 회수되는 경로는 여러 갈래입니다.

디버깅 감소

테스트를 거친 코드는 결함이 적고, 결함 대부분이 서브밋 전에 고쳐지므로 코드의 존속 기간 전체로 보면 결함이 줄어듭니다. 구글의 코드 조각은 수명이 다하는 날까지 수십 번 수정되며 다른 팀이나 자동 유지보수 시스템이 건드리기도 합니다. 테스트를 한 번 작성해두면 프로젝트가 살아 있는 내내 값비싼 결함을 예방하고, 의존하는 다른 코드가 변경되어 테스트가 실패하면 프로덕션에 릴리스되기 전에 정상 상태로 되돌릴 수 있습니다.

자신 있게 변경

좋은 테스트로 무장한 팀은 변경을 자신 있게 리뷰하고 수용합니다. 테스트가 주요 기능을 끊임없이 검증해주므로 리팩터링이 자연스럽게 권장됩니다. 행위가 달라지지 않는 변경, 즉 리팩터링은 (이상적으로는) 기존 테스트를 수정할 필요조차 없습니다.

더 나은 문서자료

소프트웨어 문서는 낡거나 극단적 상황을 누락하는 등 코드를 잘 대변하지 못하기로 악명이 높습니다. 한 번에 하나의 행위만 검증하는 명확한 테스트는 실행 가능한 문서와 같습니다. 코드가 특정 상황에서 어떻게 동작하는지 궁금하면 그 상황을 검증하는 테스트를 보면 됩니다. 요구사항이 바뀌어 새 코드가 기존 테스트를 통과하지 못하면 그 ‘문서(테스트)‘가 낡았음을 분명히 알 수 있습니다.

더 단순한 리뷰

구글에서는 최소 한 명의 다른 엔지니어가 리뷰한 코드만 서브밋할 수 있습니다. 정확성·극단 상황·오류 상황을 검사해주는 테스트가 준비되어 있으면, 리뷰어는 각 상황을 머릿속으로 그려보는 대신 해당 테스트가 통과하는지만 보면 되므로 검증 시간이 크게 줄어듭니다.

사려 깊은 설계

새 코드의 테스트를 작성하는 일은 실질적으로 그 코드의 API가 잘 설계되었는지를 시험하는 행위입니다. 테스트하기 어려운 코드는 너무 많은 역할을 짊어졌거나 의존성을 관리하기 어렵게 짜였을 가능성이 큽니다. 잘 설계된 코드는 자고로 모듈화가 잘 되어 있고 특정 역할에 집중되어 있습니다. 설계 문제를 조기에 바로잡으면 훗날 수정할 때 고생을 덜 합니다.

고품질의 릴리스를 빠르게

건실한 자동 테스트 스위트를 갖춘 팀은 새 버전을 릴리스하며 불안에 떨지 않습니다. 구글에서는 수천 명의 엔지니어가 참여하고 코드 변경도 매일 수천 번 일어나는 큰 프로젝트들이 매일같이 프로덕션으로 릴리스됩니다. 자동 테스트 없이는 상상할 수 없는 풍경입니다.

테스트 크기 — 자원 기준 분류

구글은 전통적 용어인 ‘단위 테스트’·‘통합 테스트’ 대신 크기(size)범위(scope) 라는 두 독립 축으로 테스트를 분류합니다. 이는 서로 연관되어 있지만 분명 다른 개념입니다.

  • 크기: 테스트 케이스 하나를 실행하는 데 필요한 자원(메모리, 프로세스, 시간)
  • 범위: 검증하려는 특정한 코드 경로(code path)

크기를 가늠하는 기준은 코드 줄 수가 아니라 어떻게 동작하고, 무엇을 하고, 얼마나 많은 자원을 소비하는지입니다. 구글은 크기 정의를 테스트 인프라에 직접 녹여, ‘작은 테스트’라고 태깅된 테스트가 네트워크 연결 같은 금지된 동작을 시도하면 실패로 처리합니다.

크기실행 단위허용금지
작은 테스트프로세스 하나 (언어에 따라 스레드 하나)CPU 연산만sleep, I/O, 블로킹 호출, 네트워크/디스크 접근, 별도 프로세스(DB 등) — 테스트 대역으로 대체
중간 크기 테스트기기 하나여러 프로세스·스레드, 로컬 호스트 네트워크 호출(블로킹 포함), 로컬 DB 인스턴스외부 시스템과의 통신, 원격 기기 호출(불허하여 보호막 일부 유지)
큰 테스트여러 대의 기기원격 클러스터 등 자원을 원하는 만큼(제약 없음 — 그만큼 느리고 비결정적)

크기를 명확히 정의하는 이유는 테스트 스위트에 바라는 품질이 속도와 결정성이기 때문입니다(테스트하는 범위와는 무관합니다). 범위와 상관없이 작은 테스트는 거의 항상 더 빠르고 더 결정적입니다. 한 프로세스에서 블로킹 호출 없이 실행되는 테스트는 CPU 최고 속도로 돌고, 실수로 느리게 혹은 비결정적으로 만들기가 오히려 어렵습니다. 이 제약들이 엔지니어가 자기 발등을 찍는 실수를 막아주는 보호막 역할을 합니다.

테스트가 커질수록 이 제약들이 완화되어 더 유연해지지만 비결정성 위험이 커집니다. 원격 호출은 대부분 시스템에서 속도를 떨어뜨리고 비결정성을 높이는 가장 독보적인 원흉이므로, 중간 크기 테스트를 작성하는 엔지니어는 작은 테스트를 작성할 때보다 훨씬 주의해야 합니다. 그래서 큰 테스트는 전체 시스템의 종단간(end-to-end) 테스트나 테스트 대역을 쓸 수 없는 레거시 컴포넌트처럼 가장 복잡하고 검증하기 어려운 시나리오에만 제한적으로 활용하고, 빌드나 릴리스 때만 수행되도록 하여 개발 워크플로에 영향을 주지 않습니다.

크기와 무관한 공통 특성 — 밀폐성

모든 테스트는 크기와 무관하게 밀폐(hermetic) 되어야 합니다. 셋업·실행·테어다운에 필요한 모든 정보를 스스로 담고 있어야 하며, 테스트 수행 순서 같은 외부 환경에 대해 아무것도 가정하지 않아야 합니다. 예를 들어 공유 데이터베이스에 의존해서는 안 됩니다. 또한 테스트는 확인하려는 행위를 수행하는 데 필요한 정보 ‘만’을 포함해야 하고, 조건문이나 순환문 같은 제어문은 쓰지 않는 게 좋습니다 — 복잡한 테스트일수록 버그가 숨어들 가능성이 커지고, 테스트 자체를 검사해주는 테스트는 따로 없으므로 정확성 검사는 사람이 직접 해야 하기 때문입니다.

사례 연구 — 불규칙한 테스트는 비싸다

테스트당 실패 확률이 0.1%밖에 안 되더라도 하루 1만 번 수행하면 매일 평균 10번씩 원인을 조사해야 합니다. 불규칙한 테스트(flaky test) 가 단 몇 개만 있어도 원인을 찾아 헤매느라 생산성이 급격히 떨어지고, 구글 규모에서는 이 문제 하나가 테스트 인프라 전체를 중단시킬 수도 있습니다.

더 큰 문제는 신뢰의 붕괴입니다. 불규칙한 실패를 몇 번 조사하다 보면 테스트 스위트에 대한 믿음이 깨지고, 엔지니어들은 실패해도 신경 쓰지 않게 됩니다. 경험상 불규칙한 실패가 1%에 이르면 테스트의 가치가 바래기 시작합니다. 구글은 불규칙 실패 비율이 0.15%를 오가는데도 매일 수천 개씩 실패합니다. 재수행(retry)으로 영향을 제한할 수는 있지만 이는 시기를 늦출 뿐, 클록 시간·스레드 스케줄링·네트워크 지연 같은 진짜 원인을 언젠가는 찾아 해결해야 합니다.

테스트 범위 — 검증하는 코드의 양

범위는 주어진 테스트가 얼마나 많은 코드를 검증하느냐를 말합니다. 크기와 마찬가지로 세 단계로 나뉘며, 전통적 용어와 대응됩니다.

범위전통 용어검증 대상
좁은 범위 테스트단위 테스트독립된 클래스나 메서드 등 코드베이스 중 작은 일부 로직
중간 범위 테스트통합 테스트적은 수의 컴포넌트 간 상호작용 (예: 서버와 DB)
넓은 범위 테스트기능·종단간·시스템 테스트시스템의 서로 다른 부분들 사이의 상호작용, 여럿을 조합해야 나타나는 예기치 못한 동작

여기서 중요한 함정 — 단위 테스트의 범위가 좁다는 건 ‘실행되는’ 코드가 아니라 ‘검증되는’ 코드의 양이 기준입니다. 하나의 클래스가 다른 여러 클래스를 의존·참조하는 건 흔하고, 대상 클래스를 테스트하다 보면 자연스럽게 의존 코드도 호출됩니다. 그래도 검증의 초점이 한 클래스에 있으면 좁은 범위입니다. 구글은 가짜 객체·모의 객체로 의존성을 끊기보다는 딱히 실행할 수 없는 상황이 아니라면 실제 의존성을 끊지 않는 편을 선호합니다.

크기와 범위는 보통 같이 움직이지만(좁은 범위 → 작은 테스트) 항상 그렇지는 않습니다. 데이터베이스·파일시스템 같은 프로세스 외부 의존성을 모두 테스트 대역으로 대체하면 파싱·요청 유효성 검사·비즈니스 로직을 모두 포함하는 서버의 넓은 범위 테스트도 ‘작은’ 크기가 될 수 있습니다. 반대로, 메서드 하나를 검사하는 좁은 범위라도 최신 웹 프레임워크처럼 HTML·자바스크립트를 묶어 배포하는 경우 날짜 선택기 같은 UI 요소 하나를 테스트하려면 브라우저까지 실행해야 하므로 중간 크기가 됩니다.

테스트 피라미드와 안티패턴

구글은 비즈니스 로직 대부분을 검증하는 좁은 범위 단위 테스트가 80%, 둘 이상의 구성요소 간 상호작용을 검증하는 중간 범위 통합 테스트가 15%, 전체 시스템을 검증하는 종단간 테스트가 5% 정도가 되도록 합니다(마이크 콘의 테스트 피라미드, 구글 버전).

graph TD
    subgraph 테스트_피라미드["권장: 테스트 피라미드"]
        E2E["종단간 5%"] --> INT["통합 15%"] --> UNIT["단위 80%"]
    end

단위 테스트가 기반을 넓게 차지하는 이유는 빠르고 안정적이며 범위를 극적으로 좁혀줘서 클래스나 함수의 동작을 적은 노력으로 식별하고 실패 시 원인을 빠르게 진단할 수 있기 때문입니다. 더 큰 테스트는 버그를 잡는 주된 수단이 아니라, 제품이 제 모습을 갖춰감에 따른 온전성 검사 역할을 합니다.

주의해야 할 안티패턴이 두 가지 있습니다.

안티패턴모양원인과 증상
아이스크림 콘종단간·수동 테스트가 가장 많고 단위 테스트가 가장 적음 (피라미드를 뒤집은 형태)프로토타입에서 급하게 프로덕션으로 이전하며 테스트 부채를 미처 해결하지 못한 프로젝트. 느리고 신뢰할 수 없으며 고치기 어려움
모래시계종단간·단위는 많지만 통합 테스트가 적음구성요소들이 강하게 커플링되어 각 인스턴스를 독립적으로 만들 수 없을 때. 통합으로 막았어야 할 문제를 비싼 종단간 테스트로 막고 있음

비욘세 규칙

무엇을 테스트해야 하느냐는 질문의 간단한 대답은 ‘깨뜨려보고 싶은 모든 것을 테스트하라’ 입니다. 시스템이 특정 행위(성능, 행위 정확성, 접근성, 보안, 심지어 ‘실패에 대처하는 방법’ 같은 덜 명확한 속성까지)를 올바로 수행하는지 확신하고 싶다면 그 행위를 검증하는 자동 테스트를 작성하는 것만이 유일한 선택입니다.

이 철학에 붙인 이름이 ‘비욘세 규칙’입니다. 짧게 풀면 ‘네가 좋아했다면 (CI) 테스트를 준비해뒀어야지’ 입니다(노래 가사의 패러디). 전체 코드베이스의 변경을 책임지는 인프라팀이 자주 활용합니다 — 인프라 수정으로 A팀 제품이 깨졌는데 A팀이 CI에 등록한 테스트를 모두 통과했다면, 그 문제를 수정하고 검증 테스트를 추가할 책임은 인프라팀이 아니라 전적으로 A팀에 있습니다.

의도적으로 실패 상황을 만드는 테스트

실패는 시스템이 고려해야 하는 가장 중요한 상황 중 하나입니다. 재난이 일어날 때까지 기다리는 대신 흔한 유형의 실패 상황을 시뮬레이션하는 자동 테스트를 작성하세요. 단위 테스트에서는 예외·에러를, 통합·종단간 테스트에서는 RPC 오류 주입이나 지연시간 증가를 시뮬레이션할 수 있습니다. 카오스 엔지니어링(chaos engineering) 기법을 쓰면 실제 프로덕션 네트워크에 영향을 주는 더 큰 중단 사태도 시뮬레이션할 수 있습니다.

코드 커버리지의 함정

코드 커버리지는 어느 테스트가 기능 코드의 어느 라인을 실행하는지 측정하는 수단입니다(100라인 중 90라인 실행 시 90%). 테스트 품질의 표준 지표로 간주되곤 하지만, 그러기엔 한계가 분명합니다.

  • 적은 수의 테스트로 많은 라인을 실행하면서도 의미 있는 동작은 거의 돌려보지 않을 수 있습니다.
  • 호출된 라인 수만 셀 뿐, 실행 결과로 어떤 일이 벌어졌는지는 고려하지 않습니다.
  • 무엇보다 커버리지 자체가 목표가 되기 쉽습니다. 많은 팀이 80% 같은 목표를 세우는데, 엔지니어들은 이를 최소치가 아닌 최대치로 취급해 달성한 순간 그 이상을 할 동기를 잃습니다.

더 나은 방안은 검사해야 할 행위에 집중하는 것입니다. 고객이 이용할 모든 기능이 제대로 동작하는가? 의존하는 외부 시스템에 파괴적 변경(breaking change)이 일어났을 때 바로 인지하고 대응할 수 있는가? 이런 질문이 테스트 스위트의 전체 모습을 더 잘 대변합니다. 코드 커버리지는 테스트되지 않은 코드가 어디인지는 알려줄 수 있지만, 시스템이 얼마나 제대로 테스트되었느냐를 판가름하는 지표로는 적합하지 않습니다.

Warning

큰 테스트는 커버리지 인플레이션을 일으키므로(많은 라인을 실행하지만 검증은 얕음), 커버리지는 작은 테스트에서만 측정하길 권합니다.

구글 규모의 테스트와 대규모 스위트의 함정

구글은 모든 코드를 모노리포(monorepo) 하나로 관리합니다 — 20억 라인이 넘는 코드가 한 곳에 담겨 있고, 매주 약 2천 5백만 라인이 변경됩니다(절반은 엔지니어가, 절반은 자동화 시스템이). 리포지터리 브랜치를 쓰는 팀이 거의 없어 모든 변경이 헤드(head)에 직접 커밋되고, 외부 모듈조차 빌드된 바이너리가 아니라 소스 코드로부터 새로 빌드합니다. 이 규모의 테스트를 지속적 통합(CI) 시스템으로 수행하며, 그 중심에는 테스트 자동화 플랫폼 TAP(Test Automated Platform) 이 있습니다.

코드베이스가 커지면 기존 코드 변경은 불가피한데, 이때 테스트가 엉망이면 변경이 오히려 어려워집니다. 특히 깨지기 쉬운 테스트(brittle test) — 예상 결과를 너무 세세하게 표현하거나, 광범위하고 복잡한 상용구가 덕지덕지한 테스트 — 는 관련 없는 코드가 변경되어도 실패합니다. 겨우 다섯 줄 고쳤는데 무관한 테스트 수십 개가 실패하는 경험은 리팩터링 의지를 꺾습니다. 깨지기 쉬운 테스트의 주범 중 하나가 모의 객체 오용입니다.

두 번째 함정은 느린 테스트입니다. 스위트가 커지면 수행 시간이 길어지고, 느려질수록 수행 빈도가 줄어 가치도 떨어집니다. 다른 시스템 응답을 기다리느라 5초 걸리던 통합 테스트가 몇 년 후 십여 개 서비스에 의존하며 5분이 되기도 하고, sleep()·setTimeout()을 남용해 느려지기도 합니다. 비결정적 행위의 결과를 기다릴 때는 고정 sleep 대신 폴링과 타임아웃을 결합해, 주어진 시간 안에 기대 상태로 변하지 않으면 실패시키는 전략이 낫습니다.

거대한 스위트를 관리하는 비결은 결국 테스트를 존중하는 문화입니다. 테스트를 견고하게 만든 엔지니어에게 보상하고, 느리거나 중요하지 않은 테스트는 리팩터링하며, 기본적으로 테스트도 제품 코드처럼 다룹니다. 더불어 린터·문서·표준 프레임워크 같은 테스트 인프라에 투자해 나쁜 테스트를 만드는 실수 자체를 줄여야 합니다.

구글의 테스트 문화는 어떻게 자리 잡았나

2005년까지 구글의 테스트는 체계가 없었고 호기심 충족에 가까운 업무였습니다. GWS 경험이 촉매가 되어 2005~2006년 테스트 혁명이 일어났는데, ‘테스팅 그룹릿(Testing Grouplet)‘이라 불린 자원봉사단이 세 가지 방식으로 자동 테스트를 전사에 뿌리내리게 했습니다.

활동핵심결과
오리엔테이션 수업모든 신규 입사자가 반드시 거치는 입사 교육에 자동 테스트 가치를 다루는 한 시간짜리 수업을 추가. 모든 아이디어를 ‘구글 표준 관행’인 것처럼 교육신규 엔지니어가 기존 엔지니어 수를 넘어서며 자연스럽게 문화 전파. 구글 역사상 가장 오래 지속되는 수업 중 하나
테스트 인증팀의 테스트 성숙도를 5개 레벨로 측정하고 한 단계 올라서기 위한 지침 제공. 사내 대시보드로 모든 팀의 레벨을 노출해 경쟁을 유도2015년 자동화된 방식(pH)으로 대체될 때까지 1,500개 이상 프로젝트의 테스트 문화 개선
화장실에서도 테스트(TotT)화장실 칸마다 테스트 팁을 다룬 한 페이지짜리 글을 붙임. 전 세계에 흩어진 엔지니어 모두에게 도달하는 묘수가장 오래 시행되고 영향력이 가장 컸던 활동. 수백 편 제작, 블로그로도 발전

테스트 인증 레벨1은 지속적 빌드 구축, 코드 커버리지 추적, 모든 테스트의 크기 구분, 불규칙한 테스트 식별, 빠른 테스트 스위트 마련 같은 기본 조건을 요구합니다. 레벨이 올라갈수록 ‘실패하는 테스트 없어야 릴리스 가능’, ‘비결정적 테스트 모두 제거’ 같은 조건이 추가되고, 레벨5는 모든 테스트 자동화·모든 커밋 전 빠른 스위트 수행·모든 행위 테스트를 요구합니다.

오늘날 테스트 인증을 대체한 것이 프로젝트 건실성(pH, Project Health) 입니다 — 테스트 커버리지·지연시간 등 건실성 지표 수십 가지를 지속 수집·종합해 1(최악)~5(최고)의 단일 값으로 내어줍니다. pH-1은 팀에서 해결해야 할 문제가 있다는 뜻입니다.

Quote

처음부터 테스트 작성을 강제하지 않은 이유는, 코드 개발 방식을 강제하는 것이 구글 문화에 심하게 반하기 때문입니다. 강제했다면 오히려 테스트가 뿌리내리는 시기를 늦췄을 것입니다. 엔지니어가 자신의 의지로 테스트를 작성한다는 것은 테스트가 이롭다는 생각을 완전히 받아들였다는 뜻이며, 아무도 강요하지 않더라도 계속될 가능성이 크다는 의미입니다.

자동 테스트의 한계

모든 종류의 테스트를 자동화할 수는 없습니다. 검색 결과의 품질처럼 사람의 판단이 필요한 영역(구글은 Search Quality Raters 연구를 수행), 음성·영상의 미묘한 차이처럼 인간 감각이 필요한 영역, 복잡한 보안 취약점을 찾는 것처럼 창의력이 필요한 영역이 그렇습니다.

이를 일반화한 용어가 탐색적 테스팅(exploratory testing) 입니다 — 검사 대상을 고장 내야 할 퍼즐로 취급하고, 의외의 데이터를 입력하거나 예상치 못한 절차로 망가뜨리려 시도하는 창의적 작업입니다. 핵심은 한계와의 분업입니다 — 사람이 새로운 결점을 발견하면 즉시 자동 테스트로 만들어 재발을 막고, 사람은 다시 지루한 반복에서 벗어나 인간이 가장 잘하는 영역에 집중합니다.

비교 / 트레이드오프

디버깅 vs 자동 테스트

GWS가 정상 궤도로 복귀할 수 있었던 근본 이유가 이 둘의 차이입니다. 버그 처리 방식이 개인의 사후 대응이냐, 팀의 사전 공유 자산이냐가 갈립니다.

관점디버깅 의존자동 테스트
발동 시점버그가 터진 후 (사후)변경 시마다 (사전)
주체버그를 만난 개별 엔지니어팀 전원이 공유
비용발생할 때마다 매번 분석한 번 작성, 프로젝트 수명 내내 재사용
누적 효과결함 목록이 계속 길어짐결함이 서브밋 전에 걸러짐
작은 테스트 vs 큰 테스트

구글은 가능하면 언제나 작은 테스트를 권하지만, 작은 테스트만으로는 서로 다른 팀이 만든 두 시스템의 연동 같은 구성요소 간 상호작용을 검증할 수 없습니다. 그래서 비율을 조절하는 트레이드오프가 존재합니다.

비율을 늘리면얻는 것잃는 것
단위(작은) 테스트 ↑스위트가 매우 빠르게 완료, 일반적 로직 버그를 많이 잡음구성요소 간 상호작용은 검증 못 함
통합(중간) 테스트 ↑구성요소 사이의 문제를 더 많이 잡음전체 수행 시간이 길어짐

정답 비율은 시스템 아키텍처와 조직 현실에 따라 다릅니다. 다양한 크기·범위의 테스트가 조화롭게 혼합되어 있다면 좋은 스위트입니다.

내 생각

  • ‘버그 잡기’보다 ‘변경 가능성’이 테스트의 본질이라는 프레임이 가장 쓸모 있습니다. 테스트 작성을 설득할 때 “버그 줄이려고”보다 “이 코드를 6개월 뒤 누군가 무섭지 않게 고치려고”가 훨씬 잘 먹힙니다.

  • 크기(자원)와 범위(검증 대상)를 분리한 게 핵심 통찰입니다. 흔히 ‘단위 = 빠름’으로 뭉뚱그리는데, DB만 인메모리로 바꿔도 넓은 범위 서버 테스트가 ‘작은’ 테스트가 됩니다. CI 속도 최적화의 실질적 레버가 여기 있습니다.

  • 비욘세 규칙은 플랫폼/인프라팀의 방어선입니다. “네 기능이 중요했으면 CI에 테스트를 넣었어야지”는 공통 라이브러리를 운영할 때 책임 경계를 긋는 실무 무기입니다. 테스트 없는 다운스트림은 깨져도 업스트림 책임이 아닙니다.

  • 커버리지 목표치를 KPI로 거는 순간 최대치가 됩니다. Goodhart의 법칙 그대로입니다. 라인 커버리지 대신 “깨뜨리고 싶은 행위”를 체크리스트로 관리하는 편이 낫습니다.

  • flaky 1%면 신뢰가 붕괴한다는 임계점이 인상적입니다. retry는 진통제일 뿐이고, sleep을 폴링+타임아웃으로 바꾸는 건 당장 적용 가능한 처방입니다.

관련 개념