테스트 전략 (Jest, RTL, Cypress)
2025. 4. 2. 15:29ㆍFrameworks/React
테스트 전략 (Jest, RTL, Cypress)
테스트가 왜 필요한가요?
테스트는 우리가 작성한 코드가 예상대로 작동하는지 자동으로 확인하는 방법입니다. 마치 숙제를 제출하기 전에 문제를 다시 풀어보는 것과 같습니다.
테스트의 장점
- 버그 예방: 문제를 빨리 발견하고 수정할 수 있습니다
- 리팩토링 자신감: 코드를 변경해도 기존 기능이 잘 작동하는지 확인할 수 있습니다
- 문서화: 테스트는 코드가 어떻게 작동해야 하는지 보여주는 살아있는 문서입니다
- 협업 용이: 다른 개발자들이 코드를 이해하고 수정하기 쉬워집니다
테스트 종류
- 단위 테스트 (Unit Test): 작은 기능 단위로 테스트합니다 (예: 함수, 컴포넌트)
- 통합 테스트 (Integration Test): 여러 기능이 함께 작동하는지 테스트합니다
- E2E 테스트 (End-to-End): 사용자 관점에서 전체 애플리케이션을 테스트합니다
Jest 소개
Jest는 Facebook에서 만든 JavaScript 테스트 프레임워크로, React 애플리케이션에서 가장 많이 사용됩니다.
Jest 설치하기
# create-react-app으로 만든 프로젝트에는 이미 포함되어 있습니다
npm install --save-dev jest
기본 Jest 테스트 예시
// sum.js
export function sum(a, b) {
return a + b;
}
// sum.test.js
import { sum } from './sum';
test('1 + 2 = 3', () => {
expect(sum(1, 2)).toBe(3);
});
Jest 테스트 실행하기
npm test
Jest 주요 매처(Matcher) 함수
// 동등 비교
expect(value).toBe(expectedValue); // 기본 타입 비교
expect(obj).toEqual(expectedObj); // 객체 비교
// 참/거짓 확인
expect(value).toBeTruthy();
expect(value).toBeFalsy();
// 배열/문자열 포함 여부
expect(array).toContain(item);
expect(string).toMatch(/regex/);
// 예외 발생 확인
expect(() => functionThatThrows()).toThrow();
Jest 모킹(Mocking) 기초
// 함수 모킹
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
// 반환값 설정
const mockFn = jest.fn().mockReturnValue('테스트');
console.log(mockFn()); // '테스트' 출력
// 모듈 모킹
jest.mock('./someModule', () => ({
someFunction: jest.fn().mockReturnValue('목 반환값')
}));
React Testing Library (RTL) 소개
RTL은 React 컴포넌트를 테스트하기 위한 라이브러리로, 사용자 관점에서 컴포넌트를 테스트합니다.
RTL 설치하기
# create-react-app으로 만든 프로젝트에는 이미 포함되어 있습니다
npm install --save-dev @testing-library/react
기본 RTL 테스트 예시
// Button.jsx
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('버튼 클릭 시 onClick 함수가 호출됨', () => {
// 클릭 이벤트를 추적할 모의(mock) 함수 생성
const handleClick = jest.fn();
// 버튼 컴포넌트 렌더링
render(<Button label="클릭하세요" onClick={handleClick} />);
// 화면에서 버튼 찾기
const button = screen.getByText('클릭하세요');
// 버튼 클릭하기
fireEvent.click(button);
// onClick 함수가 호출되었는지 확인
expect(handleClick).toHaveBeenCalledTimes(1);
});
컴포넌트 찾기 (Queries)
// 텍스트로 찾기
const element = screen.getByText('안녕하세요');
// 역할(role)로 찾기
const button = screen.getByRole('button', { name: '제출' });
// 레이블로 찾기
const input = screen.getByLabelText('이메일');
// 테스트 ID로 찾기
const element = screen.getByTestId('submit-button');
사용자 인터랙션 테스트
// 클릭 이벤트
fireEvent.click(button);
// 입력 이벤트
fireEvent.change(input, { target: { value: 'test@example.com' } });
// 키보드 이벤트
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
// 사용자 이벤트 (더 실제와 가까운 방식)
import userEvent from '@testing-library/user-event';
test('사용자 입력 테스트', async () => {
const user = userEvent.setup();
render(<MyForm />);
const nameInput = screen.getByLabelText('이름');
await user.type(nameInput, '홍길동');
const submitButton = screen.getByRole('button', { name: '제출' });
await user.click(submitButton);
// 결과 확인...
});
비동기 코드 테스트
test('데이터 로딩 후 표시', async () => {
render(<UserProfile userId="1" />);
// 로딩 상태 확인
expect(screen.getByText('로딩 중...')).toBeInTheDocument();
// 데이터가 로드될 때까지 대기
const userName = await screen.findByText('홍길동');
// 데이터 로드 후 확인
expect(userName).toBeInTheDocument();
});
Cypress 소개
Cypress는 E2E 테스트 도구로, 실제 브라우저에서 애플리케이션을 테스트합니다.
Cypress 설치하기
npm install --save-dev cypress
Cypress 실행하기
npx cypress open
기본 Cypress 테스트 예시
// cypress/integration/login.spec.js
describe('로그인 페이지', () => {
it('유효한 자격 증명으로 로그인 성공', () => {
// 로그인 페이지 방문
cy.visit('/login');
// 사용자 이름과 비밀번호 입력
cy.get('input[name="username"]').type('user123');
cy.get('input[name="password"]').type('pass123');
// 로그인 버튼 클릭
cy.get('button[type="submit"]').click();
// 로그인 성공 후 대시보드 페이지로 이동했는지 확인
cy.url().should('include', '/dashboard');
// 환영 메시지 확인
cy.contains('환영합니다, user123님!');
});
});
Cypress 주요 명령어
// 페이지 방문
cy.visit('/about');
// 요소 찾기
cy.get('.button').click();
cy.contains('제출').click();
// 입력
cy.get('input[name="email"]').type('test@example.com');
// 단언(Assertion)
cy.get('.message').should('be.visible');
cy.get('.count').should('have.text', '5');
cy.url().should('include', '/dashboard');
// 대기
cy.wait(1000); // 1초 대기
cy.get('.data').should('exist'); // 요소가 나타날 때까지 자동 대기
Cypress 고급 기능
// 네트워크 요청 가로채기
cy.intercept('GET', '/api/users', { fixture: 'users.json' });
// 쿠키 다루기
cy.getCookie('token');
cy.setCookie('token', 'abc123');
// 로컬 스토리지 다루기
cy.window().then((win) => {
win.localStorage.setItem('token', 'abc123');
});
// 스크린샷 찍기
cy.screenshot('login-page');
// 커스텀 명령어 만들기
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login');
cy.get('input[name="email"]').type(email);
cy.get('input[name="password"]').type(password);
cy.get('button[type="submit"]').click();
});
// 사용 예시
cy.login('test@example.com', 'password');
테스트 전략 수립하기
효과적인 테스트 전략을 수립하려면 다음과 같은 접근 방식을 고려해보세요:
테스트 피라미드 이해하기
- 단위 테스트 (80%): 많은 수의 빠른 테스트로 개별 함수와 컴포넌트 검증
- 통합 테스트 (15%): 중간 수준의 테스트로 컴포넌트 간 상호작용 검증
- E2E 테스트 (5%): 소수의 중요한 사용자 시나리오 검증
무엇을 테스트할지 정하기
- 핵심 비즈니스 로직: 중요한 계산, 데이터 변환, 유효성 검사 등
- 사용자 흐름: 로그인, 결제, 등록 등 중요한 사용자 경로
- 에지 케이스: 오류 상황, 경계값, 예외 처리 등
테스트 도구 선택 가이드
- Jest: 유틸리티 함수, 서비스, 커스텀 훅 등의 단위 테스트
- RTL: React 컴포넌트의 단위 및 통합 테스트
- Cypress: 중요한 사용자 흐름의 E2E 테스트
실용적인 테스트 작성 팁
- FIRST 원칙 따르기:
- Fast (빠르게): 테스트는 빨리 실행되어야 함
- Independent (독립적): 테스트끼리 의존성이 없어야 함
- Repeatable (반복 가능): 항상 같은 결과를 내야 함
- Self-validating (자체 검증): 테스트가 스스로 성공/실패를 판단해야 함
- Timely (적시에): 테스트는 실제 코드 작성과 함께 이루어져야 함
- 테스트 가독성 높이기:
- 명확한 테스트 이름 짓기 (given-when-then 형식 권장)
- 하나의 테스트는 하나의 개념만 검증하기
- 테스트 설정(setup)과 검증(assertion)을 명확히 구분하기
- 실용적인 테스트 커버리지 목표:
- 100% 커버리지는 현실적이지 않을 수 있음
- 중요한 코드와 복잡한 로직에 우선순위 두기
- 70-80% 정도의 커버리지를 목표로 하는 것이 일반적
실제 프로젝트에 테스트 도입하기
1. 점진적으로 시작하기
- 모든 코드를 한 번에 테스트하려 하지 마세요
- 새 기능부터 테스트 작성을 시작하고, 중요한 기존 코드에 점진적으로 테스트 추가하기
2. CI/CD 파이프라인에 통합하기
# GitHub Actions 예시
name: Run Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Run unit and integration tests
run: npm test
- name: Run E2E tests
run: npm run cypress:run
3. 테스트 문화 만들기
- 코드 리뷰에서 테스트 코드도 함께 리뷰하기
- 테스트 코드가 없는 PR은 머지하지 않는 규칙 만들기
- 테스트 작성 방법에 대한 팀 내 지식 공유하기
요약
- Jest, RTL, Cypress는 각각 다른 수준의 테스트에 적합한 도구입니다
- 테스트 피라미드에 따라 단위, 통합, E2E 테스트의 비율을 조정하세요
- 점진적으로 테스트를 도입하고, CI/CD 파이프라인에 통합하세요
- 테스트는 코드의 품질과 신뢰성을 높이는 투자입니다
'Frameworks > React' 카테고리의 다른 글
React Query/TanStack 심화 (0) | 2025.04.02 |
---|---|
React 성능 분석 실전 (0) | 2025.04.02 |
React + TypeScript 도입과 패턴 (0) | 2025.04.02 |
React 애플리케이션 성능 최적화 (0) | 2025.04.01 |
React 폼 관리와 유효성 검사 (0) | 2025.04.01 |