본문 바로가기
개발

[Tooling] 프론트엔드 테스트 전략: Jest와 Cypress로 결함 없는 코드 만들기

by 돌미나리는야생미나리 2026. 5. 2.

"이 코드를 고치면 다른 기능에 영향이 없을까?" 프론트엔드 프로젝트의 규모가 커질수록 개발자는 이 질문에 확답하기 어려워집니다. 수동으로 모든 버튼을 클릭해 보는 데는 한계가 있고, 배포 후 발견되는 버그는 대응 비용이 훨씬 큽니다.

이를 해결하는 유일한 방법은 테스트 자동화입니다. 하지만 무작정 모든 코드를 테스트하려고 하면 오히려 개발 속도가 느려질 수 있습니다. 오늘은 효율적인 테스트 피라미드 전략과 함께, 프론트엔드 생태계의 표준 도구인 JestCypress의 활용법을 알아보겠습니다.


1. 프론트엔드 테스트의 3단계 구조 (Testing Pyramid)

테스트는 범위와 비용에 따라 크게 세 단계로 나뉩니다.

1.1 유닛 테스트 (Unit Test) - Jest

가장 작은 단위인 함수나 컴포넌트 하나를 독립적으로 검증합니다.

  • 장점: 속도가 매우 빠르고 에러 위치를 정확히 알 수 있습니다.
  • 도구: Jest, Vitest

1.2 통합 테스트 (Integration Test) - React Testing Library

여러 컴포넌트가 함께 동작하거나, 컴포넌트와 API 상태(Mock)가 잘 맞물리는지 검증합니다. 사용자의 관점에서 "버튼을 눌렀을 때 텍스트가 변하는가?"를 확인하는 것이 핵심입니다.

1.3 E2E 테스트 (End-to-End Test) - Cypress

실제 브라우저 환경에서 사용자 시나리오를 처음부터 끝까지 수행합니다.

  • 장점: 실제 DB와 네트워크 통신을 포함하므로 가장 높은 신뢰도를 가집니다.
  • 단점: 속도가 느리고 인프라 비용이 발생합니다.
  • 도구: Cypress, Playwright

2. Jest와 RTL로 단단한 컴포넌트 만들기

리액트 생태계에서 가장 권장되는 방식은 JestReact Testing Library(RTL)의 조합입니다. RTL의 핵심 철학은 "구현 세부 사항이 아닌, 사용자의 행동을 테스트하라"는 것입니다.

2.1 실무 예시: 로그인 폼 테스트

컴포넌트 내부 상태(state)를 검사하는 것이 아니라, 입력창에 타이핑하고 버튼이 활성화되는지를 테스트합니다.

JavaScript
 
import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';

test('아이디와 비밀번호를 입력하면 로그인 버튼이 활성화된다', () => {
  render(<LoginForm />);
  
  const idInput = screen.getByLabelText(/아이디/i);
  const pwInput = screen.getByLabelText(/비밀번호/i);
  const submitButton = screen.getByRole('button', { name: /로그인/i });

  fireEvent.change(idInput, { target: { value: 'yumina' } });
  fireEvent.change(pwInput, { target: { value: 'password123' } });

  expect(submitButton).toBeEnabled();
});

3. Cypress로 사용자 시나리오 검증하기

유닛 테스트가 "부품의 이상 유무"를 본다면, Cypress는 "자동차가 실제로 잘 달리는지"를 봅니다. 브라우저를 직접 띄워 실제 클릭과 페이지 이동을 시뮬레이션합니다.

3.1 실무 활용: 구매 프로세스 테스트

장바구니 담기부터 결제 완료 페이지까지의 흐름을 한 번에 검증할 수 있습니다.

JavaScript
 
describe('구매 플로우', () => {
  it('상품을 장바구니에 담고 결제 페이지로 이동한다', () => {
    cy.visit('/products/1');
    cy.contains('장바구니 담기').click();
    cy.get('.cart-badge').should('contain', '1');
    
    cy.visit('/cart');
    cy.contains('주문하기').click();
    cy.url().should('include', '/checkout');
  });
});

4. TDD(Test Driven Development)는 꼭 해야 할까?

테스트 코드를 먼저 짜고 기능을 구현하는 TDD는 이상적이지만, 실무에서는 시간적 제약이 큽니다. 따라서 모든 곳에 TDD를 적용하기보다 비즈니스 핵심 로직에 집중하는 전략이 필요합니다.

  • TDD 권장: 할인율 계산 알고리즘, 복잡한 데이터 변환 함수, 유틸리티 함수.
  • 후행 테스트 권장: 복잡한 UI 레이아웃, 스타일 중심의 컴포넌트.

5. CI/CD와 테스트 자동화

테스트 코드가 빛을 발하는 순간은 자동화될 때입니다. GitHub Actions와 같은 도구를 활용하여, 모든 PR(Pull Request)마다 자동으로 테스트가 실행되게 하세요. 테스트를 통과하지 못한 코드는 병합(Merge)될 수 없도록 강제함으로써, 메인 브랜치의 품질을 항상 일정하게 유지할 수 있습니다.


6. 결론: 테스트는 '비용'이 아니라 '투자'입니다

초기에 테스트 코드를 작성하는 시간은 낭비처럼 느껴질 수 있습니다. 하지만 나중에 발생할 버그 수정 비용과 리팩토링 시의 불안감을 고려한다면, 테스트는 가장 수익률 높은 투자입니다.

오늘의 요약:

  1. 유닛 테스트(Jest)로 촘촘한 그물망을 만드세요.
  2. 통합 테스트(RTL)로 사용자 인터랙션을 검증하세요.
  3. E2E 테스트(Cypress)로 핵심 비즈니스 흐름을 보호하세요.
  4. 자동화(CI)를 통해 사람이 실수할 틈을 없애세요.