C.W.K.
Stream
Lesson 02 of 03 · published

피라미드 (그리고 안티 패턴들)

~18 min · foundations, pyramid, test-strategy

Level 0테스트 호기심
0 XP0/32 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"빠른 테스트는 아래, 느린 테스트는 위, 느린 건 아주 조금만."

이기는 모양

Mike Cohn 이 2009년에 testing pyramid 를 그렸는데, 그림이 다른 거보다 더 잘 늙었어. 바닥은 빠른 unit test 가 넓게 — 수백 개, 각각 밀리초 단위로 돌아가. 그 위에 더 얇은 integration 띠 — 수십 개. 꼭대기에 critical flow 를 커버하는 E2E 몇 개.

이 모양은 세 가지 직교 축을 따라가 — 속도, 비용, 정밀도. Unit 은 빠르고, 싸고, 정밀도가 낮아 (실제 브라우저를 시뮬레이션하지 않으니까). E2E 는 느리고, 비싸고, 정밀도가 높아 (그게 실제 브라우저야). Pyramid 가 말하는 건: 테스트 예산의 대부분을 비용 낮은 데에 쓰고, 정밀도가 중요한 데에 살짝 얹어.

안티 패턴들

아이스크림 콘: E2E 잔뜩, integration 조금, unit 거의 없음. "수동 QA 가 Selenium 스크립트 짠다" 로 시작한 프로젝트에서 흔해. 결과: 45분 CI 파이프라인이 여전히 flaky.

모래시계: unit 잔뜩, E2E 잔뜩, integration 거의 없음. 다이어그램으로는 괜찮아 보이는데 실전에서 무너져 — 사용자 대면 regression 의 대부분은 중간 층에 숨어 있거든 (unit 통과, E2E 통과, 근데 두 모듈 사이 API 계약이 깨졌어).

트로피 (Kent C. Dodds): integration 위주. 이건 틀린 게 아냐 — integration 이 가장 가치 높은 층인 프로젝트에 대한 의도적 trade-off 야. 근데 Kent 가 그렇게 말했다고 무작정 따르는 건 직전 lesson 의 종교 문제랑 같아.

모양은 규칙이 아냐 — 너의 프로젝트의 비용과 가치를 저울질한 결과야. Pure-data 라이브러리는 E2E 면이 거의 없으니까 pyramid 가 대부분 base 야. 결제 깨지면 돈 나가는 consumer SaaS 는 교과서 pyramid 보다 E2E 가 더 많아. 모양은 다이어그램이 아니라 통증에 맞춰.

뭐가 뭐에 매핑돼

전형적인 React + Next.js 프로젝트라면:

  • Unit (Vitest): pure function, reducer, util, custom hook (renderHook 으로), jsdom/happy-dom 에서 작게 렌더한 컴포넌트.
  • Integration (Vitest + MSW): mock 된 API 와 통신하는 컴포넌트, multi-component flow, 격리된 라우트 핸들러 테스트.
  • E2E (Playwright): 핵심 사용자 여정 — 가입, 로그인, 구매 완료. regression 이 실제로 비용으로 이어지는 한두 개 흐름.

테스트 예산

테스트를 짜고 유지하는 데 쓸 수 있는 시간은 유한해. Pyramid 는 그걸 쓰는 한 방식, trophy 는 다른 방식, '대충 유용해 보이는 거' 는 또 다른 방식. 나쁜 결과는 잘못된 모양을 고른 게 아니라, 골랐다는 걸 인지하지 못한 거야. 의도적으로 고르고, 왜 그랬는지 적어두고, suite 가 아프기 시작하면 (느린 CI, flake, 낮은 signal) 다시 평가해.

Code

프로젝트 레이아웃 — 각 층이 어디 사는지·text
src/
├── lib/
│   ├── format.ts
│   └── format.test.ts          ← unit (Vitest)
├── hooks/
│   ├── use-counter.ts
│   └── use-counter.test.ts     ← unit (Vitest + renderHook)
├── components/
│   ├── login-form.tsx
│   └── login-form.test.tsx     ← integration (Vitest + RTL + MSW)
├── app/
│   └── api/
│       └── auth/
│           ├── route.ts
│           └── route.test.ts   ← integration (Vitest)
└── ...

e2e/
├── auth.spec.ts                ← E2E (Playwright)
└── checkout.spec.ts            ← E2E (Playwright)

vitest.config.ts                ← unit + integration config
playwright.config.ts            ← E2E config
CI 에서 '아래 빠르고 위 느림' 이 실제로 어떻게 보이는지·text
# What "fast at the bottom, slow at the top" actually looks like in CI

Unit tests (Vitest)         :  200 tests    2.3s
Integration tests (Vitest)  :   30 tests    8.1s
E2E tests (Playwright)      :    8 tests   47.0s    ← 4x the time for 25x fewer tests

Total: ~1 minute. Bug in unit logic? Caught at 2.3s.
Bug in the checkout flow? Caught at 47s — still fast enough to run on every PR.

External links

Exercise

지금 일하는 프로젝트를 봐 (아니면 잘 아는 오픈소스 repo). 세 개 숫자 세어: unit 몇 개, integration 몇 개, E2E 몇 개. 그 다음 실제 모양을 그려봐. Pyramid 야, cone 이야, hourglass 야, 아니면 '테스트 전혀 없음' 이야? 아직 고치지 마 — 그냥 보기만 해. 보는 것 자체가 절반의 작업이야.
Hint
어떤 카테고리에 들어가는 테스트인지 모르겠으면, 그것도 정보야. '테스트 50개 있는데 어떤 모양인지 모르겠다' 가 가장 흔한 출발점이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.