테스트 10분 읽기

프론트엔드 테스트 코드

프론트엔드 테스트 코드는 일반적으로 다음 네 가지로 구분할 수 있으며, 각각은 계층적으로 역할이 다르고, 목적도 명확히 구분됩니다.

프론트엔드 테스트 코드는 일반적으로 다음 네 가지로 구분할 수 있으며, 각각은 계층적으로 역할이 다르고, 목적도 명확히 구분됩니다.


✅ 프론트엔드 테스트 종류 요약

테스트 종류대상 범위속도목적주 사용 시점
유닛 테스트함수, 컴포넌트 단위빠름개별 기능의 정확성 검증함수 로직, 유틸, 훅 개발 시
통합 테스트모듈 간 상호작용보통여러 컴포넌트/함수 조합 검증커스텀 훅 + 컴포넌트 결합 시
UI 테스트DOM 렌더링 + 이벤트보통UI 요소 및 이벤트 반응 검증폼, 버튼 클릭, 리스트 렌더링 등
E2E 테스트전체 사용자 흐름느림실제 유저 플로우 검증기능 완성 후 릴리즈 전 단계

1️⃣ 유닛 테스트 (Unit Test)

📌 역할

  • 작고 독립적인 유닛(함수, 클래스, 컴포넌트)이 주어진 입력에 대해 올바른 출력을 내는지 테스트.
  • 예: calculateTotalPrice(price: number, quantity: number): number

✅ 장점

  • 빠르다 (ms 단위)
  • 디버깅이 쉽고 실패 지점이 명확하다
  • 로직에 대한 정확성 보장

❌ 단점

  • UI나 사용자 상호작용 테스트는 어렵다
  • 너무 작게 쪼개면 의미 없는 테스트가 생길 수 있음

💡 언제 사용?

  • 유틸 함수, 커스텀 훅, 도메인 로직, form validation

🧪 적용 예시 (Jest + Testing Library or React Hooks Testing Library)

describe('calculateTotalPrice', () => {
  it('should return correct total', () => {
    expect(calculateTotalPrice(1000, 2)).toBe(2000);
  });
});

2️⃣ 통합 테스트 (Integration Test)

📌 역할

  • 여러 유닛이 함께 작동하는지 검증
  • 예: 커스텀 훅 + 폼 컴포넌트 + API 요청

✅ 장점

  • 유닛 간 결합이 잘 작동하는지 확인 가능
  • 의존성 있는 코드의 동작 확인

❌ 단점

  • 테스트 복잡성 증가
  • 속도 느려질 수 있음
  • 실패 원인 파악이 어려울 수 있음

💡 언제 사용?

  • 컴포넌트와 훅을 함께 테스트할 때
  • API 요청과 UI 연동을 검증할 때

🧪 적용 예시 (Jest + React Testing Library)

test('form submits with correct value', async () => {
  render(<UserForm />);
  fireEvent.change(screen.getByLabelText(/name/i), {
    target: { value: 'Jinwoo' }
  });
  fireEvent.click(screen.getByRole('button'));
  await waitFor(() => expect(api.submitUser).toHaveBeenCalledWith({ name: 'Jinwoo' }));
});

3️⃣ UI 테스트 (Component Testing / DOM Testing)

📌 역할

  • UI 렌더링, 사용자 입력에 대한 반응, 버튼 클릭, 조건부 렌더링 등을 테스트

✅ 장점

  • 실제 브라우저 환경에 가까운 테스트
  • 사용자 경험을 코드로 보장 가능

❌ 단점

  • 로직이 없으면 테스트가 너무 단순해질 수 있음
  • 변경에 민감한 테스트가 많아질 수 있음 (fragile test)

💡 언제 사용?

  • 사용자의 동작(입력, 클릭)이 발생하는 컴포넌트
  • 조건부 렌더링 확인이 필요한 컴포넌트

🧪 적용 예시

test('button shows loading when clicked', () => {
  render(<SubmitButton />);
  fireEvent.click(screen.getByText('Submit'));
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
});

4️⃣ E2E 테스트 (End-to-End Test)

📌 역할

  • 실제 브라우저에서 사용자의 전체 플로우를 테스트
  • 예: 로그인 → 게시글 작성 → 게시글 목록 확인

✅ 장점

  • 진짜 유저 경험과 거의 동일
  • 통합 환경에서 문제를 조기에 발견 가능

❌ 단점

  • 느리다 (초 단위)
  • 환경 설정/유지 보수 복잡
  • CI 환경에서 flaky test가 자주 발생

💡 언제 사용?

  • 로그인 / 회원가입 / 결제 등 핵심 유저 플로우
  • QA, 배포 전 회귀 테스트

🧪 적용 예시 (Cypress)

describe('Login Flow', () => {
  it('logs in successfully', () => {
    cy.visit('/login');
    cy.get('input[name=email]').type('user@test.com');
    cy.get('input[name=password]').type('secure123');
    cy.get('button[type=submit]').click();
    cy.url().should('include', '/dashboard');
  });
});

🧠 정리

•	빠르고 안정적인 코드 품질 확보에는 유닛 + 통합 테스트가 핵심입니다.
•	사용자 경험 검증은 UI 테스트와 E2E 테스트로 커버합니다.
•	각 테스트는 중복이 아닌 협업 구조를 갖는 것이 좋습니다.

🌀 애자일 기반 테스트 구성 전략 개요

테스트 종류애자일에서의 역할적용 시점적용 주체자동화 여부
유닛 테스트기능 단위 정확성 확보, 회귀 오류 방지개발 중개발자✅ 자동화
통합 테스트모듈 간 연결 테스트, 변화에 강한 아키텍처 유도스프린트 중간개발자✅ 자동화
UI 테스트UI 동작 보장, 변경된 요구사항의 반영 확인스프린트 후반 또는 변경 시개발자 + QA✅ 자동화
E2E 테스트사용자 플로우 보장, 릴리즈 전 기능 통합 확인스프린트 종료 시점QA 또는 테스트 담당자✅ 자동화(밤마다 CI)

🪜 애자일 프로세스 단계별 테스트 적용 방식

1. 백로그 정리 & 요구사항 정의 (Sprint Planning)

  • Acceptance Criteria를 바탕으로 테스트 시나리오 초안 작성
  • 예: “사용자는 로그인 후 대시보드로 리다이렉트되어야 한다” → E2E 시나리오 정의
  • 유닛/통합 테스트 가능한 기능과 그렇지 않은 기능 구분

2. 개발 시작 (Sprint Execution)

✅ 유닛 테스트 우선 작성 (TDD 지향)

  • utils, form validation, 도메인 로직, 커스텀 훅 등
  • React Testing Library 또는 Jest로 작성
// 예시: 이메일 유효성 검사 함수
test('isValidEmail returns true for valid email', () => {
  expect(isValidEmail('user@example.com')).toBe(true);
});

✅ 통합 테스트 추가

  • useForm + UI 컴포넌트 연결, API 요청 등
  • 데이터 흐름, context 처리 검증

🛠 도구

  • Jest + React Testing Library
  • Mock Service Worker (MSW) 활용해 API mocking

3. UI 피드백 반영 & QA

  • UI 이벤트/렌더링 테스트 작성
  • aria-label, role, text 기반으로 사용자 경험 테스트
  • Storybook 기반으로 비주얼 테스트 병행 가능
test('Clicking "Send" triggers submission', () => {
  fireEvent.click(screen.getByRole('button', { name: 'Send' }));
  expect(mockSubmit).toHaveBeenCalled();
});

4. 스프린트 종료 전 (Pre-Release)

  • Cypress 또는 Playwright로 E2E 작성
  • CI에 연결하여 릴리즈 전 자동 실행
  • Critical Path만 우선 커버 → 점진적 확장
describe('End-to-End: Login Flow', () => {
  it('logs in and shows dashboard', () => {
    cy.visit('/login');
    cy.get('input[name=email]').type('user@demo.com');
    cy.get('input[name=password]').type('1234');
    cy.get('button').click();
    cy.url().should('include', '/dashboard');
  });
});

🔄 CI/CD 파이프라인과 연계

예: GitHub Actions 기준

name: Frontend CI

on: [pull_request, push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - run: pnpm install
      - run: pnpm test           # 유닛 + 통합
      - run: pnpm test:e2e       # E2E (선택적)

🎯 요약

전략설명
✅ TDD 시도가능하면 유닛 테스트 기반의 개발 주도
✅ 스프린트 내 테스트 작성QA의 의존도를 줄이고, 반복 가능한 안정성 확보
✅ CI 자동화PR마다 테스트 실행 → 회귀 방지
✅ 실패 이유 빠른 파악단위별 책임 분리 → 어느 테스트가 실패했는지 명확
🔁 테스트 리팩터링 주기적 실행요구사항 변경에 따른 테스트 갱신
⚠️ E2E는 핵심 플로우만 우선속도 및 유지보수 이슈 대비