어제 도서관에서 테스트 자동화에 대한 책을 읽고 돌아와서, 책의 내용을 티스토리에 정리하면서 실제로 해당 툴/프레임워크를 직접 사용해보며 공부해야겠다는 생각이 들었다.
https://hjinn0813.tistory.com/197
[도서] 프런트엔드 개발자를 위한 테스트 가이드
다른 책 읽으려고 도서관에 갔다가 우연히 발견하고 제목이 눈길을 끌어 그 자리에서 정독하고 온 책이다.사실 나도 요즘 테스트 자동화에 대해 관심이 있었는데, 책 덕분에 시야가 넓어진 것
hjinn0813.tistory.com
어제 글에서도 언급했지만, 나는 테스트 코드를 작성해본 경험이 없다.😅
실무에서 [로컬 개발 → 테스트 → 스테이징 배포 → 테스트 → 운영 배포 → 테스트]의 워크플로우로 일했지만, 이 과정 사이에 존재하는 모든 검증은 전부 100% 개발자가 직접 확인하는 수동 테스트였다. 새로운 기능을 빠르게 운영 서버에 반영해야하는 상황이라 테스트 코드를 작성할 시간이 없었으니 어쩔 수 없었지만, 매 단계마다 똑같은 UI를 일일이 클릭하고 입력하며 전수 검사를 하는 과정은 솔직히 지치는 작업이었다. 테스트를 할 때마다 ‘반복되는 검증 단계 중에 한 번 정도는 누가 대신해주면 좋겠다’ 라고 생각했었다.
그래서 현재 작업하고 있는 개인 프로젝트에 테스트 자동화 환경을 직접 구축해보기로 했다. 테스트 자동화를 시작하려면, 가장 먼저 내 코드를 대신 실행하고 채점해줄 '테스트 실행기(Test Runner)'라는게 필요하다. 본격적으로 공부하기에 앞서, 테스트 실행기의 핵심 개념과 대표적인 툴인 Jest, Vitest의 특징부터 정리해본다.✍️
테스트 실행기(Test Runner)란?
테스트 코드를 자동으로 실행하고 검증 결과를 시각적으로 출력해주는 도구.
대표적인 툴로는 Jest, Vitest, Mocha, Jasmine 등이 있으며, 현재 시장을 양분하고 있는건 Jest와 Vitest이다.
터미널에 npm run test 같은 명령어를 입력하면, 테스트 실행기는 사전에 정의된 규칙에 따라 철저하게 순차적(직렬적)으로 움직이며 코드를 검증한다. 구체적인 워크플로우는 다음과 같다.
테스트 실행기의 6단계 워크플로우
- 테스트 파일 탐색 (Discovery)
명령어 입력 직후, 실행기는 프로젝트 최상단(Root)에 있는 설정 파일(jest.config.js또는vitest.config.ts)을 찾아 읽는다. 설정 파일에 명시된 규칙을 바탕으로 프로젝트 전체 디렉토리를 검색하며,*.test.js혹은*.spec.ts같은 테스트 대상 파일들을 찾아내어 하나의 실행 큐(Queue)에 수집한다. - 모킹 및 환경 격리 (Mocking Isolation)
테스트 코드를 본격적으로 구동하기 전, 외부 환경과 완전히 분리되어 안전한 '독립 실험실'을 세팅한다. 실제 데이터베이스(DB) 연결이나 외부 서버 API 호출처럼 테스트 결과를 예측할 수 없게 만드는 의존성들을 가짜 객체(Mock)로 가로채어 대체한다. 이를 통해 네트워크가 끊기거나 외부 서버가 다운되어도 오직 코드의 논리적 결함만 순수하게 검증할 수 있도록 보장한다.
(만약 외부 의존성이 없는 순수한 유틸 함수 로직을 테스트한다면 이 단계는 자동으로 생략된다.) - 테스트 코드 실행 (Execution)
준비된 격리 환경 위에서 실제 소스 코드와 작성된 테스트 코드를 메모리에 올리고, 위에서부터 차례대로 구동한다. - Assertion 검사 (Evaluation)
코드가 구동되다가 expect()로 시작하는 단언(Assertion) 구문을 만나면 실시간으로 채점이 이루어진다. 소스 코드가 실제로 뱉은 결과값(Actual)과 개발자가 적어둔 기대값(Expected)이 완벽하게 일치하는지 대조한다. - 코드 커버리지 측정 (Coverage Analysis)
모든 테스트 코드의 실행이 종료되면, 실행기는 소스 코드 전체를 다시 훑어본다. 개발자가 작성한 전체 소스 코드 중 실제로 테스트 로봇이 밟고 지나간 코드가 몇 줄인지를 계산하여, 테스트의 빈틈과 충실도를 백분율(%) 수치로 측정한다. - 최종 결과 출력 (Reporting)
모든 검증 프로세스가 완료되면 터미널 창에 최종 성적표를 시각적으로 인쇄한다. 전체 통과 여부(PASS / FAIL), 실패한 테스트가 있다면 에러가 발생한 정확한 코드 라인과 이유, 그리고 방금 계산된 코드 커버리지 수치를 종합 리포트로 출력하며 워크플로우를 마무리한다.
💡 Assertion 라이브러리
코드의 실행 결과가 예상한 조건에 부합하는지 검증하고, 조건이 불일치하면 테스트를 실패시키는 도구.
역사적으로 JS의 Assertion 라이브러리는 두 가지 흐름이 있는데,
1. TDD 스타일 (Test Driven Development)
가장 고전적인 방식으로,assert.equal(add(1, 2), 3)같은 형식을 사용한다.
함수 호출이라 직관적이고 단순하지만, 조건이 복잡해지면 가독성이 떨어진다는 단점이 있다.
2. BDD 스타일 (Behavior Driven Development)
코드가 문장처럼 읽히는 문법을 추구하여,expect(add(1, 2)).toBe(3)같은 식으로 작성한다.
가독성이 좋아서 초보에게도 친화적이지만, 제대로 사용하려면 많은 메서드(matcher)를 알아야된다는 단점이 있다.
최근 프론트엔드에서는 BDD 스타일이 표준처럼 사용되고 있다.
대표적으로 많이 쓰이는 matcher는 아래와 같다.
expect(결과).toBe(정답): 숫자나 문자열 같은 원시 값이 정확히 일치하는지 비교 (===)expect(결과).toEqual(정답): 객체나 배열의 내부 내용물이 똑같은지 깊게 비교 (Deep Equal)expect(결과).toContain(값): 배열이나 문자열에 특정 값이 포함되어 있는지 검사expect(결과).toBeNull(): 결과가 null인지 검사
Jest : 자바스크립트 테스트 생태계의 절대 강자
2014년 Meta(구 Facebook)에서 React 프로젝트를 지원하기 위해 개발한 오픈소스 테스트 프레임워크.
Jest가 등장하기 이전의 자바스크립트 테스트 환경은 테스트 실행기(Mocha), Assertion 라이브러리(Chai), Mocking 라이브러리(Sinon)를 개발자가 일일이 따로 설치하고 복잡하게 설정해야 했다. Jest는 이 모든 기능을 '올인원(All-in-One)'으로 한 번에 제공하며 등장했고, 복잡한 설정에 지쳐있던 전 세계 개발자들의 선택을 받으며 단숨에 사실상의 표준으로 자리 잡았다.
특징 및 문법
- Zero Configuration (설정 최소화):
별도의 복잡한 세팅 없이도 대다수의 자바스크립트 프로젝트에서 곧바로 작동하는 것을 목표로 한다. - 직관적인 BDD 문법: 앞서 설명한 describe, test/it, expect().toBe() 형태의 인간 친화적인 문법 구조를 사용한다.
- 스냅샷 테스트 (Snapshot Testing):
React 컴포넌트의 HTML 구조를 통째로 저장해 두고, 코드가 수정되었을 때 UI가 의도치 않게 변하지 않았는지 통째로 비교한다.
장점과 단점
| 장점 | - 압도적인 생태계와 커뮤니티: 10년 가까이 왕좌를 지켜온 만큼 구글링이나 스택오버플로우에 해결책이 널려있다. 웬만한 라이브러리나 프레임워크는 Jest와의 연동 가이드를 기본으로 제공한다. - 안정성: 수많은 글로벌 대기업의 대규모 프로젝트에서 검증된 엔진이다. |
| 단점 | - Vite 및 최신 빌드 도구와의 호환성 저하: Jest는 기본적으로 Webpack 시절에 설계되었다. 따라서 최신 빌드 도구인 Vite 기반의 프로젝트에서 사용하려면 Babel, TypeScript, ESM(EcmaScript Modules) 관련 플러그인을 수없이 깔고 설정해야한다. - 속도의 한계: 내부적으로 무거운 변환(Transpile) 과정을 거치기 때문에, 프로젝트 규모가 커질수록 테스트 실행 속도가 눈에 띄게 느려진다. |
Vitest : Vite 생태계를 위해 태어난 차세대 주자
2021년 말, Vite 개발 팀에 의해 만들어진 차세대 테스트 프레임워크.
Webpack 중심의 무거운 개발 환경이 Vite라는 초고속 도구로 세대교체되면서, "빌드 도구는 이렇게 빠른데 왜 테스트 실행기는 아직도 옛날 Jest를 쓰며 느려야 하지?" 라는 의문에서 출발했다. Vite의 초고속 아키텍처를 테스트 환경에 그대로 이식하여, 출시되자마자 최신 프론트엔드 개발자들 사이에서 폭발적인 인기를 얻고 있다.
특징 및 문법
- Vite와의 완벽한 통합:
프로젝트 최상단에 있는vite.config.ts설정 파일과 플러그인 세팅을 테스트 실행기가 그대로 공유한다. 즉, Vite 프로젝트라면 별도의 테스트 설정 파일조차 거의 필요 없다. - Jest와의 호환성:
Vitest는 개발자들이 기존 Jest 문법에 익숙하다는 점을 배려하여, Jest의 API와 마이그레이션 가이드를 99% 똑같이 제공한다. 개발자 입장에서 학습 비용이 제로에 가깝다. - 초고속 HMR(Hot Module Replacement) 기반 Watch 모드:
코드를 수정하면 전체 테스트를 다시 돌리는 것이 아니라, 수정된 파일과 연관된 테스트만 번개 같은 속도로 찾아내어 실시간으로 재실행한다.
장점과 단점
| 장점 | - 빠른 실행 속도: Vite의 강력한 로더와 파일 캐싱 시스템을 활용하기 때문에, 처음 실행할 때나 코드를 수정할 때나 Jest와는 차원이 다른 속도를 자랑한다. - 타입스크립트(TypeScript) 및 JSX 기본 지원: 별도의 Babel이나 ts-jest 설정 없이도 타입스크립트 코드를 곧바로 읽고 실행해 준다. - 기본적인 UI 대시보드 제공: 터미널 창뿐만 아니라, 브라우저 상에서 시각적으로 테스트 현황을 볼 수 있는 전용 UI(대시보드) 기능을 내장하고 있다. |
| 단점 | - 상대적으로 젊은 생태계: 2021년에 출시된 만큼, 오래된 고전 프로젝트나 특정 백엔드 프레임워크(예: NestJS의 일부 기본 보일러플레이트)에서는 여전히 Jest를 기본값으로 제공하는 경우가 많아 아주 드물게 호환성 이슈를 직접 해결해야 할 수 있다. |
Jest, Vitest 한 눈에 비교하기
| Jest | Vitest | |
| 모듈 시스템 (ESM) | CommonJS 기반 (최신 문법은 변환 필요) |
네이티브 ESM 지원 (변환 없음) |
| DOM 환경 (jsdom) | 기본 탑재 + 강제 실행 → 무거움 | 선택적 탑재 가능 (jsdom / happy-dom) |
| 멀티스레딩 방식 | 자식 프로세스 (Child Process) → 무거움 | 워커 스레드 (Worker Threads) → 가벼움 |
| Vite에서의 설정 | Babel, ts-jest 등 설정이 복잡함 | vite 설정 파일 공유하여 별도 설정 없음 |
| 실행 속도 | 상대적으로 무겁고 느림 | 압도적으로 빠름 (HMR 및 캐싱 기반) |
| 생태계 및 자료 | 역사가 오래되어 자료가 많음 | 빠르게 성장 중 (Jest와 문법 99% 호환) |
결정적인 3가지 차이
Jest와 Vitest는 겉보기에는 비슷한 문법을 쓰지만, 내부 아키텍처를 들여다보면 Vitest가 압도적으로 빠른 이유를 알 수 있다.
1. ESM(EcmaScript Modules)을 대하는 태도 차이
요즘 자바스크립트는 import / export를 사용하는 ESM 문법이 표준이다.
- Jest는 과거 require를 쓰던 CommonJS 시절에 태어났기 때문에, 최신 ESM 문법을 이해하려면 중간에서 바벨(Babel) 같은 도구가 옛날 코드로 변환(Transpile)해주는 과정을 거쳐야 하고, 이 과정에서 설정이 꼬이고 속도가 느려진다.
- 반면, Vitest는 처음부터 최신 ESM과 TypeScript를 네이티브로 지원하도록 설계되었다. 불필요한 변환 과정 없이 코드를 있는 그대로 읽어 들이기 때문에 오버헤드(낭비)가 없고 속도가 빠르다.
2. 브라우저 환경(DOM)을 흉내 내는 방식의 차이
터미널(Node.js 환경)에는 화면을 그려주는 브라우저가 없다.
따라서 리액트 컴포넌트를 테스트하려면 가상 DOM을 만들어야 한다.
- Jest는 무거운 가짜 브라우저 라이브러리인 jsdom을 내부에 기본 탑재하고 강제로 구동한다. 이 때문에 화면이 필요 없는 순수한 함수(Utility)만 테스트할 때도 불필요하게 무거운 브라우저 환경을 띄우느라 리소스가 낭비된다.
- Vitest는 기본적으로 아주 순수한 Node.js 환경만 제공하여 유틸 함수 테스트를 빛의 속도로 끝낸다. 그리고 컴포넌트 테스트처럼 브라우저 환경이 꼭 필요할 때만 jsdom이나 이보다 훨씬 가벼운 happy-dom을 선택적으로 지정할 수 있어 메모리를 효율적으로 쓴다.
3. 멀티스레딩(Multi-threading) 아키텍처의 차이
자바스크립트(Node.js)는 기본적으로 한 번에 한 가지 일만 하는 싱글 스레드로 작동한다. 수많은 테스트 파일을 동시에 돌리려면 멀티태스킹 아키텍처가 필수적이고, 두 가지 툴 모두 여러 개의 테스트 파일을 동시에 빠르게 돌리기 위해 컴퓨터의 두뇌(CPU)를 나누어 쓰는 멀티스레딩 기법을 사용한다.
- Jest는 테스트 파일마다 완전히 독립된 자식 프로세스(Child Process)를 컴퓨터에 새로 띄운다. 이 방법은 안전하지만, 프로세스를 생성하고 메모리를 통째로 할당하는 비용이 굉장히 커서, 파일이 많아질수록 무거워진다.
- Vitest는 Node.js의 최신 기술인 "워커 스레드(Worker Threads)"를 적극 활용한다. 하나의 거대한 프로세스 안에서 일의 줄기(Thread)만 가볍게 여러 갈래로 나누어 동시 처리를 하기 때문에, 파일 간 전환 속도가 빠르고 메모리 점유율도 대폭 낮아진다.
결론 : 그래서 나의 선택은?
과거 Webpack 기반의 거대한 레거시 프로젝트나, 안정성이 최우선이며 기존에 구축된 생태계를 유지해야 하는 대형 백엔드 시스템(예: NestJS 중심의 환경)이라면 Jest가 여전히 안전하고 든든한 선택지일 것이다. 수많은 레퍼런스와 커뮤니티의 힘은 무시할 수 없기 때문이다.
하지만 현재 내가 진행하고 있는 개인 프로젝트처럼 Vite와 TypeScript를 기반으로 한 프론트엔드 웹앱이라면, 고민의 여지 없이 Vitest를 선택하는 것이 압도적으로 이득이라고 생각했다. 복잡한 설정 지옥에서 벗어날 수 있을 뿐만 아니라, 코드 수정과 동시에 피드백을 받을 수 있다는건 개발 경험(DX)을 완전히 다른 차원으로 끌어올려 주기 때문이다. 이러한 명확한 기술적 배경과 이유 때문에, 내가 공부해볼 테스트 엔진은 Vitest로 낙점했다.👍
매 배포 단계마다 지루하게 반복되던 수동 테스트의 늪에서 벗어나기 위한 첫 단추는 꿰어졌다. 코드를 대신 실행하고 채점해 줄 든든한 로봇(Test Runner)의 정체를 알았으니, 다음 포스팅에서는 이 로봇을 실제로 브라우저 위에서 움직이게 만들어 줄 '테스트 자동화 프레임워크(Cypress, Playwright, Puppeteer)'의 개념을 정리해봐야겠다!

'💻 Frontend > JS, TS' 카테고리의 다른 글
| 테스트 실행기 입문 과정 (vitest 적용) (0) | 2026.05.26 |
|---|---|
| 테스트 자동화 프레임워크 개념 정리 (cypress, playwright, puppeteer) (0) | 2026.05.25 |
| 쿠키 설정하기 (0) | 2025.07.04 |
| 교차상태 감지 웹 API - IntersectionObserver() (0) | 2025.04.23 |
| Socket.io 문법 개념 정리 (0) | 2025.04.19 |