Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

co-cherry

테스트 전략 본문

React

테스트 전략

co-cherry 2026. 5. 11. 20:20

"You should write tests if you value your time. 
Much better to catch a bug locally from the tests 
than getting a call at 2:00 in the morning and fix it then."

— Kent C. Dodds

 

버그는 발견이 늦을수록 비용이 기하급수적으로 증가한다.

설계 단계 대비 프로덕션에서는 최대 100배, CrowdStrike처럼 한 줄이 80억 달러로 이어질 수도 있다. 

 

테스트는 "만일의 보험"이 아니라 개발 속도를 높이는 투자다.

 

Viest + React Testing Library + MSW로 프론트엔드 테스트 전략을 완성하는 법을 알아보자. 

 

테스트가 왜 필요할까?

테스트가 없는 코드는 단순히 "버그가 있을 수 있는 코드"가 아니라 변경할 수 없는 코드다. 

기도하는 배포

우아한 형제들 기술 블로그에 나온 표현이다.

테스트 코드 없이 레거시 코드를 고치면 다음과 같은 과정을 거친다. 

  1. 소스 분석하고
  2. 변경하고
  3. 수동으로 기능 확인하고
  4. 배포하고
  5. 기도한다 🙏

왜? 내가 건드린 게 어디를 망가뜨렸을지 모르니까...

상황 테스트가 없을 때 테스트가 있을 때
새 기능 추가 "이거 건드리면 뭐가 깨질까?" 전전긍긍 CI가 깨지면 바로 알림
리팩토링 무서워서 못 함 → 기술부채 누적 자유롭게 구조 개선
라이브러리 업데이트 수동 회귀 테스트 → 결국 안 함 npm update, npm test 면 끝 
신규 팀원 온보딩 코드 동작을 추측만 가능 테스트 = 실행 가능한 문서

 

단위 테스트 VS 통합 테스트 

테스트란?

프로그램이 요구사항에 맞춰 제대로 동작하는지 검증하는 행위

테스트는 범위에 따라 단위 테스트 → 통합 테스트 → E2E 테스트로 구분한다. 

단일 테스트(Unit Test)

소프트웨어의 가장 작은 단위(함수, 메소드, 컴포넌트)가 의도대로 작동하는지 검증

개별 모듈의 기능 요구사항을 만족하는지 확인한다. 

  • 작은 부분만 테스트하므로 빠르게 실행
  • 개발 초기 단계에서 버그 발견 및 수정에 효과적
  • 레이어 단위 또는 특정 클래스 단위로 테스트 

보통 프론트엔드에서는 Jest, React Testing Library 를, 백엔드에서는 JUnit을 사용한다. 

통합 테스트(Integration Test)

여러 단위/컴포넌트 간의 상호작용을 테스트

개별 단위가 시스템 전체에서도 올바르게 동작하는지 확인한다.

  • 외부 라이브러리, 데이터베이스 등 개발자가 변경할 수 없는 부분까지 포함
  • 독립된 2개 이상의 모듈이 함께 동작할 때 테스트
  • 모듈 간 연결에서 발생하는 에러 검증
  • 단위 테스트보다 실행 시간이 길고 복잡함 

효과적인 테스트 작성 방법 

AAA 패턴 (Arrange-Act-Assert)

모든 테스트를 준비, 실행, 검증 세 부분으로 나누는 구조

단순하고 균일한 구조로 일관성을 확보할 수 있다. 

  • 준비(Arrange) 테스트 대상의 초기 상태 설정
  • 실행(Act) 사용자 상호작용 또는 함수 호출 실행
  • 확인(Assert) 기대한 결과가 나왔는지 검증 

Given-When-Then 패턴

  • Given 준비 구절에 해당
  • When 실행 구절에 해당 
  • Then 검증 구절에 해당

기능적으로는 AAA와 동일하나, 비개발자에게 더 읽기 쉬운 표현

BDD(Behavior-Driven Development) 스타일에서 주로 사용 

언제 어떤 테스트를 사용할까?

테스팅 피라미드 (Mike Cohn)

  • 단위 테스트 > 통합 테스트 > E2E 테스트 순으로 많이 작성
  • 테스트 커버리지 최대화 + 시간/리소스 최소화
  • 낮은 단계일수록 아이디어와 구현 간극이 짧아 안정화 필요 

테스팅 트로피/다이아몬드 (Guillermo Rauch)

  • 통합 테스트에 가장 큰 비중
  • 통합 테스트 > 단위 테스트 > E2E 테스트 순
  • 정적 테스트의 추가로 에러 사전 발견 

vitest 와 React Testing Library 실습해보기 

https://white-blank.tistory.com/186

 

vitest + React Testing Library 사용하여 테스트 케이스 만들기

해당 글에서는 vitest 와 React Testing Library 를 이용하여 테스트 케이스를 만드는 방법을 소개합니다.vitest 는 실행속도와 vite 와 통합이 간편하며, RTL 은 리액트 컴포넌트를 테스트 하기 위한 도구

white-blank.tistory.com

↑ 위 블로그 과정을 보고 따라 해보았다. 

 

먼저 필요한 라이브러리를 설치한다. 

pnpm add -D vitest jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom

 

그리고 vite.config.ts 를 수정한다. 

  • globals: true 테스트 파일에서 전역 함수를 import 없이 사용할 수 있게 설정
  • environment: 'jsdom' 테스트 환경을 JSDOM(Node.js 환경에서 브라우저와 유사한 DOM 환경을 시뮬레이션)으로 설정
  • setupFiles: './src/setupTests.ts' 테스트 실행 전, 실행될 설정 파일의 경로 지정 
/// <reference types="vitest" />
// https://vite.dev/config/
export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
  },
})

 

그 다음, 위에서 설정한 setup 파일을 해당 경로에 생성한다. 

import '@testing-library/jest-dom'

 

tsconfig.app.json 에서 types 배열에 vitest/globals와 @testing-library/jest-dom을 추가해

TypeScript가 전역  API를 인식하게 한다. 

{
  "compilerOptions": {
    "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
  }
}

 

마지막으로, package.json 에 스크립트를 추가하면 설정은 완료된다. 

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}

 

테스트 파일을 작성해야 하는데 나의 경우는 MovieCard 파일에 대한 테스트를 작성했다. 

파일명은 컴포넌트명.test.tsx 의 규칙으로 생성하며 컴포넌트 파일 옆에 두는 것이 일반적이다. 

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import MovieCard from './MovieCard'
import type { Movie } from '../model/types'

const baseMovie: Movie = {
  id: 1,
  title: '인터스텔라',
  overview: '우주 탐험 영화',
  poster_path: '/poster.jpg',
  release_date: '2014-11-06',
  vote_average: 8.6,
  popularity: 100,
  adult: false,
  original_language: 'en',
  original_title: 'Interstellar',
}

describe('MovieCard', () => {
  it('영화 제목이 렌더링된다', () => {
    render(<MovieCard movie={baseMovie} onClick={() => {}} />)
    expect(screen.getByText('인터스텔라')).toBeInTheDocument()
  })

  it('포스터 이미지가 렌더링된다', () => {
    render(<MovieCard movie={baseMovie} onClick={() => {}} />)
    const img = screen.getByAltText('인터스텔라')
    expect(img).toBeInTheDocument()
  })

  it('poster_path가 없으면 No Image 텍스트가 보인다', () => {
    render(<MovieCard movie={{ ...baseMovie, poster_path: null }} onClick={() => {}} />)
    expect(screen.getByText('No Image')).toBeInTheDocument()
  })

  it('adult가 true이면 19 배지가 보인다', () => {
    render(<MovieCard movie={{ ...baseMovie, adult: true }} onClick={() => {}} />)
    expect(screen.getByText('19')).toBeInTheDocument()
  })

  it('adult가 false이면 19 배지가 없다', () => {
    render(<MovieCard movie={baseMovie} onClick={() => {}} />)
    expect(screen.queryByText('19')).not.toBeInTheDocument()
  })

  it('카드 클릭 시 movie.id를 인자로 onClick이 호출된다', async () => {
    const handleClick = vi.fn()
    render(<MovieCard movie={baseMovie} onClick={handleClick} />)
    await userEvent.click(screen.getByText('인터스텔라'))
    expect(handleClick).toHaveBeenCalledWith(1)
  })

  it('예매하기 버튼이 렌더링된다', () => {
    render(<MovieCard movie={baseMovie} onClick={() => {}} />)
    expect(screen.getByRole('button', { name: '예매하기' })).toBeInTheDocument()
  })
})

 

pnpm test로 테스트를 실행하면 아래와 같이 테스트 결과가 나온다. 

MSW(Mock Service Worker)를 활용한 API Mocking

https://mswjs.io/docs/quick-start 

 

Quick start

Get MSW up and running in under five minutes.

mswjs.io

API Mocking

실제 백엔드 서버 없이 가짜 API 응답을 만들어내는 것

요청이 가기 전에 중간에서 가로채서 미리 만든 가짜 데이터를 응답으로 돌려준다. 

  • 백엔드가 아직 없을 때 프론트 먼저 개발 가능
  • 테스트 할 때 실제 서버에 의존하지 않아도 됨 
  • 에러 상황, 로딩 상황 등을 마음대로 재현 가능 

MSW(Mock Service Worker)

서비스 워커 기술을 활용하여 네트워크 레벨에서 API 요청을 가로채고 모킹할 수 있는 라이브러리 

쉽게 말해, 실제 네트워크가 이루어지는 것처럼 프론트엔드의 응답값을 주는 라이브러리이다. (가짜 API 생성)

  • 별도 Mocking 서버를 구축할 필요 없음
  • 프레임워크과 라이브러리에 종속되지 않음
  • 브라우저, Node.js 등 다양한 환경 지원 
  • 기존 코드 수정 없이 사용 가능 

세팅해보기

1. MSW 설치 

pnpm add -D msw

 

2. Service Worker 파일 생성하기 

public/mockServiceWorker.js가 생성되고 package.json에 msw.workerDirectory 항목이 자동으로 추가된다. 

npx msw init public/ --save

 

3. 가짜 데이터 파일 작성하기 

src/mocks/data/ 위치에 가짜 데이터 파일을 생성한다.

실제 API가 반환값과 같은 형태의 데이터를 타입에 맞게 작성해야 한다. 

나의 경우, 영화 데이터를 넣었다. 

import type { Movie, MovieListResponse, MovieDetail } from '@/entities/movie/model/types'

export const mockMovies: Movie[] = [
  {
    id: 1,
    title: '인터스텔라',
    overview: '우주를 배경으로 한 SF 영화',
    poster_path: '/poster1.jpg',
    release_date: '2014-11-06',
    vote_average: 8.6,
    popularity: 100,
    adult: false,
    original_language: 'en',
    original_title: 'Interstellar',
  },
  {
    id: 2,
    title: '기생충',
    overview: '계층 간의 갈등을 다룬 한국 영화',
    poster_path: '/poster2.jpg',
    release_date: '2019-05-30',
    vote_average: 8.5,
    popularity: 90,
    adult: false,
    original_language: 'ko',
    original_title: '기생충',
  },
]

export const mockMovieListResponse: MovieListResponse = {
  page: 1,
  results: mockMovies,
  total_pages: 1,
  total_results: 2,
}

export const mockMovieDetail: MovieDetail = {
  ...mockMovies[0],
  genres: [{ id: 878, name: 'SF' }],
  runtime: 169,
  tagline: '우주의 끝에서 답을 찾다',
  status: 'Released',
  vote_count: 30000,
  backdrop_path: '/backdrop1.jpg',
}

 

4. 핸들러 파일 작성하기

어떤 URL로 요청이 오면 어떤 데이터를 응답할지 규칙을 정의한다.

import { http, HttpResponse } from 'msw'
import { mockMovieListResponse, mockMovieDetail } from './data/movies'

const BASE = 'https://api.themoviedb.org/3'

export const handlers = [
  http.get(`${BASE}/movie/popular`, () => {
    return HttpResponse.json(mockMovieListResponse)
  }),

  http.get(`${BASE}/movie/now_playing`, () => {
    return HttpResponse.json(mockMovieListResponse)
  }),

  http.get(`${BASE}/movie/top_rated`, () => {
    return HttpResponse.json(mockMovieListResponse)
  }),

  http.get(`${BASE}/movie/upcoming`, () => {
    return HttpResponse.json(mockMovieListResponse)
  }),

  http.get(`${BASE}/movie/:movieId`, () => {
    return HttpResponse.json(mockMovieDetail)
  }),
]

 

5. 브라우저용 설정 파일 생성하기 

브라우저는 테스트와 달리, Service Worker를 사용한다.

[React 앱] → fetch 요청 → [Service Worker가 가로챔] → 가짜 응답 반환 의 순서로 실행된다. 

import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)

 

6. 테스트용 설정 파일 생성하기 

테스트 환경(Vitest)은 브라우저가 아닌 Node.js에서 실행된다. 

Node.js에는 Service Worker가 없으므로 MSW가 대신 http 모듈을 가로채는 방식으로 작동된다. 

[테스트 코드] → fetch 요청 → [MSW Node 서버가 가로챔] → 가짜 응답 반환 의 순서로 실행된다

import { setupServer } from 'msw/node'
import { handlers } from './handlers'

export const server = setupServer(...handlers)

 

7. 테스트 환경에 MSW 연결하기 

이전에 설정한 setup 파일에 추가적으로 설정한다. 

  • beforeAll(() => server.listen()) 테스트 시작 전 서버 켜기 
  • afterEach(() => server.resetHandlers()) 각 테스트 후 핸들러 초기화 
  • afterAll(() => server.close()) 테스트 끝나면 서버 끄기 
import '@testing-library/jest-dom'
import { server } from './mocks/server'

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

 

8. 앱 진입점에 MSW 연결하기 

main.tsx 에서 개발 환경에서만 MSW가 켜지도록 enableMocking()으로 감싼다. 

  • onUnhandledRequest: 'bypass' mock 등록 안 한 요청은 실제 서버로 그냥 통과시킨다. 
async function enableMocking() {
  if (import.meta.env.DEV) {
    const { worker } = await import('./mocks/browser')
    return worker.start({ onUnhandledRequest: 'bypass' })
  }
}

enableMocking().then(() => {
  createRoot(document.getElementById('root')!).render(...)
})

 

pnpm dev 를 통해 실행하면, 이렇게 설정한 가짜 데이터가 응답으로 나오는 것을 볼 수 있다. 

 

https://techblog.woowahan.com/2613/

 

테스트 코드 없이 레거시 코드를 다 감수하시겠습니까? | 우아한형제들 기술블로그

부서 이동을 하다 2018년 말미, 결제/정산 파트에서 주문중계 파트로 부서 이동하게 되었습니다. 인사 발령을 받고 나서 팀 이동을 하게 되면 누구나 직면하게 되는 상황이 발생하는데요. 그것은

techblog.woowahan.com

https://velog.io/@leeseonseonje/%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%EC%A1%B0

 

단위 테스트 AAA 패턴

AAA 패턴 AAA 패턴은 각 테스트를 준비(arrange), 실행(act), 검증(assert)이라는 세부분으로 나눌 수 있다. 모든 테스트가 단순하고 균일한 구조를 갖는데 도움이 된다. (일관성) 테스트를 쉽게 읽고, 이

velog.io

https://velog.io/@antisdun/%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8

 

단위 테스트와 통합 테스트

단위 테스트 단위 테스트는 소프트웨어 개발 과정에서 가장 작은 단위의 코드, 예를 들어 각 컴포넌트 혹은 함수가 기대한 대로 작동하며 그 기능 요구사하을 만족시키는지 검증하는 것이다. 프

velog.io

https://velog.io/@xeropise1/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%86%B5%ED%95%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-E2E%ED%85%8C%EC%8A%A4%ED%8A%B8

 

테스트란? (단위테스트, 통합테스트, E2E테스트)

테스트란 소프트웨어의 관점에서 정의하면프로그램을 실행하는 경우에 요구 사항에 맞춰 동작하는지 검증하는 행위 라고 할 수 있다.테스트에는 다양한 종류가 있는데, 보통 범위에 따라서 단

velog.io

https://velog.io/@sujeong_dev/MSWMock-Service-Worker%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-API-mocking

 

MSW(Mock Service Worker)를 이용한 API mocking

프론트 개발을 하던 와중에 API 개발속도와 맞지 않아 동시에 개발이 진행되게 되어 /public경로에 실제 API response의 json key-value값을 맞춰서 mock data를 만들어 mocking하는 식으로 UI를 만들었다. 그러

velog.io

https://tech.ktcloud.com/entry/MSW%EB%A1%9C-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-API-Mocking

 

MSW로 프론트엔드 개발 프로세스 개선하기 : API Mocking

[kt cloud 플랫폼Innovation팀 송재희 님] MSW로 프론트엔드 개발 프로세스 개선하기 : API Mocking 프론트엔드 개발자라면, 종종 백엔드 API가 준비되기 전까지 대기해야 하는 상황을 경험해 보셨을 겁니

tech.ktcloud.com