Skip to content
Published on

테스트의 현대 — 단위·통합·E2E·Playwright·Property-based·Mutation·Fuzz·Chaos·AI 생성 테스트 심층 가이드 (2025)

Authors

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 TestingFuzz 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

  1. Testing Trophy/Diamond 중 하나의 모양을 팀이 의식하는가?
  2. Testcontainers로 실제 DB를 쓰는 통합 테스트가 있는가?
  3. Playwright로 핵심 happy path E2E가 있는가?
  4. Pact 또는 Schema 기반 contract test가 있는가?
  5. Property-based test가 핵심 알고리즘에 1~5개 있는가?
  6. Mutation testing이 분기마다 또는 주기적으로 돌아가는가?
  7. Visual regression이 디자인 시스템에 붙어 있는가?
  8. Flaky test가 3% 임계로 자동 격리되는가?
  9. Fuzz testing이 파서/프로토콜 코드에 있는가?
  10. Chaos experiment가 분기 1회 이상 실행되는가?
  11. CI가 10분 이하에 끝나는가?
  12. LLM 사용 기능에 eval suite가 있는가?

⚠️ 안티패턴 10

  1. 커버리지 숫자만 쫓다가 mock이 mock을 부름
  2. Snapshot을 의미 없이 업데이트 ("그냥 바뀌었네")
  3. Flaky를 retry 3 으로 덮음
  4. E2E 1,000개를 매 PR마다 돌림
  5. Contract test 없이 마이크로서비스 30개
  6. prod DB에 직접 접속하는 통합 테스트
  7. AI가 만든 테스트를 검증 없이 머지
  8. Chaos 실험을 prod에서 관측가능성 없이
  9. 성능 테스트를 릴리스 전날에만
  10. 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 파이프라인

기술이 아닌 가치가 엔지니어의 마지막 렌즈다. 다음 편에서 그 렌즈를 닦는다.