✍️ 필사 모드: 테스트의 현대 — 단위·통합·E2E·Playwright·Property-based·Mutation·Fuzz·Chaos·AI 생성 테스트 심층 가이드 (2025)
한국어2025년의 테스트는 왜 다시 뜨거운가
2010년대는 "유닛 테스트 다다익선" 시대였다. 커버리지 숫자가 팀 자랑이었고, @Test 어노테이션 아래 assertEquals(1+1, 2)가 쌓였다. 2020년대 초에 그 반성이 왔다 — "우리는 많이 썼는데 왜 프로덕션에서 터지나". 단위 테스트가 mock으로 가득해 실제 통합 문제를 잡지 못했다는 진단. Kent C. Dodds가 Testing Trophy를 제안했고(2018~), 업계는 통합 테스트 중심으로 무게추를 옮겼다. 거기에 Playwright의 압도적 완성도가 Cypress를 밀어내고 E2E의 표준으로 올라섰다.
동시에 테스트의 자동 생성이 현실이 되었다. Qodo(Codium)·Diffblue Cover·CodiumAI가 기존 코드에 테스트를 만들어준다. 성능·보안 영역에서는 Property-based Testing과 Fuzz Testing이 주류화했고, LLM 시스템 자체를 평가하는 Eval이 새로운 테스트 카테고리로 떴다. 인프라 회복력은 Chaos Engineering이 Netflix 이후 성숙했다.
이 글은 그 2025년 테스트 판의 지형을 해부한다.
이 글은 앞선 엔지니어링 글쓰기와 코드 리뷰 가이드의 자매편이다. 좋은 글 + 좋은 리뷰 + 좋은 테스트가 품질 문화의 삼각형이다.
1부. Testing Pyramid·Trophy·Diamond — 형태 전쟁
1.1 기존 피라미드 (Mike Cohn, 2009)
- 바닥: 많은 유닛 테스트
- 중간: 통합 테스트
- 꼭대기: 적은 E2E
문제: "유닛이면 빠르니까" 철학에서 mock이 남발되어 실제 계약 위반을 못 잡음.
1.2 Testing Trophy (Kent C. Dodds, 2018)
- 바닥: 정적 분석 (TypeScript, ESLint, Semgrep)
- 좀 더 큰 층: 유닛 테스트
- 가장 큰 층: 통합 테스트 ← 여기가 다름
- 꼭대기: E2E
프런트엔드에서 특히 설득력을 얻었고, React Testing Library 철학과 결합.
1.3 Testing Diamond (Google 등)
- 작은 유닛
- 큰 통합/서비스 테스트
- 작은 E2E
백엔드 마이크로서비스 세계에서 수렴한 형태.
1.4 어떤 모양이든 공통 원칙
- 실행 속도와 신뢰도의 트레이드오프를 의식적으로 설계
- "커버리지 90%"는 목표가 아니라 임계 구간의 커버리지가 진짜
- Mock은 필요악 — 신뢰가 아니라 격리를 위해서만
2부. 유닛 테스트 — 진짜 필요한 것과 잉여
2.1 언제 유닛 테스트가 빛나는가
- 순수 함수: 복잡한 도메인 로직, 파싱, 계산
- 경계값·에러 분기: 0, 빈 배열, 최대값
- 회귀 방지: 버그 고친 직후 테스트 남기기
2.2 유닛 테스트가 잉여가 되는 순간
- getter/setter에 대한 테스트
- 라이브러리 그 자체의 테스트 (axios 응답을 mock해서 axios 호환성 검증)
- Mock이 mock을 부르는 3-depth 테스트 (진짜 검증 대상이 소실)
2.3 테스트 이중화 전략 (Test Double)
- Dummy — 필드만 채움
- Stub — 고정 응답
- Mock — 호출 검증 + 응답
- Fake — 실제 동작 가능한 경량 구현 (인메모리 DB)
- Spy — 실제 호출 + 기록
2025년 합의: Fake를 선호, mock은 인터페이스 경계에서만.
2.4 도구
- JS: Vitest(>Jest), Node built-in
node:test - Java/Kotlin: JUnit 5, Kotest
- Python: pytest + hypothesis
- Go: 기본 testing + testify
- Rust: 기본
#[test]+insta+proptest - Swift: swift-testing (2024 WWDC 신규)
3부. 통합 테스트 — Testcontainers 혁명
3.1 전통적 고통
- 로컬 DB 띄워야 함
- 버전 불일치
- CI에서 "돌아간다"가 개발자 기계에서 안 돌아감
3.2 Testcontainers — 2020 이후 대세
- Docker 기반, 테스트마다 실제 Postgres/Redis/Kafka 띄움
- Java·JS·Python·Go·Rust·.NET 전부 지원
- 2024 Testcontainers Cloud로 CI 속도 문제 해결 (remote docker daemon)
import { PostgreSqlContainer } from "@testcontainers/postgresql";
const db = await new PostgreSqlContainer("postgres:16").start();
const conn = await connect(db.getConnectionUri());
// 실제 Postgres에서 통합 테스트
await db.stop();
3.3 Ephemeral Environment — PR마다 서비스 전체
- Preview deploy의 백엔드 버전
- Preevy, Okteto, Coder/Gitpod, Qovery, Argo Preview
- DB 복사 전략: Neon branch, Supabase branch, Postgres template
3.4 Service Virtualization
- 실제 서드파티 API 대신 녹화된 응답
- WireMock, MockServer
- Polly.js (2024 거의 쇠퇴), msw (Mock Service Worker, 프런트엔드 표준)
- Prism (Stoplight) — OpenAPI spec에서 mock 자동 생성
3.5 Snapshot 테스트의 함정
- 결과 덩어리를 저장·비교
- 쉽지만 변경 이유가 불명확 → "커밋 찍고 스냅샷 업데이트" 습관성
- Kent Beck이 경계 — "스냅샷이 바뀐 이유를 알 수 있을 때만"
4부. E2E — Playwright의 독주
4.1 Cypress의 흥망
- 2017~2022: 현대 E2E의 대표
- 한계: iframe·다중 탭·여러 origin 약함, 느림
- 2023~2024: Playwright에 시장 잠식
4.2 Playwright (Microsoft, 2020~)
- Chromium·Firefox·WebKit 전부
- auto-wait: 요소 준비 대기 자동
- Trace Viewer — 실패 시 전체 타임라인 시각화
- Test Generator (
codegen) - Component Testing (React/Vue/Svelte 컴포넌트 단위)
- 2024 Playwright MCP로 AI 에이전트가 직접 브라우저 조작
- VS Code extension으로 개발자 경험 최고
4.3 Cypress가 여전히 살아남는 영역
- 작은 팀, 단일 Chromium 앱, 기존 투자
- Component Testing Cypress 버전도 유지 중
4.4 WebdriverIO·TestCafe·Nightwatch
- 특정 팀에서 유지, 신규 선택으로는 드뭄
4.5 E2E 안티패턴
- Flaky test의 온상 → 전체 시스템 불신
- 500개 이상 E2E → 1시간 CI → 기여 문화 붕괴
- happy path 위주로 20~50개, 나머지는 통합 테스트에서
5부. Visual Regression — 픽셀 수준 회귀
5.1 왜 필요한가
- CSS 변경이 무심결에 레이아웃 깨뜨림
- 테마 시스템·다국어에서 UI 깨짐
5.2 도구
- Chromatic (Storybook) — 컴포넌트별 스냅샷 + 리뷰 UI 훌륭
- Percy (BrowserStack) — 전체 페이지 비교
- Lost Pixel — OSS, Storybook 호환, 2024 인기 급상승
- Applitools — AI 기반 스마트 비교, 엔터프라이즈
- Playwright snapshot — 내장 basic 기능
5.3 AI 기반 비교
- 픽셀 단순 diff는 anti-alias·폰트 렌더링 차이에 약함
- Applitools·Lost Pixel은 ML로 "시각적으로 동일한가" 판단
6부. Contract Testing — 마이크로서비스 간 안전망
6.1 문제
- Service A가 API 바꿈 → Service B 터짐
- E2E로는 감지 느림·비쌈
6.2 Consumer-Driven Contract
- Consumer가 "내가 이렇게 쓸게"를 기록
- Provider가 "이 계약 지킨다"를 CI에서 검증
- Pact (오픈소스, 2013~)
6.3 Pact Broker
- 계약 레지스트리
- 여러 버전 호환성 매트릭스
- PactFlow (상용 호스팅)
6.4 Schema-first 접근
- OpenAPI / AsyncAPI / gRPC proto를 계약 원천으로
- Prism, Dredd로 런타임 검증
- GraphQL은 스키마 자체가 계약
6.5 실전 도입
- 핵심 서비스 3~5개에만 Pact, 전체 도입은 과잉
- 이벤트 스트리밍은 Schema Registry + Compatibility Mode가 대안
7부. Property-based Testing — 수학이 버그를 찾는다
7.1 개념
- "이 함수는 모든 입력에 대해 다음을 만족" 같은 속성을 선언
- 프레임워크가 수백~수만 랜덤 입력 생성
- 실패 시 shrink로 최소 재현 케이스 축소
7.2 전설 — Jepsen
- Kyle Kingsbury가 DB들을 파괴 — CockroachDB, etcd, Elasticsearch
- Jepsen 보고서는 분산 시스템 신뢰의 기준
- 내부적으로 Clojure + property-based + 네트워크 분할
7.3 언어별 주력
- Haskell: QuickCheck (원조, 1999)
- Python: Hypothesis
- JS/TS: fast-check
- Rust: proptest, quickcheck
- Java: jqwik
- Erlang: PropEr, Quviq QuickCheck
7.4 예제
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_reverse_twice_is_identity(xs):
assert list(reversed(list(reversed(xs)))) == xs
"리스트를 뒤집어 두 번 뒤집으면 원래". 1000개 랜덤 리스트가 자동 생성돼 검증.
7.5 실전 팁
- 함수 경계값(0, negative, 큰 수, 빈 리스트)은 자동 생성의 승리
- Stateful property test (Hypothesis
RuleBasedStateMachine)로 클래스 invariant - 속성 발견이 어렵다면 metamorphic testing(입력을 변환해도 결과 관계 유지)
8부. Mutation Testing — 테스트의 테스트
8.1 개념
- 코드를 의도적으로 조작(
+→-) - 기존 테스트가 실패해야 "살아남지 못한" (killed) 것
- 살아남은(survived) mutation = 테스트가 못 잡는 영역
8.2 도구
- JS: Stryker
- Java: PIT (pitest)
- Python: mutmut, cosmic-ray
- Rust: cargo-mutants (2023~)
- Go: go-mutesting
8.3 왜 적게 쓰나
- 느림 (모든 mutation마다 테스트 재실행)
- CI 전체 돌리면 1~10배 시간
- 그래서 PR로 변경된 부분만 mutation testing이 현실적
8.4 진지하게 쓰는 곳
- 금융·보안·의료 — 신뢰성이 법적 요건
- 오픈소스 핵심 라이브러리 (Stryker 자체, lodash 등)
9부. Fuzz Testing — 랜덤이 발견하는 취약점
9.1 개념
- 랜덤·반구조화 입력을 쏟아 부어 crash / 무한 루프 / 메모리 오류를 유발
- 파서·디코더·프로토콜 구현에 특히 효과
9.2 도구
- AFL++ — C/C++ 표준, 커널·OpenSSL·libxml에서 수백 CVE 발굴
- libFuzzer — LLVM 통합
- honggfuzz, syzkaller (Linux 커널)
- Jazzer — Java/JVM
- cargo-fuzz, afl.rs — Rust
- Atheris — Python (Google)
9.3 OSS-Fuzz
Google이 오픈소스 라이브러리를 상시 fuzzing. 수천 CVE 발굴. 참여하면 무료로 CI급 자원 제공.
9.4 Coverage-guided Fuzzing
- 실행 경로를 추적해 "새 분기를 커버하는 입력"에 가중치
- AFL 이후 업계 표준
- SanitizerCoverage + AddressSanitizer 조합
9.5 Differential Fuzzing
- 두 구현(예: Rust TLS vs OpenSSL)이 같은 입력에 다른 결과 → 버그
- 호환성 검증에 강력
10부. Chaos Engineering — 실패를 연습하는 기술
10.1 Netflix가 시작한 패러다임
- 2011 Chaos Monkey — 무작위 인스턴스 종료
- 2013 Simian Army (Latency Monkey, Chaos Gorilla)
- 2014 Principles of Chaos 발표
10.2 실험 설계
- 가설: "한 AZ가 죽어도 사용자 영향 없다"
- 실험: 그 AZ 노드를 drain
- 측정: SLO 위반 여부
- 블라스트 반경을 controlled
10.3 도구
- Chaos Mesh (CNCF, 오픈소스) — Kubernetes 네이티브
- LitmusChaos (CNCF, 인도 MayaData) — Hub 구조
- Gremlin — 상용, UX 최고
- AWS Fault Injection Simulator (FIS)
- GameDay — 사람 참여 시나리오 훈련
10.4 Chaos가 성숙하는 조건
- 관측가능성이 먼저 — 실험 중 뭐가 깨지는지 못 보면 위험
- 단계: dev → staging → prod (시간 제한 + 롤백 준비)
- 심리적 안전 — 실험 실패를 학습 기회로
11부. AI 생성 테스트 — 2024~2025 폭발
11.1 도구
- Qodo (Codium) — PR에 테스트 제안, context-aware
- Diffblue Cover — Java 특화, 대형 레거시 커버리지 가속
- Ponicode (2023 Snyk 인수 후 흐름 약화)
- Cursor·Claude Code로 "이 함수 테스트 만들어줘"가 일상
11.2 현실
- AI는 happy path·boundary case 생성을 꽤 잘함
- AI가 만든 테스트는 Mutation testing + 커버리지로 검증 필수
- "AI가 만든 테스트가 AI 코드를 검증" 자기검증 순환 조심
11.3 생산성 영향
- 커버리지 50% → 75%를 2일에 달성한 팀 사례
- 하지만 "쓰레기 테스트"도 쉽게 추가됨 → 리뷰 기준 엄격히
11.4 LLM Eval — 새 카테고리
- Promptfoo, DeepEval, Ragas — LLM 응답 품질 자동 평가
- Ground truth + LLM-as-a-judge 조합
- CI에 통합: 프롬프트/모델 바뀌면 자동 회귀
12부. Flaky Test — 10년된 숙제
12.1 원인 분류
- Timing — setTimeout, race condition
- Order dependency — 테스트 A가 B에 영향
- External state — 공유 DB, 네트워크
- Non-determinism — 랜덤, 시간, 부동소수
12.2 탐지
- GitHub Actions의 Retry Failed Jobs만 쓰면 은폐됨
- Trunk Flaky Tests, BuildPulse, Datadog CI Visibility — 실패율 추적
- 3% 실패율 넘으면 자동 격리
12.3 해결 패턴
- 시간은 clock을 주입 가능하게 (
@testing-library/fake-timers,tokio::time::pause) - 외부 API는 msw/WireMock으로 격리
- 테스트 병렬 실행 가능하도록 DB 스키마 격리
- Kafka·Redis는 Testcontainers로 테스트마다 새 인스턴스
12.4 문화
- "빨간 CI는 즉시 대응" 규범
- Flaky를 방치하면 경고 피로 → 진짜 실패도 무시
13부. 성능·부하 테스트
13.1 도구
- k6 (Grafana) — JS DSL, 빠름, CI 친화
- Gatling — Scala DSL, 엔터프라이즈
- Locust — Python, 쉬움
- JMeter — 레거시, 기능 풍부하지만 UX 고역
- Vegeta — Go, 초경량
- wrk / wrk2 — 초고부하 벤치
13.2 시나리오
- Load: 정상 부하 시 SLO 유지
- Stress: 한계점 찾기
- Spike: 순간 급증
- Soak: 24시간 장시간
13.3 CI 통합
- PR마다 small load test → 회귀 조기 감지
- Baseline 비교, 10% 이상 저하 시 알람
- 환경 차이 의식 — Staging != Prod
14부. 실전 — 팀 규모별 테스트 전략
14.1 5명
- TypeScript + Vitest
- Playwright happy path 10개
- Testcontainers로 DB 통합 테스트
- CI: GitHub Actions, 5분 목표
14.2 50명
- 통합 테스트 중심 (Trophy)
- Pact 핵심 서비스 3개
- Stryker weekly mutation
- Chromatic visual regression
- Flaky 탐지 대시보드
14.3 500명+
- 전담 QA Platform 팀
- Chaos Engineering program
- OSS-Fuzz 스타일 상시 fuzz
- Property-based 도메인별 확산
- LLM eval 인프라
15부. 체크리스트 12 · 안티패턴 10
✅ 체크리스트 12
- Testing Trophy/Diamond 중 하나의 모양을 팀이 의식하는가?
- Testcontainers로 실제 DB를 쓰는 통합 테스트가 있는가?
- Playwright로 핵심 happy path E2E가 있는가?
- Pact 또는 Schema 기반 contract test가 있는가?
- Property-based test가 핵심 알고리즘에 1~5개 있는가?
- Mutation testing이 분기마다 또는 주기적으로 돌아가는가?
- Visual regression이 디자인 시스템에 붙어 있는가?
- Flaky test가 3% 임계로 자동 격리되는가?
- Fuzz testing이 파서/프로토콜 코드에 있는가?
- Chaos experiment가 분기 1회 이상 실행되는가?
- CI가 10분 이하에 끝나는가?
- LLM 사용 기능에 eval suite가 있는가?
⚠️ 안티패턴 10
- 커버리지 숫자만 쫓다가 mock이 mock을 부름
- Snapshot을 의미 없이 업데이트 ("그냥 바뀌었네")
- Flaky를
retry 3으로 덮음 - E2E 1,000개를 매 PR마다 돌림
- Contract test 없이 마이크로서비스 30개
- prod DB에 직접 접속하는 통합 테스트
- AI가 만든 테스트를 검증 없이 머지
- Chaos 실험을 prod에서 관측가능성 없이
- 성능 테스트를 릴리스 전날에만
- LLM 응답 회귀를 수동 확인 (eval 없음)
다음 글 예고 — "프로덕트 엔지니어링: 사양·실험·A/B·Feature Flag·RICE·OKR·엔지니어-PM 협업·고객 인터뷰" — 기술이 아니라 가치를 만드는 엔지니어
테스트까지 왔다면, 이제 **'무엇을 만들 것인가'**라는 가장 상위 질문이다. 다음 글은 기술이 아니라 제품을 만드는 엔지니어의 일.
- Product Engineer vs Software Engineer 개념의 분화
- JTBD (Jobs To Be Done) 프레임워크
- Shape Up (Basecamp) · Dual Track Agile
- Experimentation Platform — Statsig·LaunchDarkly·Optimizely·GrowthBook
- A/B Test 통계 — MDE, p-hacking, sequential testing
- Feature Flag 운영 패턴
- RICE·ICE·WSJF 우선순위 프레임워크
- OKR·KR·Input vs Output metrics
- 엔지니어-PM 협업 모델 — Spotify Squad, GitLab Manifest, Linear의 접근
- 고객 인터뷰를 엔지니어가 하는 법
- No-code 붐과 엔지니어의 가치 재정의
- Proof of Concept → Prototype → Production 파이프라인
기술이 아닌 가치가 엔지니어의 마지막 렌즈다. 다음 편에서 그 렌즈를 닦는다.
현재 단락 (1/245)
2010년대는 "유닛 테스트 다다익선" 시대였다. 커버리지 숫자가 팀 자랑이었고, `@Test` 어노테이션 아래 `assertEquals(1+1, 2)`가 쌓였다. 2020년대 초...