장식적 스타일링. 디자이너가 padding 미세 조정할 때마다 테스트 업데이트 필요하면, 테스트가 동작 아니라 구현을 테스트하는 거.
지워지고 있는 코드. 죽어가는 거에 테스트 투자할 가치 없음.
'이거 테스트해야 해?' 는 '이거 문서화해야 해?' 와 같은 답 가짐 — 보통 no, 가끔 yes, 기준 비슷. 둘 다 future-you 에게 serve. 둘 다 지속 유지 비용. 둘 다 잘못된 이유로 쓰면 실패 ('docs 있어야 해' / '테스트 있어야 해' 가 둘 다의 사형 선고).
'E2E 추가할 때' 트리거
대부분 프로젝트는 첫날 E2E 필요 없어. 이 트리거 중 하나 발동할 때 E2E 테스트 추가:
실제 브라우저 테스트만 잡았을 버그가 프로덕션에 도달.
Critical 흐름 (auth, 결제) 가 refactor 됨 — 숨 안 참고 refactor 할 수 있게 E2E 먼저 짜.
여러 팀이 같은 surface 에 ship 하고 integration regression 이 주간으로 침.
제품이 유료 고객 가지고 regression 비용이 진짜 돈이 됨.
이 중 어느 것도 참 아니면 너의 테스트 예산은 unit + integration 에 더 잘 쓰여. 선제적으로 추가된 E2E 는 아무도 안 돌리는 유지 부채가 돼.
리소스로서의 테스트 예산
테스팅에 쓸 수 있는 주의력이 유한해. 예산 포함: 짜는 시간, 유지 시간, CI 분, 실패 읽을 때 정신적 부하. Suite 가 예산 터질 때까지 자라게 두는 대신 suite 를 예산에 맞게 사이즈 — 그게 시니어 엔지니어 움직임. Pyramid (또는 trophy, 뭐든) 는 예산 할당 사고 위한 TOOL. 처방이 아냐.
Code
실제로 쓸 결정 매트릭스·text
# A practical test-budget decision matrix
│ Low cost-of-failure │ High cost-of-failure
──────────────────────────────┼───────────────────────┼─────────────────────────
Low cost-of-test │ Worth it (cheap) │ DEFINITELY worth it
(unit, pure function) │ e.g. format helpers │ e.g. money math
──────────────────────────────┼───────────────────────┼─────────────────────────
Medium cost-of-test │ Skip unless │ Worth it
(integration w/ providers) │ the test is fun │ e.g. auth, payment
──────────────────────────────┼───────────────────────┼─────────────────────────
High cost-of-test │ Definitely skip │ Worth it for the
(E2E, flaky, slow) │ │ critical paths only
│ │ e.g. checkout, signup
싼 unit 테스트 — 순수 로직·typescript
// Unit-level test — cheap, fast, narrow blast radius (5 min to write)
import { describe, it, expect } from 'vitest';
import { calculateTax } from './tax';
describe('calculateTax', () => {
it('applies the standard rate', () => {
expect(calculateTax(100, 'US-CA')).toBeCloseTo(8.75);
});
it('exempts food in NY', () => {
expect(calculateTax(100, 'US-NY', { category: 'food' })).toBe(0);
});
});
Integration 테스트 — 컴포넌트 + mock 된 API·typescript
// Integration test — moderate cost, covers a flow (~15 min to write)
import { describe, it, expect } from 'vitest';
import { render, screen } from './test-utils';
import userEvent from '@testing-library/user-event';
import { CheckoutForm } from './checkout-form';
describe('<CheckoutForm />', () => {
it('submits with the right payload when the user fills it out', async () => {
const user = userEvent.setup();
render(<CheckoutForm />);
await user.type(screen.getByLabelText('Card number'), '4242424242424242');
await user.type(screen.getByLabelText('Expiry'), '12/30');
await user.click(screen.getByRole('button', { name: 'Pay' }));
expect(
await screen.findByRole('alert', { name: /payment processing/i })
).toBeInTheDocument();
});
});
E2E 테스트 — critical 여정, end-to-end·typescript
// E2E test — expensive, covers a critical journey end-to-end (~30 min stable)
import { test, expect } from '@playwright/test';
test('user can sign up, log in, and reach the dashboard', async ({ page }) => {
// Sign up
await page.goto('/signup');
await page.getByLabel('Email').fill(`pippa+${Date.now()}@example.com`);
await page.getByLabel('Password').fill('s3cret!');
await page.getByRole('button', { name: 'Create account' }).click();
// Land on dashboard automatically after signup
await expect(
page.getByRole('heading', { name: /welcome back/i })
).toBeVisible();
// Sign out and sign back in — confirms the round trip
await page.getByRole('button', { name: 'Sign out' }).click();
await page.getByLabel('Email').fill(`pippa+${Date.now()}@example.com`);
await page.getByLabel('Password').fill('s3cret!');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(
page.getByRole('heading', { name: /welcome back/i })
).toBeVisible();
});
현재 테스트 suite 봐 (작으면 멘탈로 리스트). 각 테스트에 물어: 이 테스트 지우면 cost-of-failure 가 뭐고, 유지 비용은 뭐야? 식별: (a) 명확히 유지할 값어치 있는 테스트 하나, (b) 경계선 테스트 하나, (c) 진짜 신호 없는 오버헤드라고 의심되는 테스트 하나. 아직 아무것도 지우지 마 — 그냥 예산 frame 으로 보기만.
Hint
'오버헤드' 테스트는 보통 '이게 어떤 버그 잡아?' 물으면 아무도 설명 못 하는 거. 답이 '잘 모름, 아마 React 가 렌더링 멈추는 거?' 면 — 그 테스트가 너의 코드 아니라 프레임워크 테스트하는 거. 은퇴 마크.
Progress
Progress is local-only — sign in to sync across devices.