유닛 테스트 vs E2E 테스트

contability·2025년 10월 15일

출처: https://medium.com/better-programming/the-test-pyramid-80d77535573

이번에 Appium을 사용 해보다가 Jest 테스트 코드와 비슷하게 생겼다는 걸 느낄 수 있었다.

예를 들어:

A 버튼을 누르면 B Field에 "TEST"라는 값이 입력되는가?

이런 시나리오는 Jest로도, Appium으로도 테스트할 수 있다. 그렇다면 둘의 차이는 무엇일까?

핵심 차이점

1. 테스트 환경의 차이

유닛/통합 테스트 (Jest + Testing Library)

  • JSDOM 또는 시뮬레이션 환경에서 실행
  • 실제 브라우저/디바이스 없이 JavaScript 환경에서 컴포넌트 로직 검증
  • 빠르고 가볍지만, 실제 렌더링 엔진을 사용하지 않음

E2E 테스트 (Appium, Playwright, Cypress 등)

  • 실제 브라우저/디바이스에서 실행
  • 진짜 렌더링 엔진, 네이티브 API, 실제 네트워크 환경 사용
  • 느리고 무겁지만, 실제 사용자 환경과 동일

2. 테스트 범위의 차이

유닛 테스트: 개별 함수/컴포넌트의 로직
통합 테스트: 여러 컴포넌트의 상호작용
E2E 테스트: 전체 시스템의 사용자 시나리오

구체적인 예시

Jest + Testing Library (통합 테스트)

it('A 버튼을 누르면 B Field에 "TEST"가 입력된다', () => {
  render(<MyComponent />);
  
  const button = screen.getByRole('button', { name: 'A' });
  fireEvent.click(button); // 시뮬레이션된 클릭
  
  const field = screen.getByRole('textbox', { name: 'B' });
  expect(field).toHaveValue('TEST'); // JSDOM에서 확인
});

이 테스트가 놓치는 것들:

  • 실제 터치 이벤트 처리
  • 네이티브 키보드 입력
  • 실제 접근성 기능 (VoiceOver, TalkBack)
  • 디바이스별 렌더링 차이
  • 실제 네트워크 레이턴시
  • 네이티브 모듈 연동 (카메라, GPS 등)

Appium (E2E 테스트)

it('A 버튼을 누르면 B Field에 "TEST"가 입력된다', async () => {
  const button = await driver.$('~button-a'); // 실제 앱에서 찾기
  await button.click(); // 실제 디바이스에서 터치
  
  const field = await driver.$('~field-b');
  const value = await field.getText(); // 실제 렌더링된 값
  expect(value).toBe('TEST');
});

이 테스트가 추가로 검증하는 것들:

  • 실제 디바이스의 렌더링 결과
  • 네이티브 이벤트 처리
  • 실제 성능과 응답성
  • OS 레벨의 접근성 기능

실전에서의 차이

Jest로는 발견 못하지만 E2E 테스트로 발견하는 버그들:

  1. iOS에서만 키보드가 텍스트 필드를 가리는 문제
  2. Android에서 특정 폰트가 깨지는 문제
  3. 실제 네트워크에서 타임아웃 발생
  4. 스크린 리더에서 버튼이 읽히지 않는 문제 (accessibilityLabel 누락)
  5. 메모리 누수로 앱이 느려지는 문제

E2E 테스트 도구들의 포지션

Appium, Playwright, Cypress, Selenium은 모두 E2E 테스트 도구다.

차이는 "어떤 환경을 테스트하느냐"만 다르다:

플랫폼 커버리지

Playwright
├─ 웹 브라우저 (Chromium, Firefox, WebKit)
└─ 데스크톱/모바일 웹

Cypress  
└─ 웹 브라우저 (Chrome, Firefox, Edge)

Selenium
└─ 웹 브라우저 (거의 모든 브라우저)

Appium
├─ 네이티브 모바일 앱 (iOS, Android)
├─ 하이브리드 앱
└─ 모바일 웹

공통점

  1. 실제 환경에서 실행

    • Playwright → 실제 브라우저
    • Appium → 실제 디바이스/에뮬레이터
  2. 사용자 시나리오 테스트

// Playwright
await page.click('button');
await page.fill('input', 'test');

// Appium
await driver.$('button').click();
await driver.$('input').setValue('test');
  1. 전체 스택 검증
    • 프론트엔드 + 백엔드 + 인프라
    • 실제 네트워크, 실제 데이터베이스

테스트 전략: 피라미드 구조

        /\
       /E2E\      ← 적은 수, 핵심 시나리오만
      /------\
     /통합 테스트\   ← 중간 수, 주요 기능 조합
    /----------\
   /  유닛 테스트  \  ← 많은 수, 모든 로직 커버
  /--------------\

각 레벨의 역할

유닛 테스트 (많이)

  • 개별 함수, 컴포넌트의 로직 검증
  • 빠른 피드백
  • 높은 코드 커버리지

통합 테스트 (적당히)

  • 여러 컴포넌트의 상호작용 검증
  • 주요 기능 조합 테스트
  • 유닛 테스트와 E2E 테스트 사이의 갭 메우기

E2E 테스트 (소수 정예)

  • 핵심 사용자 시나리오만
  • 실제 환경에서의 동작 보장
  • 배포 전 최종 검증

선택 기준

프로젝트 타입별

웹 프로젝트 → Playwright/Cypress
React Native/Flutter → Appium
웹 + 앱 → Playwright(웹) + Appium(앱)

프론트엔드 엔지니어 관점

  • : Playwright가 현재 가장 강력하다 (빠르고, API 좋고, 안정적)
  • : Appium이 사실상 표준이다

결론

Jest (유닛/통합): 코드 로직이 의도대로 작동하는가?
E2E 테스트: 실제 사용자 환경에서 제대로 작동하는가?

둘 다 필요하다. 유닛 테스트로 빠르게 로직을 검증하고, E2E 테스트로 실제 환경에서의 동작을 보장하는 것이 이상적인 전략이다.

코드상으로 문제가 없어도 실제 디바이스에서는 문제가 생길 수 있다. 그래서 두 종류의 테스트 모두 중요하다.

0개의 댓글