테스트는 왜 쓰는가 — 진짜 목적
테스트를 공부하려고 자료를 펼치면 기법의 홍수에 빠진다. TDD, BDD, 테스트 더블, 피라미드, 트로피, 커버리지, 변이 테스트, 속성 기반 테스트, 계약 테스트… 서로 다른 이름들이 각자 옳다고 말한다. 하지만 이것들을 나란히 놓으면 하나의 질문으로 수렴한다: 이 테스트가 정말 나를 도와주는가?
테스트의 목적은 “버그를 잡는 것”이 아니다. 그건 부산물이다. 진짜 목적은 변경에 대한 자신감이다 — 코드를 바꾸고 배포 버튼을 누를 때 “안 깨졌다”를 두려움 없이 믿을 수 있는 상태. 이 렌즈로 보면 모든 기법이 두 부류로 갈린다: 자신감을 키우는 것(회귀를 잡고, 설계를 이끌고, 신뢰도를 측정), 그리고 자신감을 갉는 적을 없애는 것(취약함·비결정성 제거).
테스트 스위트는 “변경에 대한 자신감”을 파는 자산이다. 신뢰할 수 없는 테스트는 자산이 아니라 부채다. 모든 기법은 이 자신감을 높이거나, 그것을 갉는 적을 없애는 일이다.
그렇다면 “좋은 테스트”란 무엇인가? Vladimir Khorikov는 좋은 테스트를 떠받치는 네 기둥을 제시한다. 이 넷이 이 자료 전체의 채점 기준이다.
이후 8개 장은 이 자신감을 따라 흐른다. 테스트로 설계를 이끌고(TDD·BDD) → 무엇을 얼마나 테스트할지 층위를 잡고(피라미드·트로피) → 빠르고 안정된 테스트를 위해 격리하고(더블·테스트 가능성) → 스위트를 얼마나 신뢰할지 측정하고(커버리지·변이·속성) → 그 신뢰를 갉는 적을 제거한다(flaky·냄새).
테스트가 설계를 이끈다
테스트를 “다 만든 뒤 검증하는 절차”로 생각하면 절반만 본 것이다. 정전들의 공통된 통찰은 더 급진적이다: 테스트를 먼저 쓰면, 그것이 설계를 이끈다. 쓰기 어려운 테스트는 곧 나쁜 설계의 신호이기 때문이다.
Kent Beck · TDD By Example
TDD — Red · Green · Refactor
테스트 주도 개발(TDD)은 구현보다 먼저 테스트를 쓰고, 그 실패하는 테스트를 통과시키는 최소한의 코드를 쓴 뒤 정리하는 개발법이다. Kent Beck의 *Test-Driven Development: By Example*이 원전이며, 사이클은 세 박자로 돈다.
여기서 핵심은 두 가지다. 첫째, 테스트는 검증 도구이자 설계 도구다. 테스트가 쓰기 고통스럽다면 그 코드의 결합이 너무 강하다는 뜻이고, 이는 3장의 테스트 가능성으로 이어진다. 둘째, TDD엔 두 학파가 있다.
- classicist / Detroit 학파 — 실제 객체를 쓰고 결과 상태를 검증한다. 리팩터링에 강하다.
- mockist / London 학파 — 협력 객체를 mock으로 대체하고 상호작용을 검증하며, 바깥에서 안으로(outside-in) 설계를 유도한다.
Ian Cooper의 강연 “TDD, Where Did It All Go Wrong”은 이 논쟁을 한 문장으로 정리한다 — “구현 세부가 아니라 동작(behavior)을 테스트하라.” 테스트의 단위는 클래스나 메서드가 아니라 관찰 가능한 동작이어야 리팩터링에도 살아남는다.
건강한 반론도 정전의 일부다. DHH는 “Is TDD Dead?” 대담에서 test-induced design damage(테스트를 위해 설계가 뒤틀리는 현상)를 지적했고, James Coplien의 “Why Most Unit Testing Is Waste”는 mock 과용·의미 없는 유닛 테스트를 비판한다. TDD의 교훈은 “항상 테스트를 먼저”가 아니라 “테스트하기 쉽게 설계하고, 동작을 테스트하라”에 가깝다.
Dan North · Introducing BDD
BDD — 동작을 비즈니스 언어로
TDD를 하다 보면 “다음에 뭘 테스트하지?”, “test라는 단어가 주는 오해”에 부딪힌다. Dan North의 행위 주도 개발(BDD)은 이를 동작(behavior)이라는 말로 다시 세운다. 시스템이 “무엇을 해야 하는가”를 개발자·QA·비즈니스가 공유하는 언어(ubiquitous language)로 서술하는 것이다.
형식은 Given-When-Then: “주어진 상황에서 · 어떤 사건이 일어나면 · 이런 결과가 나온다.” 이 시나리오는 요구사항이자 곧 실행 가능한 테스트가 된다(Cucumber/Gherkin 등). 명세가 항상 최신 검증 상태를 반영하는 살아있는 문서(living documentation)가 되는 것이 핵심 이점이다.
Gojko Adzic · Bill Wake
예시로 명세하고, AAA로 구조화한다
BDD와 짝을 이루는 것이 Gojko Adzic의 예시 기반 명세(Specification by Example)다. 추상적 요구 대신 구체적 핵심 예시(key examples)를 협업으로 도출하고, 그 예시를 자동화된 명세로 만든다. “무엇을 만들지”에 대한 공유된 이해가 그대로 회귀 스위트가 된다.
테스트 한 개의 내부 구조에도 정전적 형태가 있다 — Arrange-Act-Assert(AAA). Bill Wake가 대중화한 이 패턴은 테스트 본문을 세 구간으로 나눠 의도를 한눈에 읽히게 한다. BDD의 Given-When-Then과 정확히 동형(isomorphic)이다.
무엇을·얼마나 테스트하나
테스트는 공짜가 아니다. 느린 테스트는 자주 못 돌리고, 취약한 테스트는 신뢰를 갉는다. 그래서 어느 층위에 얼마나 투자할지가 전략의 핵심이다. 정전들은 이를 모양으로 이야기한다.
Cohn · Fowler · Google
테스트 피라미드 — 아래로 많이, 위로 적게
테스트 피라미드는 Mike Cohn의 *Succeeding with Agile*에서 나와 Martin Fowler가 대중화한 은유다. 아래로 갈수록 많고 빠르고 싸게, 위로 갈수록 적게.
- 바닥 · 유닛 테스트 — 많고 빠르고 결정적. 로직의 세부를 촘촘히. (‘단위’를 하나만 격리하면 solitary, 실제 협력자와 함께 검증하면 sociable 유닛 테스트.)
- 중간 · 통합/서비스/계약 테스트 — 컴포넌트 경계와 연동을 검증.
- 꼭대기 · UI / E2E — 적게. 느리고 깨지기 쉽다.
이 모양의 논리는 단순하다. 상위 테스트는 느리고 불안정하며 실패해도 원인을 찾기 어렵다(Google의 “Just Say No to More End-to-End Tests”). 각 층은 중복이 아니라 역할 분담이다. 이를 뒤집은 형태 — UI·수동 테스트가 위로 뚱뚱한 — 가 악명 높은 안티패턴 “아이스크림 콘”이다.
Kent C. Dodds
테스팅 트로피 — 통합에 무게를
프론트엔드 맥락에서 Kent C. Dodds는 테스팅 트로피를 제안했다. 표어 “Write tests. Not too many. Mostly integration.”(Guillermo Rauch의 말을 Dodds가 대중화)를 걸고, 정적분석(타입·린트)을 최하층에 두고 렌더링·상호작용을 함께 검증하는 통합 테스트에 가장 큰 무게를 둔다. 근거는 이 한 문장에 있다.
“The more your tests resemble the way your software is used, the more confidence they give you.”
— Kent C. Dodds, The Testing Trophy
피라미드와 트로피는 대립이 아니다. 근본 취지(느리고 취약한 E2E를 최소화)는 같고, 강조점만 시스템 특성에 따라 이동한 것이다. 백엔드·도메인 로직이 두꺼우면 피라미드가, UI 통합 신뢰가 중요한 프론트엔드면 트로피가 더 맞는다. 모양은 목적이 아니라 맥락의 함수다.
Robinson & Fowler · Pact
중간층을 지키는 법 — 소비자 주도 계약 테스트
마이크로서비스에선 “서비스 A가 바뀌면 B가 깨지지 않을까?”가 늘 문제다. 이를 느리고 취약한 E2E로 확인하는 대신, 소비자 주도 계약 테스트(Consumer-Driven Contract Testing)는 소비자가 실제로 기대하는 상호작용을 계약으로 명시하고 제공자가 그 계약을 만족하는지 검증한다(Pact 등). 소비자는 제공자를 테스트 더블로 세워 계약을 만들고, 제공자는 그 계약을 재생해 확인한다. 피라미드 중간층을 튼튼하게 만드는 핵심 도구다.
격리의 기술 — 더블과 테스트 가능성
빠르고 결정적인 테스트를 쓰려면 대상을 그 협력자(데이터베이스·네트워크·시간)로부터 격리해야 한다. 이 장은 격리의 두 얼굴을 다룬다: 협력자를 대체하는 도구(테스트 더블)와, 애초에 대체가 쉽도록 만드는 설계(테스트 가능성).
Meszaros · Fowler · Searls
테스트 더블 — 다섯 종류의 대역
테스트 더블(Test Double)은 실제 협력 객체를 대신하는 대역의 총칭이다(영화의 스턴트 더블에서 온 말). Gerard Meszaros가 *xUnit Test Patterns*에서 다섯 종류로 표준화했다.
여기서 2장의 TDD 학파가 다시 등장한다(Fowler의 “Mocks Aren’t Stubs”). classicist는 결과 상태를 확인하며 stub·fake를 선호하고, mockist는 상호작용을 mock으로 확인한다. 무엇을 고르든 원칙은 하나다.
소유한 인터페이스만 mock하라(서드파티 라이브러리를 직접 mock 금지 — Justin Searls “Please Don’t Mock Me”). 구현 세부를 mock으로 고정하면 리팩터링만 해도 테스트가 깨진다(리팩터링 내성 붕괴). 그리고 mock이 자꾸 많아진다면, 그건 대개 설계가 강결합이라는 신호다 — 다음 절로 이어진다.
Feathers · Hevery
테스트 가능성 — 좋은 설계와 같은 방향
테스트하기 어렵다는 건 대개 테스트의 문제가 아니라 설계의 문제다. Michael Feathers의 표현대로 테스트 가능성과 좋은 설계는 같은 방향을 가리킨다(“The Deep Synergy Between Testability and Good Design”). Miško Hevery는 테스트를 어렵게 만드는 전형을 짚는다:
- 생성자에서 일하기 — new로 협력자를 직접 만들어 버리면 대체할 수 없다.
- 전역/정적 상태·싱글턴 남용 — 숨은 결합.
- 암묵적 부수효과(숨은 의존성) — 시간·랜덤·네트워크에 몰래 의존.
처방은 의존성 주입(DI), 부수효과를 경계로 밀기(함수형 코어 / 명령형 셸), 시간·랜덤·네트워크의 추상화다. 이는 아키텍처의 정보 은닉·의존성 규칙과 정확히 같은 방향이다. 레거시 코드라면 Michael Feathers의 이음새(seam)를 찾아 테스트를 주입한다(*Working Effectively with Legacy Code*). UI 층에서는 화면을 객체로 캡슐화하는 페이지 객체(Page Object)가 같은 역할을 한다 — 셀렉터 같은 세부를 감춰, UI가 바뀌어도 한 곳만 고치면 되게 한다(Fowler).
테스트를 어떻게 신뢰하나
테스트를 많이 썼다고 안심할 수 있을까? “테스트가 있다”와 “테스트를 믿을 수 있다”는 다르다. 이 장은 스위트의 진짜 실력을 재는 세 가지 렌즈다 — 커버리지(무엇을 실행했나), 변이(정말 결함을 잡나), 속성(사람이 놓친 입력까지 잡나).
Ammann & Offutt · Marick · Fowler
커버리지 — 목표가 아니라 도구
테스트 커버리지는 테스트가 코드나 입력 공간의 어느 부분을 실행했는지를 재는 척도다. Ammann & Offutt는 기준을 체계화한다: 구문(statement)·분기(branch)·경로(path), 논리(condition·MC/DC), 입력공간(동등분할·경계값·pairwise). 하지만 정전들의 공통 경고가 있다.
단언(assert) 없이 코드를 실행만 해도 커버리지는 오른다(Marick “How to Misuse Code Coverage”). 그래서 커버리지 목표치(예: 100%)를 강제하면 의미 없는 테스트만 양산된다(Fowler “Test Coverage”). 커버리지의 진짜 용도는 테스트가 비어 있는 위험 지점을 찾는 것이지, 품질을 증명하는 숫자가 아니다.
Jia & Harman
변이 테스트 — 테스트를 테스트하기
커버리지가 “실행했는가”만 본다면, 변이 테스트(Mutation Testing)는 “결함이 있으면 정말 실패하는가”를 본다. 코드에 인위적 결함(mutant)을 심고, 스위트가 그것을 잡아내는지로 테스트의 실제 품질을 측정한다.
Claessen & Hughes · QuickCheck
속성 기반 테스트 — 예시를 넘어서
사람이 쓰는 예시 테스트는 사람이 상상한 입력만 검사한다. 속성 기반 테스트(Property-Based Testing)는 구체적 예시 대신 항상 성립해야 하는 속성을 명시하고, 도구가 무작위 입력을 대량 생성해 반례를 찾는다. Claessen & Hughes의 QuickCheck 논문이 원전이다.
신뢰를 갉는 적 — Flaky와 테스트 냄새
아무리 잘 설계한 스위트도 신뢰를 잃으면 자산에서 부채로 바뀐다. 신뢰를 갉는 두 적이 있다: 비결정적으로 깜빡이는 flaky 테스트와, 유지보수를 좀먹는 테스트 냄새.
Luo 외 · Fowler · Google
Flaky Test — 빨간 불을 무시하게 만드는 병
Flaky 테스트는 코드 변경이 없는데도 같은 테스트가 통과·실패를 오가는 비결정적 테스트다. 가장 무서운 점은 2차 피해다 — 한 번 “가끔 빨개지는 테스트”가 생기면 팀은 빨간 불을 무시하기 시작하고, 그러면 진짜 회귀도 함께 묻힌다. 스위트 전체의 신뢰가 무너진다.
Meszaros · xUnit Test Patterns
테스트 냄새 — 테스트도 1급 코드다
테스트 코드도 유지보수 대상이다. Gerard Meszaros는 테스트의 신뢰·유지보수를 해치는 반복적 징후를 테스트 냄새로 카탈로그화했다. 대부분의 냄새는 결국 프로덕션 코드의 설계 문제(테스트 가능성)를 비추는 거울이다.
Fragile Test
구현 세부만 바꿔도 깨진다. 과한 mock·구현 결합 → 리팩터링 내성 상실.
Obscure Test
의도가 안 읽힌다. setup 과다·AAA 붕괴. 테스트가 문서 역할을 못 함.
Assertion Roulette
단언이 뭉쳐 어느 게 실패했는지 모호. 한 테스트=한 개념으로.
Slow Test
느려서 자주 못 돌린다 → 피드백 붕괴. 외부 의존을 격리.
Mystery Guest
테스트 밖의 파일·DB에 몰래 의존. 재현·이해가 어렵다.
Test Duplication
준비 코드 중복. 헬퍼·빌더로 의도만 남긴다.
하나의 지도로 묶기
16개 개념을 지나왔다. 처음의 목적으로 돌아가 전체를 접으면, 모든 것은 “변경에 대한 자신감을 파는 자산”이라는 중심 아래 다섯 갈래다.
① 테스트가 설계를 이끈다
먼저 쓰고, 동작으로 명세한다.
② 무엇을·얼마나
어느 층위에 얼마나 투자하나.
③ 격리의 기술
협력자를 끊어 빠르고 안정되게.
④ 신뢰를 측정
스위트의 진짜 실력을 잰다.
⑤ 신뢰를 갉는 적
자산을 부채로 만드는 것들.
이 지도의 화살표들은 서로를 가리킨다. TDD(①)로 먼저 쓰면 설계가 좋아지고(③ 테스트 가능성), 그래서 협력자를 더블(③)로 끊어 빠른 유닛 테스트(②)를 만든다. 그 스위트를 얼마나 믿을지는 커버리지·변이(④)로 재고, flaky·냄새(⑤)가 그 신뢰를 갉으면 즉시 고친다. 그리고 층위(②)를 잘못 뒤집으면(아이스크림 콘) 전체가 느려져 자신감이 무너진다. 모든 실은 “자신감”이라는 한 매듭으로 이어진다.
좋은 테스트란 동작을 명세해 설계를 이끌고, 층위를 맞게 배치하고, 격리로 빠르고 안정되게 만들며, 신뢰를 측정하고 그 신뢰를 지키는 것이다 — 그 모든 목적은 두려움 없는 변경, 즉 자신감이다.
더 깊이 — 먼저 읽어야 할 세 원전
이 자료는 지형을 훑는 지도였다. 50여 편의 정전 중 딱 세 개만 먼저 읽는다면 이 순서를 권한다 — 나머지 개념 대부분이 여기서 갈라져 나온다.
TDD의 원전. Red-Green-Refactor를 실제 예제로 몸에 새긴다. “테스트는 설계 도구”라는 이 분야의 출발점. — 1장의 뿌리.
좋은 테스트의 네 기둥, classicist vs mockist, “구현이 아니라 동작을 테스트하라”를 가장 명료하게 정리한 현대 표준서. — 0·1·3장의 뼈대.
층위 전략을 짧고 정확하게. 이어서 Dodds의 “Testing Trophy”와 Google의 “Just Say No to More E2E Tests”를 함께 읽으면 2장이 완성된다.
이 셋을 소화한 뒤엔 관심 갈래로 넓히면 된다: 더블·설계가 궁금하면 Meszaros xUnit Test Patterns와 Fowler “Mocks Aren’t Stubs”, 레거시라면 Feathers Working Effectively with Legacy Code로. 측정이 궁금하면 Ammann & Offutt Introduction to Software Testing과 Hughes QuickCheck로. 대규모 실전은 Software Engineering at Google의 테스트 장으로.
새 자료를 읽을 때마다 흩어진 메모로 남기지 말고 개념 단위로 누적하자. 각 원전이 이 지도의 어느 갈래에 속하는지 이미 보이니, 새로 읽은 사실을 그 개념 위에 덧붙이면 지식이 복리로 쌓인다.