PromleeBlog
sitemap
aboutMe

posting thumbnail
프론트엔드 테스트 유형 정리 - React로 시작하는 TDD 13편
Summary of Frontend Test Types - TDD with React Part 13

📅

들어가기 전에 🔗

지금까지 우리는 열심히 코드를 짜고 테스트를 돌렸습니다.
그런데 문득 이런 궁금증이 들지 않나요?

'내가 지금 짠 테스트는 무슨 테스트지? 단위 테스트인가? 통합 테스트인가?'

개발자들 사이에서도 이 경계는 가끔 모호할 때가 있습니다.
하지만 이
유형
(Type)을 구분하는 것은 중요합니다.
어떤 테스트는 빠르지만 꼼꼼하지 못하고, 어떤 테스트는 느리지만 확실하기 때문입니다.
오늘은 이
테스트의 3대장
을 비교하고, 우리는 어떤 전략을 취해야 하는지 알아보겠습니다.

1. 단위 테스트 (Unit Test) 🔗

가장 작은 단위인
함수
컴포넌트 하나
를 독립적으로 테스트하는 것입니다.
마치 자동차 부품 공장에서
나사 하나
가 규격에 맞는지 검사하는 것과 같습니다.

특징 🔗

실습: 순수 함수 테스트 🔗

비즈니스 로직을 담은 유틸리티 함수는 단위 테스트의 가장 좋은 대상입니다.
src/utils/calculator.ts를 만들고 테스트해 봅시다.
src/utils/calculator.ts
export const add = (a: number, b: number) => a + b;
export const formatPrice = (price: number) => `${price.toLocaleString()}원`;
src/utils/calculator.test.ts
import { describe, it, expect } from 'vitest';
import { add, formatPrice } from './calculator';
 
describe('계산기 유틸리티 단위 테스트', () => {
  it('두 숫자를 더한다', () => {
    const result = add(1, 2);
    console.log(`[Unit] add(1, 2) 결과: ${result}`);
    expect(result).toBe(3);
  });
 
  it('숫자를 금액 포맷으로 변환한다', () => {
    const result = formatPrice(10000);
    console.log(`[Unit] formatPrice(10000) 결과: ${result}`);
    expect(result).toBe('10,000원');
  });
});

실행 및 결과 분석 🔗

Terminal
npm test calculator
실행 결과 (Unit)
실행 결과 (Unit)
[Unit] add(1, 2) 결과: 3
[Unit] formatPrice(10000) 결과: 10,000원
 
 PASS  src/utils/calculator.test.ts
외부 의존성 없이 입력값에 대한 출력값만 확인하므로 매우 빠르고 명확합니다.

2. 통합 테스트 (Integration Test) 🔗

여러 개의 유닛(컴포넌트, 훅, 함수)이 함께 모여서 잘 동작하는지 테스트합니다.
우리가 지금까지
RTL
로 작성한 대부분의 컴포넌트 테스트가 여기에 속합니다.

특징 🔗

실습: 컴포넌트 통합 테스트 🔗

지난 시간에 만든 LoginForm은 입력창(Input), 버튼(Button), 상태(State), 검증 로직(Validation)이 모두 합쳐져 있습니다.
이것이 바로 통합 테스트입니다.
src/components/LoginForm.integration.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, test, expect } from 'vitest';
import LoginForm from './LoginForm';
 
describe('로그인 폼 통합 테스트', () => {
  test('사용자가 이메일을 입력하고 로그인 버튼을 누르는 전체 흐름', async () => {
    const user = userEvent.setup();
    const handleSubmit = vi.fn();
 
    render(<LoginForm onSubmit={handleSubmit} />);
 
    // [통합] 여러 요소(Input, Button)가 상호작용합니다.
    const emailInput = screen.getByLabelText('이메일');
    const passwordInput = screen.getByLabelText('비밀번호');
    const submitBtn = screen.getByRole('button', { name: '로그인' });
 
    console.log('[Integration] 사용자 입력 시뮬레이션');
    await user.type(emailInput, 'user@test.com');
    await user.type(passwordInput, '1234');
    
    console.log('[Integration] 버튼 클릭');
    await user.click(submitBtn);
 
    // 검증 로직과 상태 관리가 잘 통합되어 작동했는지 확인
    expect(handleSubmit).toHaveBeenCalledWith({
      email: 'user@test.com',
      password: '1234'
    });
  });
});

실행 및 결과 분석 🔗

Terminal
npm test integration
실행 결과 (Integration)
실행 결과 (Integration)
[Integration] 사용자 입력 시뮬레이션
[Integration] 버튼 클릭
 
 PASS  src/components/LoginForm.integration.test.tsx
단위 테스트와 달리, 컴포넌트 내부의 여러 요소가 유기적으로 연결되어 동작하는 것을 확인했습니다.

3. E2E 테스트 (End-to-End Test) 🔗

사용자가 실제 브라우저를 켜고 사이트에 접속해서부터 나갈 때까지의
전체 과정
을 테스트합니다.
대표적인 도구로는
Cypress
,
Playwright
가 있습니다.

특징 🔗

(이번 시리즈는 단위/통합 테스트 중심이므로 E2E 코드는 개념적으로만 설명합니다.)
추후에 번외편으로 다뤄보도록 하겠습니다.

결론: 테스팅 트로피 (Testing Trophy) 🔗

그렇다면 우리는 비율을 어떻게 가져가야 할까요?
RTL
의 창시자
Kent C. Dodds
테스팅 트로피
라는 개념을 제안했습니다.
  1. Static (정적 분석):
    TypeScript, ESLint (가장 밑바닥, 필수)
  2. Unit (단위):
    유틸리티 함수 등 (적당히)
  3. Integration (통합):
    컴포넌트 간 상호작용 (가장 많이!)
  4. E2E (종단 간):
    핵심 시나리오 (조금만)

요약 🔗


소스 코드는 GitHub - Promleeblog/react-tdd-setup 에서 확인할 수 있습니다.
Terminal
git clone https://github.com/PROMLEE/my-tdd-app.git
cd my-tdd-app
git checkout part13
이제 테스트의 종류와 목적을 명확히 알게 되었습니다.
우리가 지금까지 작성한 테스트들이 대부분
통합 테스트
였다는 사실도 알게 되었죠.

하지만 혼자 개발할 때와 팀으로 개발할 때의 TDD는 또 다릅니다.
다음 시간에는
14편. 실무에서의 React TDD 적용 시 고려사항
을 통해, 팀 프로젝트에서 TDD를 도입할 때 부딪히는 현실적인 문제와 해결책을 이야기해 보겠습니다.

참고 🔗