본문으로 건너뛰기
yutils

testing pyramid 는 어떻게 동작할까?

unit / integration / E2E, 70/20/10 이 법칙 아닌 시작점인 이유, ice cream cone anti-pattern, testing trophy 의 modern 대안, 코드베이스에 맞는 형태 선택.

약 9분 읽기

Mike Cohn 의 2009 년 책에서 시작된 testing pyramid — unit 70 / integration 20 / E2E 10. 그 비율이 절대 법칙이 아니라 속도·비용·신뢰도의 trade-off 표현. 이 가이드는 pyramid 의 각 layer 가 무엇을 검증하고, modern 대안 (testing trophy) 이 등장한 이유를 정리한다.

Pyramid 의 의도 — 속도·비용 piramid

         /\
        / E\  ← E2E (느림 ms~s 단위, 비쌈, 깨지기 쉬움)
       / 2  \
      /  E   \
     /────────\
    / Integ.   \  ← Integration (DB·service 통합, 중간)
   /────────────\
  /              \  ← Unit (빠름 μs 단위, 싸고 정확)
 /     Unit       \
/──────────────────\

비율 70/20/10 = 다음에 답:
"빠르고 싼 test 를 많이, 느리고 비싼 test 를 적게"

→ feedback loop 빠름, CI 비용 적음, debug 쉬움 (unit fail 이 더 정확).

Layer 별 의미

Unit Test

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

특징:
- 한 함수·class 의 행동만
- 외부 의존 (DB, network, filesystem) 없음 (있으면 mock)
- ms 미만 (보통 μs)
- 1 file 변경 = 1-10 unit test 영향

장점: 빠른 feedback, 정확한 실패 위치, 안정적 (env 영향 X)
단점: 통합 동작 검증 안 됨, mock-heavy 시 "구현 결합"

Integration Test

def test_user_signup_saves_to_db():
    user = create_user("alice", "pw")  # 실제 DB
    found = db.users.find_by_name("alice")
    assert found.id == user.id

특징:
- 여러 module / external system 통합
- 실제 DB (또는 testcontainer), filesystem, in-memory broker
- 10ms-1s
- 통합 boundary 검증 (SQL query, ORM mapping, transaction)

장점: real 통합 검증, mock 안 됨
단점: 느림 (CI 시간 ↑), env 의존 (DB schema 등)

E2E Test

async def test_signup_flow():
    page = await browser.new_page()
    await page.goto("/signup")
    await page.fill('input[name="email"]', "a@b.com")
    await page.click('button[type="submit"]')
    await page.wait_for_selector(".welcome")
    assert "Welcome" in await page.text_content(".welcome")

특징:
- 사용자 관점 전체 흐름
- 진짜 브라우저 (Playwright, Cypress) + 진짜 backend + DB
- s 단위 (보통 5-30s 한 test)
- frontend + backend + 인프라 모두 검증

장점: 사용자 경험 직접 검증
단점: 매우 느림, 매우 flaky (timing / network), 깨지기 쉬움 (selector 변경)

Ice Cream Cone — 안티 패턴

        ____________________
       \   E2E (대부분)       /
        \______/¯¯¯¯¯¯\______/
              \Integ./
              \Unit /
               \___/

증상:
- E2E test 100+, unit test 거의 없음
- CI 1 시간+
- 1 변경 = 5 E2E fail (어디서 진짜 깨졌는지 불명)
- "다 retry 하면 되겠지"

원인:
- "사용자 경험만 검증하면 되잖아" — 부분적 진실, 그러나 cost 폭발
- legacy / mock 어려운 codebase
- code 변경 빈도 < test 작성 빈도

방어: 새 feature 마다 unit 부터, E2E 는 critical path 만 (signup,
       checkout 등 핵심 user journey).

Testing Trophy — modern 대안 (Kent C. Dodds)

     ___
    |   |  ← E2E (소수, critical path)
   _|___|_
  |       |  ← Integration (가장 많이) ← 핵심
   \_____/
   |     |  ← Unit (논리·계산 중심)
    \___/
      |    ← Static (TypeScript, ESLint) ← base
     ___

vs Pyramid 의 차이:
- Integration 이 가장 굵음 — frontend 의 경우 component 의
  통합 동작 (state + render + interaction) 이 가장 가치 큼
- Static (typecheck, lint) 을 base 로 — modern toolchain 에서 무료에
  가까운 검증

배경:
- Pyramid 는 backend 중심 (2009). frontend 에서는 unit 너무 잘게
  쪼개면 구현 결합 ↑.
- React Testing Library, Vitest, Playwright Component Test —
  integration-style 테스트가 만들기 쉬워짐.
- "user 관점에서 test 쓰면 자연스럽게 integration shape"

어느 shape 이 옳은가

코드 type권장 shape이유
Pure algorithm (parser, math)Unit-heavy (정상 pyramid)외부 의존 적음, edge case 많음
Backend API servicePyramid + integration 강화DB · 외부 service 통합이 핵심
Frontend SPATrophy (integration-heavy)component 통합 + user interaction
Microservice (consumer-provider)Contract test 추가service 간 boundary
Data pipelineUnit + 작은 integration sample큰 데이터 full E2E 어려움

흔한 함정

  • 비율에 집착 — 70/20/10 은 가이드라인 아님, shape 의 idea. coverage 30% 라도 critical path 잘 cover 면 OK.
  • 모든 함수에 unit test — getter / DTO mapping 같은 trivial 코드는 typecheck 만으로 충분.
  • mock-heavy unit test — 100% coverage 인데 실제 통합 안 됨 → integration test 적정량 필수.
  • E2E test 만 추가 — feedback loop 30 분, fail 시 debug 1 시간. 비싼 시그널.
  • flaky test retry — flaky 의 진짜 원인 안 잡으면 CI 신뢰 ↓ (flaky tests 가이드 참조).

마무리

Pyramid · Trophy 는 ratio rule 아닌 cost vs value 의 시각화. 코드베이스 type 에 맞게 shape 선택. 모든 조직이 같은 비율 X.

실용 시작 — typecheck + lint (free) base → unit (pure logic) + integration (boundary) 적정량 → E2E 는 critical user journey 만 (signup, checkout). flaky 한 E2E 는 즉시 fix 또는 quarantine.

가이드 목록으로