Files
2nd/10_Wiki/Topics/Coding/Quality_Test_Strategy.md
T
2026-05-10 22:08:15 +09:00

6.5 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
quality-test-strategy Test Strategy — pyramid / trophy / ice cream Coding draft B conceptual 2026-05-09 2026-05-09
quality
testing
vibe-coding
language applicable_to
any
Engineering
test pyramid
test trophy
ice cream cone
integration heavy
Kent Dodds

Test Strategy

"어떤 test 가 어느 비율?" Pyramid (옛), Trophy (modern), Ice cream cone (anti).

📖 핵심 개념

  • Unit: 빠름, 격리.
  • Integration: 의존 포함.
  • E2E: 사용자 flow.
  • 매 type 의 trade-off.

💻 코드 패턴

Test Pyramid (Mike Cohn)

       /\  E2E (작음)
      /  \ Integration
     /____\
    /      \
   /  Unit  \  (큰)
  /__________\

→ 70% unit, 20% integration, 10% E2E.

Test Trophy (Kent C. Dodds)

      ___
     /   \  E2E (작음)
    /_____\
   /       \  Integration (큰)
  /_________\
   \       /
    \ Unit/  (중간)
     \___/
   Static (lint, type) — 큰

→ Integration 가 큰. Static 도 비.

Ice cream cone (anti-pattern)

        /\  Manual (큰)
       /__\ E2E (큰)
      /____\
     /      \  Integration
    /________\
   /          \
  /  Unit (작음) \
 /______________\

→ Manual / E2E heavy. 느린, 깨짐.

매 type 의 cost

Unit:
- ms
- 매 commit
- 빠른 feedback
- Low confidence (mock heavy)

Integration:
- sec
- 매 PR
- Real DB / external
- Higher confidence

E2E:
- min
- 매 deploy
- Full UI / network
- Highest confidence + brittle

권장 비율

Static (lint + type): 무한.
Unit: 60-70% (간단 logic).
Integration: 20-30% (DB / API).
E2E: 5-10% (critical path).

Static

eslint src/
tsc --noEmit
prettier --check

→ "Test 의 99% 가 catch 가능".

Unit (격리)

test('add', () => {
  expect(add(2, 3)).toBe(5);
});

→ Mock everything. 빠름.

Integration (real dep)

test('user creation', async () => {
  const user = await api.createUser({ email: 'a@x' });
  const found = await db.users.findOne({ id: user.id });
  expect(found).toBeDefined();
});

→ Real DB (testcontainer). 느린.

E2E (Playwright)

test('login flow', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name=email]', 'a@x');
  await page.fill('[name=password]', 'pw');
  await page.click('button[type=submit]');
  await expect(page).toHaveURL('/dashboard');
});

→ Real browser. 매우 느린.

Mock 의 함정

// ❌ Mock 가 진실 안 반영
const mockDB = { findUser: jest.fn().mockResolvedValue({ id: 1 }) };
// → Real DB 가 다른 schema 면 silent break.

// ✅ Integration test (real DB).

→ Trophy 가 integration heavy 의 이유.

Test Doubles

Stub: 가짜 (return value).
Mock: 호출 검증.
Spy: 옛 호출 record.
Fake: 작은 진짜 구현 (in-memory DB).

→ Fake > Mock 가 일반.

Boundary test

- Validation: invalid input.
- Auth: unauthorized.
- Rate limit.
- Concurrency.
- Failure (network, DB).

→ Happy path 만 = 부족.

Coverage

80% line coverage = baseline.
100% = 가능 가 brittle.
60% + critical 100% = 합리.

→ Mutation testing 가 진짜 quality.

Testing_Stryker_Mutation.

Snapshot test

expect(component).toMatchSnapshot();

→ Visual regression. 주의: 매 변경 = 다른 snapshot. Update 자주.

Contract test

// Pact / Spectral
// API consumer + provider 의 schema.

→ Microservice 사이.

Testing_Pact_Contract_Deep.

Property test

import fc from 'fast-check';

test('add commutative', () => {
  fc.assert(fc.property(fc.integer(), fc.integer(), (a, b) => {
    expect(add(a, b)).toBe(add(b, a));
  }));
});

→ 100+ random input. Edge case 가 더.

Visual regression

// Chromatic / Percy
// 매 component 의 screenshot 비교.

→ UI 의 unintended change.

Test Data

Factory + faker.
Per-test reset.
Anonymized prod data.

Testing_Test_Data_Management.

CI 의 test

- run: npm run lint
- run: npm run typecheck
- run: npm test           # unit + integration
- run: npm run e2e         # Playwright

→ 매 PR 가 빠른 unit. E2E 가 매일 / 매 deploy.

Test 가 안 좋은 패턴

- 모든 거 E2E: 느린, 깨짐.
- 모든 거 mock: false confidence.
- 매 line 1 test: brittle.
- 변경 마다 snapshot update: 의미 X.
- "Coverage 100%" 강제: 가짜 test 추가.

Test pyramid 의 이유

- Unit 가 가장 빠름 + 정확.
- E2E 가 가장 느린 + 깨짐.

→ 대부분 = unit. 작은 = E2E.

Trophy 의 이유 (Kent)

- Mock 가 confidence ↓.
- Integration 가 진짜 동작.

→ Integration heavy.

React Testing Library

import { render, screen } from '@testing-library/react';

test('button click', () => {
  render(<Counter />);
  const btn = screen.getByRole('button');
  fireEvent.click(btn);
  expect(screen.getByText('1')).toBeInTheDocument();
});

→ User-centric (a11y query).

Test code 의 quality

- DRY (helper, factory).
- 명확 이름 ("user can login").
- Setup / teardown 격리.
- Fast (slow = skip in CI).
- Stable (flaky 0%).

Flaky test

간헐 fail = trust ↓.
- Race condition.
- Network.
- Time.

→ Find + fix. Don't `it.skip` permanently.

Test 의 ROI

high: critical path E2E, complex logic unit.
low: trivial getter, mock heavy.

→ "이 test 가 fail 시 무엇 catch?"

A/B feature test

새 feature 가 release 가 있어:
- Unit: feature flag 의 양쪽.
- Integration: 양쪽 path.
- E2E: 1 path 만 (cost).

🤔 의사결정 기준

상황 추천
일반 React Trophy
Backend API Pyramid 변형 (integration heavy)
Library Pyramid + property
Critical path E2E
단순 logic Unit + property
API contract Pact
UI regression Chromatic / Percy
Mutation Stryker

안티패턴

  • Ice cream cone: 느린, brittle.
  • Mock 모든 것: false confidence.
  • No static check: 가장 cheap 만.
  • Coverage 100% 강제: 가짜 test.
  • Flaky 무시: trust ↓.
  • E2E 만: 매 commit slow.
  • Test 만 + no E2E: production 가 처음 진실.

🤖 LLM 활용 힌트

  • Trophy 가 modern (integration heavy).
  • Static (lint + type) = 가장 cheap 의 catch.
  • Mutation 가 진짜 quality.
  • Critical path E2E 만.

🔗 관련 문서