테스트 환경

테스트의 정의에서 소개한 바와 같이, JavaScript 테스트는 기본적으로 Error을 발생시키지 않고 성공적으로 실행을 확인하는 코드에 불과합니다. 하지만 이 정의가 지나치게 단순화된 방법 중 하나는 코드를 실행하는 위치테스트 환경을 고려하지 않는 것입니다.

테스트 환경은 일반적으로 테스트를 실행하는 데 사용하는 런타임 환경 (예: 노드 또는 브라우저)과 사용할 수 있는 API라는 두 가지 구성요소로 간주될 수 있습니다.

런타임 환경

Node와 같은 런타임이나 Deno 또는 Bun과 같은 유사한 도구는 서버 측 또는 범용 JS 코드를 지원하는 것을 목표로 합니다. 이러한 환경에는 DOM 및 HTML 요소를 만들고 사용하는 등 브라우저에서 기대할 수 있는 API나 시각적 구성요소나 렌더링 타겟의 개념 (단순한 요소가 아니라 CSS를 사용하여 이러한 요소를 표시 영역에 시각적으로 렌더링)도 포함되지 않습니다.

따라서 사용 가능한 document 또는 window 객체가 없으므로 예를 들어 React 요소를 테스트 가능하도록 React 요소를 렌더링하려고 하면 이러한 범용 런타임이 실패합니다.

반면 브라우저 내에서 테스트를 실행하는 경우 이러한 런타임에서 기대할 수 있는 내장 API는 폴리필링이나 추가 작업 없이는 사용하지 못할 수 있습니다. 일반적으로 발생하는 문제는 파일 읽기 및 쓰기와 같습니다. 테스트 과정에서 브라우저 내에서 import { fs } from 'node:'fs';를 사용하여 이러한 방식으로 파일을 읽는 것은 불가능합니다.

'웹'과 '백엔드' API 문제는 서버 부분과 클라이언트 부분이 모두 있는 코드베이스를 두는 것이 어색할 수 있기 때문에 단순한 테스트 범위를 벗어납니다. 하지만 이는 테스트 가능한 코드 작성 아이디어와 연결되어 있으며 이 과정 전반에 걸쳐 다시 살펴보겠습니다.

알고리즘 또는 비즈니스 로직 테스트

일부 코드는 작동 및 테스트에 노드 또는 브라우저 가져오기가 필요하지 않습니다. 이 내용은 이 과정의 후반부에서 다룰 예정이지만 순수한 '비즈니스 로직'이 렌더링과 분리되거나 노드별 코드와 구분되도록 코드베이스를 구조화하면 테스트하기가 더 쉬워집니다.

간단한 예로 디스크에서 파일을 읽고 쓰고 프로세스에서 파일을 수정하는 노드 함수가 있다고 가정해 보겠습니다. 디스크에서 읽기 및 쓰기를 수행하는 함수를 허용하도록 함수를 리팩터링하면 어디서나 함수를 테스트할 수 있습니다.

이 경우 서버 측 런타임이나 브라우저에서 모든 환경을 사용하여 이 코드를 테스트할 수 있습니다. 테스트에서는 메모리에 가상 파일을 저장하거나 자리표시자 데이터를 반환하는 도우미를 제공할 수 있습니다. 이런 종류의 도우미는 테스트에 도입해도 괜찮습니다. 예를 들어 fs.writeFileSync의 작동 여부를 확인하는 것은 중요하지 않기 때문입니다. 자신의 코드와 코드를 고유하거나 위험하게 만드는 요소에 집중하세요.

브라우저 API 에뮬레이션

Vitest와 같은 많은 테스트 프레임워크에서는 브라우저를 실행하지 않고도 브라우저의 API 환경을 에뮬레이션하는 옵션을 제공합니다. Vitest는 내부적으로 JSDOM이라는 라이브러리를 사용합니다. 이는 브라우저 사용 오버헤드가 높은 간단한 구성요소 테스트에 적합합니다.

에뮬레이션 라이브러리의 일반적인 기능은 브라우저(예: DOM, 요소, 쿠키)를 에뮬레이션할 수 있지만 시각적 구성요소가 없다는 것입니다. 즉, HTML 요소 및 기타 프리미티브로 작업하는 필수적인 방법을 제공하지만 이미지 또는 화면에 출력을 렌더링하거나 페이지에서 요소의 위치를 픽셀로 확인할 수는 없습니다.

마찬가지로, 이 옵션은 구성요소가 React 요소나 웹 구성요소 등을 나타내는 구성요소 테스트에 적합할 수 있습니다. 이러한 유형의 구성요소는 일반적으로 비교적 작은 방식으로 DOM을 만들고 상호작용하며, 에뮬레이션된 브라우저는 구성요소가 의도한 대로 작동하는지 확인하기에 충분한 기능을 제공할 수 있습니다. 다음 섹션에는 Vitest 및 JSDOM을 사용한 React 구성요소 테스트의 예가 포함되어 있습니다.

브라우저 에뮬레이션은 JSDOM이 2014년에 출시된 것으로 정립된 관행이지만, 항상 실제 브라우저를 사용하는 것과 다릅니다. 이러한 차이는 명백할 수 있습니다. 예를 들어 JSDOM에는 레이아웃 엔진이 포함되어 있지 않으므로 요소의 크기를 확인하거나 스와이프와 같은 복잡한 동작을 테스트할 수 있는 방법이 없습니다. 차이는 미묘하고 알 수 없기 때문에 JSDOM 기반 테스트를 간결하게 유지하는 것이 가장 좋습니다. 그래야 동작이 실제와 다르게 될 위험을 '타임박스'할 수 있습니다.

실제 브라우저 제어

사용자가 경험하는 대로 코드를 테스트하려면 실제 브라우저를 사용하는 것이 가장 좋습니다. 실제로는 브라우저를 지원하는 런타임 테스트가 Node.js 내에서 '시작'을 실행하더라도 실제 브라우저의 인스턴스를 시작하고 제어합니다.

브라우저를 테스트의 일부로 제어하면 브라우저가 사용자와 같은 방식으로 열리므로 URL, 맞춤 HTML, JS 또는 테스트 실행에 필요한 모든 것을 로드하여 테스트에서 제어할 수 있습니다. 그런 다음 마우스를 조정하거나 입력 상자에 입력을 입력하는 등 사용자 역할을 하는 코드를 작성할 수 있습니다.

WebdriverIO 또는 웹 테스트 실행자와 같은 최신 도구는 모든 주요 브라우저를 제어할 수 있으며 동시에 여러 인스턴스를 실행할 수도 있습니다. 이러한 브라우저는 테스트 실행기에 인접하여 (예: 자체 컴퓨터에서 또는 CI 작업의 일부로) 실행되거나 자동으로 실행되는 외부 상용 서비스로 아웃소싱될 수 있습니다.

기존 테스트 라이브러리 (Vitest 및 Jest 포함)에는 브라우저 모드가 있는 경우가 많지만 출처가 Node.js이므로 브라우저 모드가 '추가'되어 유용한 기능이 누락되는 경우가 많습니다. 예를 들어 Vitest는 브라우저에서 모듈 가져오기를 모의 처리할 수 없습니다. 이는 다음 페이지의 예에서 사용하는 강력한 프리미티브입니다.

실제 사례

테스트가 복잡할수록 실제 브라우저를 사용하는 것이 점점 더 중요해집니다.

  • DOM의 기능을 전혀 사용하지 않거나 최소한으로 사용하는 테스트의 경우, Node.js 및 유사한 런타임(예: fetch 또는 EventTarget)에서 사용할 수 있는 기능조차도 환경이 중요하지 않습니다.
  • 소규모 구성요소 테스트의 경우 JSDOM이 적합할 수 있습니다.
  • 사용자 로그인 및 핵심 작업 실행을 시뮬레이션할 수 있는 엔드 투 엔드 테스트와 같이 규모가 더 큰 테스트는 실제 브라우저에서 완전히 실행하는 것이 합리적입니다.

이 섹션에서는 이론에 중점을 두며 테스트를 실행할 위치에 관한 다양한 관점을 제시합니다. 실제로 코드베이스는 요구사항과 테스트 도구가 제공하는 항목에 따라 다양한 유형의 테스트에 다양한 접근 방식을 사용하는 경우가 많습니다.

이해도 테스트

에뮬레이션 레이어 jsdom이 지원하지 *않는* 브라우저의 기능은 무엇인가요?

레이아웃 엔진
JSDOM은 시각적 도구가 아니므로 페이지의 요소 위치, 결정된 CSS 속성 또는 웹사이트 레이아웃의 다른 부분을 확인하는 데 사용할 수 없습니다.
WebSocket
JSDOM은 WebSocket 폴리필을 포함하므로 이를 사용하는 코드가 작동합니다.
requestAnimationFrame
`pretendToBeVisual` 플래그를 사용하면 실제로 아무것도 그려지지 않은 경우에도 jsdom이 60fps로 '애니메이션' 콜백을 호출합니다.