자바스크립트 테스트의 진화 — Jasmine에서 Vitest까지

테스트를 염두에 두지 않은 장소인 브라우저에서 출발한 자바스크립트 테스트 문화는, Jasmine·Mocha+Chai+Sinon·Karma·QUnit이 병립하던 분열기를 거쳐 2016년 Jest의 대개편으로 「올인원」에 수렴했다. Testing Library의 「구현 세부사항을 피한다」는 철학, 스냅샷 테스트의 공과, 그리고 Vite 시대의 Vitest까지, 분열과 수렴을 반복한 JS 테스트사를 따라간다.

testinghistoryjavascriptjesttesting-libraryvitest

자바스크립트의 테스트 생태계는 다른 언어보다 훨씬 격렬한 분열과 수렴을 반복해 온 역사를 갖고 있다. 이유는 명확하다. 실행 환경이 오랫동안 「브라우저」라는, 애초에 테스트를 상정하지 않은 장소였다는 점, 그리고 언어 자체가 표준 라이브러리가 빈약해 작은 부품들을 조합해 쓰는 커뮤니티 문화를 갖고 있었다는 점이다. 테스트 러너·어서션 라이브러리·모킹 라이브러리·DOM 환경 시뮬레이터가 각각 별개의 패키지로 존재했고, 개발자는 스스로 그 조합을 선택해야 했다. 이 상태는 2010년대 중반 Jest의 등장으로 한 차례 「올인원」으로 수렴했고, 2020년대에는 Vite 생태계의 Vitest가 새로운 수렴점으로 떠오르고 있다.

브라우저라는 까다로운 출발점

2000년대 후반까지 자바스크립트 테스트는 Selenium 같은 브라우저 자동화 도구로 UI를 외부에서 두드리거나, 아예 테스트를 하지 않는 쪽이 주류였다. 유닛 테스트 문화는 자바나 루비에 비해 뿌리내리는 속도가 확연히 느렸다.

배경에는 다음과 같은 사정이 있다.

  • DOM과 window 객체에 대한 의존이 강해, Node.js가 등장하기(2009년) 전에는 브라우저 밖에서 자바스크립트를 실행할 표준적인 방법이 없었다
  • 모듈 시스템이 존재하지 않아(ES Modules는 ES6에 해당하는 2015년에야 표준화됨), 코드를 분리하고 주입하기가 어려웠다
  • QUnit(jQuery에서 유래, 후술)이나 JsUnit 같은 선구자들이 있었지만, 커뮤니티 전체의 표준이 되지는 못했다

이 시기에 태어난 Jasmine·Mocha·Karma·QUnit이, 이후 10년 넘게 쓰이는 토대를 만들게 된다.

Jasmine — BDD 스타일의 선구자

Jasmine은 Pivotal Labs(현 VMware 산하)가 개발해 2010년에 공개했다. 루비의 BDD 프레임워크 RSpec과, JSSpec·ScrewUnit 같은 선행 도구들의 영향을 강하게 받아, describe / it / expect라는 자연어에 가까운 구문으로 소프트웨어의 동작을 기술하는 BDD(Behavior-Driven Development) 스타일을 자바스크립트에 들여왔다.

Jasmine의 특징은 「의존이 적다는 것」이었다. DOM에도 다른 라이브러리에도 의존하지 않고, 어서션과 모킹(스파이)까지 내장하고 있어 그 자체로 완결된 테스트를 작성할 수 있었다. 이 「프레임워크 단독으로 완결된다」는 설계 사상은 훗날 Jest에도 이어진다. AngularJS의 공식 테스트 스택으로 Karma와 함께 오랫동안 쓰였다.

Mocha + Chai + Sinon — 조합의 문화

Mocha는 TJ Holowaychuk이 만들어 2011년 무렵 공개했다. Jasmine과 달리 Mocha는 테스트 러너(describe/it의 실행 기구)에만 전념하고, 어서션 라이브러리는 내장하지 않는 「unopinionated(무주견)」한 설계를 택했다. 이는 Node.js 커뮤니티의 「작은 부품을 조합한다」는 철학을 짙게 반영한 것이다.

Mocha는 그 자체로는 기능이 부족해, 보통 다음 부품들과 조합해 사용되었다.

라이브러리 역할 작성자
Mocha 테스트 러너(describe/it, 비동기 지원) TJ Holowaychuk
Chai 어서션 라이브러리(expect/should/assert 3가지 스타일) Jake Luer
Sinon.js 스파이·스텁·모킹 라이브러리 Christian Johansen

Christian Johansen은 저서 『Test-Driven JavaScript Development』(2010년)의 저자이기도 하며, Sinon.js는 그 실전 지식의 결정체로 설계되었다. 「Mocha + Chai + Sinon」 조합은 2010년대 전반~중반의 사실상 정석 스택이 되었지만, 뒤집어 말하면 신규 프로젝트를 시작할 때마다 각 라이브러리의 버전 정합성과 설치 절차를 스스로 해결해야 했고, 이 「셋업의 무거움」이 오랫동안 자바스크립트 테스트의 진입 장벽이었다.

Karma — 브라우저 러너의 표준화

유닛 테스트의 로직 자체는 Node.js 위에서 완결되더라도, 「실제 브라우저(Chrome·Firefox·Safari, 당시에는 IE도)에서 정말로 동작하는가」를 확인하고 싶은 수요는 남는다. Karma는 이 과제에 답하는 테스트 러너로, 구글의 AngularJS 팀에 소속되어 있던 Vojta Jína가 개발했다. 처음에는 Testacular라는 이름이었지만, 상표와 발음 문제로 2012년 Karma로 개칭되었다.

Karma는 설정 파일에 대상 브라우저(실기·헤드리스 무관)를 지정하면, 여러 브라우저에서 테스트를 병행 실행하고 결과를 집약한다. AngularJS의 공식 테스트 스택(Jasmine + Karma)으로 널리 퍼지며, 「유닛 테스트는 Node.js 안에서 완결하되, 실제 브라우저 검증도 자동화한다」는 이중 구조의 발상을 정착시켰다.

QUnit — jQuery에서 태어난 노포

QUnit은 jQuery의 작성자 John Resig가 jQuery 프로젝트 자신의 테스트 코드로 2006년에 쓰기 시작해, 2008년에 독립 프로젝트로 분리했다. 처음에는 jQuery 자체에 의존했지만, 2009년의 재작성으로 완전히 독립했다. 자바스크립트 테스트 도구 중에서도 가장 오래된 축에 속하며, 지금도 jQuery 본체와 그 플러그인 생태계의 표준 테스트 도구로 계속 쓰이고 있다.

Jest — 제로 컨피그의 혁명

Jest는 원래 페이스북 사내에서 2011년 무렵, 채팅 기능을 자바스크립트로 다시 쓰는 프로젝트의 일환으로 태어났다. 2014년 오픈소스로 공개될 당시에는 Jasmine을 기반으로 하고 있어 아직 뚜렷한 독자성은 없었다. 전환점은 2016년, Christoph Nakazawa를 중심으로 한 팀에 의한 대규모 재작성이다. 이 해에 다음 요소들이 실현되면서, Jest는 「프레임워크」에서 「올인원 테스트 플랫폼」으로 다시 태어났다.

  • 어서션·모킹·커버리지 계측을 모두 내장해, 추가 라이브러리 없이 테스트를 작성할 수 있는 「제로 컨피그」 설계
  • jsdom을 기본으로 통합해, 실제 브라우저 없이 DOM API를 시뮬레이션하며 테스트를 실행
  • 테스트의 병렬 실행(워커 프로세스 분할)을 통한 고속화
  • 워치 모드(변경된 파일과 관련된 테스트만 재실행)
  • 스냅샷 테스트(후술)의 도입

이 「하나만 설치하면 다 갖춰진다」는 경험은 Mocha + Chai + Sinon + Karma의 조합에 지쳐 있던 커뮤니티의 강한 지지를 받아, React 생태계를 중심으로 빠르게 사실상의 표준이 되었다. 2022년에는 Meta(구 페이스북)에서 OpenJS Foundation으로 프로젝트가 이관되었다.

스냅샷 테스트의 공과

Jest가 2016년에 도입한 스냅샷 테스트는 함수나 컴포넌트의 출력을 직렬화해 「정답」으로 파일에 저장해 두고, 이후 실행할 때마다 그 저장된 스냅샷과 차이가 없는지 비교하는 기법이다. React 컴포넌트의 렌더링 결과처럼 「복잡하지만 눈으로 확인하기 어려운 출력」의 회귀 검출에 적합해, React Storybook의 「Storyshots」 등 다른 도구에도 파급되었다.

한편 운용상의 문제도 널리 지적되고 있다.

  • 차이가 날 때마다 --updateSnapshot(속칭 「스냅샷 업데이트」)을 기계적으로 실행하는 버릇이 들어, 리뷰되지 않은 채 「정답」이 다시 쓰인다
  • 거대한 컴포넌트 전체를 스냅샷하면, 무관한 변경에도 대량의 차이가 발생해 아무도 차이를 읽지 않게 된다
  • 스냅샷 자체는 「의도한 동작」을 주장하지 않기 때문에, 잘못된 출력이 그대로 「정답」으로 고정될 위험이 있다

현재의 베스트 프랙티스는 스냅샷을 작고 의도가 명확한 범위로 한정하고, 일반적인 어서션과 병용하는 것으로 여겨진다.

Testing Library — 「구현 세부사항」을 피하는 철학

Testing Library(dom-testing-library를 토대로 react-testing-library 등 프레임워크별 래퍼가 올라가는 구성)는 교육자이자 엔지니어인 Kent C. Dodds가 2018년에 공개했다. 그 이전 주류였던 Enzyme(Airbnb 제작)이 컴포넌트의 내부 상태나 private 메서드를 직접 검사할 수 있는 「shallow rendering」에 강점이 있었던 반면, Dodds는 이를 「구현 세부사항(implementation detail)에 의존하는 테스트」라고 비판했다. 리팩터링으로 동작이 바뀌지 않았는데도 테스트가 깨지는 문제였다.

Testing Library의 설계 철학은 다음 한 문장으로 집약된다.

“The more your tests resemble the way your software is used, the more confidence they can give you.” (테스트가 소프트웨어의 실제 사용 방식과 닮을수록, 그 테스트는 더 높은 확신을 준다)

구체적으로는 컴포넌트의 내부 구현이 아니라, 사용자가 실제로 접하는 DOM(역할·라벨·표시 텍스트 등)에 대해 쿼리하고 조작하는 API를 제공한다. 이 사상은 React 커뮤니티를 넘어 Vue·Angular·Svelte용 래퍼로도 퍼졌고, 지금은 공식적으로도 권장되는 표준적 접근법이 되었다. Dodds는 비슷한 시기에 통합 테스트에 대한 투자를 강조하는 「테스팅 트로피」라는 모델도 제안했는데, 그 전체상은 다음다음 회 기사에서 다룬다.

Vitest — Vite/ESM 네이티브의 신성

Vitest는 빌드 도구 Vite의 생태계에서 태어난 테스트 프레임워크로, 2021년 말에 공개되었다. Vite 코어 팀 멤버인 Anthony Fu가 중심이 되어 개발했다. Jest가 테스트 플랫폼으로서 지배적 지위를 구축한 뒤에도, 다음과 같은 불만이 남아 있었다.

  • Jest는 Babel 변환을 거치기 때문에, Vite(ESM 네이티브·고속 HMR)를 사용하는 프로젝트와 빌드 파이프라인이 이중화된다
  • ESM(ES Modules)이나 TypeScript의 네이티브 지원이 나중에 덧붙여진 형태라 설정이 복잡해지기 쉬웠다

Vitest는 Vite의 변환·해석 파이프라인을 그대로 재사용함으로써, Vite 프로젝트와의 이중 설정 관리를 없애고 빠른 테스트 실행을 실현했다. API는 의도적으로 Jest와 거의 호환되도록(describe/it/expect, 모킹 API도 마찬가지) 설계되어, 기존 Jest 프로젝트로부터의 이행 비용을 낮춘 점도 보급을 뒷받침했다.

JS 생태계의 분열과 수렴

자바스크립트 테스트 도구의 역사는 다음과 같은 「분열 → 수렴 → 재분열 → 재수렴」의 사이클로 정리할 수 있다.

시대 상황
~2009년 표준 없음. Selenium 등 외부 도구, 혹은 독자적인 스크립트가 중심
2010~2015년 Jasmine / Mocha+Chai+Sinon / Karma / QUnit이 병립하는 분열기
2016~2020년 Jest가 올인원으로서 사실상의 표준으로 수렴
2018년~ Testing Library가 테스트를 「어떻게 쓸 것인가」의 철학을 재통일
2021년~ Vite의 보급에 따라 Vitest가 새로운 고속 선택지로 대두, 재분열과 재수렴이 공존

흥미로운 점은, 도구가 바뀌어도 API의 겉모습(describe/it/expect)은 거의 공통적으로 유지되고 있다는 것이다. 이는 Jasmine이 확립한 BDD풍 어휘가 업계 표준으로 정착해, 도구를 갈아타는 비용을 낮추고 있다는 뜻이다. 브라우저를 통한 E2E 테스트 영역의 세대교체(Selenium→Cypress→Playwright)는 별도의 기사에서 다룬다.

이 기사에서 다음 기사로

자바스크립트라는 하나의 언어 안에서만도 이 정도의 분열과 수렴이 일어났다. 다음 기사에서는 시야를 넓혀 Python·Ruby·Java/Kotlin·Go·Rust·C#/.NET·Swift라는 주요 언어를 횡단하며, 「표준 라이브러리에 내장하는가」 혹은 「서드파티가 사실상의 표준이 되는가」라는 설계 사상의 차이를 비교한다.

← Back to 소프트웨어 테스트의 계보