TDD/React Test

[React Test] 리액트 테스트에 대해 알아보자

Lamue 2023. 11. 24. 15:00

Create React App으로 리액트 앱을 생성해본 적이 있으신 분들은 한번씩 App.test.js 파일을 들어본 경험이 있으실 겁니다. 제가 처음 React를 배웠을 땐 생성과 함께 과감하게 삭제해도 되는 파일로 간주하곤 했습니다.

 

React는 생성과 함께 기본적인 테스팅 과정에서 React Testing Library를 사용하고 있습니다. 이러한 React Test Library는 App.test.js에 자동으로 추가되기 때문에 개발자는 프로젝트 생성과 함께 npm test 명령어를 실행해도 정상적으로 테스트가 동작하는 것을 확인할 수 있습니다. 이와같이 React에선 다양한 방식으로 테스트 코드를 작성하여 코드를 테스팅할 수 있습니다. 

 

이번 포스팅에선 React Test 진행할 알아야 React Testing Library Jest, 그리고 TDD 방법론에 대해 소개하겠습니다.

 

 

React Testing Library란 ?

React Testing Library(RTL)는 Behavior Driven Test(행위 주도 테스트) 방법론이 대두 되면서 함께 주목 받기 시작한 테스팅 라이브러리 입니다. 행위 주도 테스트는 기존에 관행이던 Implementation Driven Test(구현 주도 테스트)의 단점을 보완하기 위한 방법론입니다.

React 구성 요소 작업을 위한 API추가하여 DOM Testing Library 위에 구축됩니다. 여기서 DOM Testing Library란 DOM 노드를 테스트하기 위한 매우 가벼운 솔루션입니다. 

Create React App으로 생성된 프로젝트는 즉시 React Testing Library 지원합니다. 만약 React Testing Library 별도로 설치하고 싶다면 다음과 같이 npm 통해 설치할 있습니다.

$ npm install —save-dev @testing-library/react

 

React Testing Library는 에어비앤비에서 만든 Enzyme을 보완하는 솔루션입니다. 

Enzyme은 위에서 설명드린 Implementation Driven Test 방법론을 따르는 테스트를 작성하기에 적합합니다. 왜냐하면 Enzyme은 실제 브라우저 DOM이 아닌, React가 만들어내는 가상 DOM을 기준으로 테스트를 작성해야 합니다. 따라서 테스트 대상 React 컴포넌트에 어떤 prop이 넘어가고, 현재 state이 어떻게 되는지에 대해서 검증하기 용이합니다.

반면에, React Testing Library는 Behavior Driven Test 방법론을 따르는 테스트를 작성하는데 적합합니다. 왜냐하면 React Testing Library는 jsdom이라는 라이브러를 통해 실제 브라우저 DOM을 기준으로 테스트를 작성하게 됩니다. 따라서 어떤 React 컴포넌트를 사용하는지는 의미가 없으며, 결국 사용자 브라우저에서 랜더링하는 실제 HTML 마크업의 모습이 어떤지에 대해서 테스트하기 용이합니다.

테스트 패러다임 자체가 Behavior Driven Test 쪽으로 흘러가는 추세이기 때문에, 앞으로 점점 신규 프로젝트에서는 Enzyme이라는 보다는 React Testing Library 많이 선택될 확률이 높을 것입니다. 실제로 npm trends 확인해보면 React Testing Library 완벽히 패러다임 전환을 이루어낸 것으로 보입니다. 

 

 

구현 주도 테스트와 행동 주도 테스트

그렇다면 구현 주도 테스트와 행동 주도 테스트는 어떠한 차이가 있는지 예제와 함께 알아보겠습니다.

다음과 같은 코드가 있다고 가정해보겠습니다.

<p>
  Edit
    <code>
      src/App.js
    </code>
  and save to reload…
</p>

구현 주도 테스트에서는 위의 UI를 테스트할 때 주로 <p>태그가 쓰였고, Edit 등의 문자가 들어갔다는 것을 테스트합니다. 그래서 만약 <p>태그를 <h2>태그로 바꾸면 에러가 발생할 것입니다.

하지만 행동 주도 테스트에서는 사용자의 입장에서 테스트 하기에 <p>태그가 쓰이고 어떻게 글자를 표현하는지가 아닌 어떠한 이벤트를 발생시켰을 화면이 어떻게 변화 되는지 중점을 두고 있습니다. 

 

 

DOM(Document Object Model) 이란 ?

React Testing Library에 대해 알아보기 전에 DOM에 대해서 자세히 알아보겠습니다. 

DOM(문서 객체 모델) XML, HTML 문서의 항목을 계층으로 표현하여 생성, 변형, 삭제할 있도록 돕는 인터페이스입니다. DOM 대해 자세히 알아보기 위해 웹페이지의 빌드과정을 확인해보겠습니다.

 

 

페이지 빌드 과정(Critical Rendering Path, CRP)

브라우저가 서버에서 페이지에 대한 HTML 응답을 받고 화면에 표시하기까지 여러 단계를 거칩니다.

다음은 브라우저가 HTML 문서를 읽고, 스타일을 입히고, 뷰포트에 표시하는 과정을 도식화한 이미지입니다.

순서대로 정리하면 다음과 같습니다.

  1. 문서를 읽어들여 이를 파싱하고 어떤 내용을 페이지에 렌더링할 결정합니다. 과정의 결과물로 DOM CSSOM 산출합니다.
  2. 브라우저 엔진을 통해 DOM CSSOM 결합합니다. 프로세스는 화면에 보이는 콘텐츠의 내용과 스타일 정보를 모두 포함하는 최종 렌더링 트리 출력합니다. 
  3. 브라우저가 페이지에 표시되는 요소의 크기와 위치를 계산합니다. 
  4. 페인트 단계에 도달하면 브라우저는 레이아웃 결과를 선택하고 픽셀을 화면에 페인트합니다. 

 

 

결국 DOM이란 ?

DOM이란 HTML 요소들의 구조화된 표현입니다. 

DOM은 HTML이 브라우저의 렌더링 엔진에 의해 분석된 결과물입니다. HTML은 화면에 보이고자 하는 모양과 구조를 문서로 만들어서 단순 텍스트로 구성되어 있습니다. DOM은 HTML문서의 내용과 구조가 객체 모델로 변화되어 다양한 프로그램에서 사용될 수 있습니다. 

 

HTML문서가 유효하지 않게 작성되었을 때는 브라우저가 올바르게 교정을 해주며, DOM은 자바스크립트에 의해 수정될 수 있습니다. 

하지만 HTML 수정되지 않습니다. 

 

 

Jest 란?

Jest Facebook( Meta) 의해 만들어진 테스팅 프레임워크입니다. 최소한의 설정으로 동작하며 Test Case 만들어 어플리케이션 코드가 돌아가는지 확인해줍니다. 주로 단위(Unit) 테스트 위해 사용합니다. 

 

 

Jest가 Test파일을 찾는 방법

Jest Test 파일을 찾는 방법을 도식화하면 다음과 같습니다. 

  1. 파일 확장자를 .test.js 설정하면 Jest 테스트 파일임을 자동으로 인식합니다. 
  2. 파일 확장자를 .spec.js 설정하면 Jest 테스트 파일임을 자동으로 인식합니다.
  3. tests 폴더 있는 모든 파일은 Jest 테스트 파일로 인식합니다.

 

 

Jest 파일 구조와 사용법

Jest에서의 테스트 파일 구조는 다음과 같습니다.

각각의 용어를 설명하면 다음과 같습니다.

  1.  describe(name, fn): 여러 관련 테스트를 그룹화하는 블록을 만듭니다.
  2.  test(name, fn, timeout): 개별 테스트를 수행하는 곳으로, 각 테스트를 작은 문장처럼 설명합니다.

다음은 간단한 test에 대한 예시 코드입니다.

test(‘two plus two is four’, () => {
  expect(2+2).toBe(4);
});

 

위의 코드에서 test에 expectmatcher가 사용된 것을 확인할 수 있습니다. 

 

expect는 작성한 함수가 주어진 입력값에 대해서 리턴하는 값이 기대하는 값과 같은지를 비교할 때 사용합니다. 즉, 테스트하는 값과 기대값을 비교하기 위해 expect 함수를 사용합니다.

expect 함수 혼자서는 거의 사용되지 않으면 matcher와 함께 사용됩니다. 이 때 matcher는 '기대조건'에 해당하는 함수를 의미합니다.

 

 

React Testing Library의 주요 API

CRA 통해 프로젝트를 생성하면 src폴더에 App.test.js파일이 생성되는 것을 확인할 있습니다. 해당 파일에서 기본 테스트가 진행됩니다. 다음은 CRA 직후 App.test.js 파일을 구성하는 코드입니다.

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

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

render 함수는 DOM에 컴포넌트를 렌더링하는 함수입니다. 인자로 렌더링할 React 컴포넌트가 들어갑니다. 

render 함수는 @testing-library/react 모듈로 부터 바로 import 가능하며, 인자로 랜더링할 React 컴포넌트를 넘깁니다. 그리고 render 함수는 React Testing Library 제공하는 모든 쿼리 함수와 기타 유틸리티 함수 담고 있는 객체를 리턴합니다. 따라서 자바스크립트의 객체 Destructuring 문법으로 render 함수가 리턴한 객체로 부터 원하는 쿼리 함수만 얻어올 있습니다.

Destructuring 문법으로 원하는 쿼리 함수만 얻어올 있지만 프로젝트의 규모가 커짐에따라 소스 코드가 복잡해질 있습니다. 따라서 위와 같이 screen 객체를 사용하는 방식을 권장하고 있습니다. 

 

 

쿼리함수란 ?

쿼리는 페이지에서 요소를 찾기 위해 테스트 라이브러리가 제공하는 방법입니다. get, find, query 같은 다양한 유형의 쿼리가 있습니다. 

 

 

get, find, query의 차이점

React Testing Library 공식문서에서 제공하는 쿼리함수는 다음과 같습니다. 

정리하면 다음과 같습니다. 

1. getBy

쿼리에 대해 일치하는 노드를 반환하고 일치하는 요소가 없거나 둘 이상의 일치가 발견되면 오류를 발생시킵니다. 

둘 이상의 요소가 예상되는 경우엔 getAllBy를 사용합니다. 

 

2. queryBy

쿼리에 대해 일치하는 노드를 반환하고 일치하는 요소가 없으면 null을 반환합니다. 이는 존재하지 않는 요소를 assertion하는데 유용합니다.

둘 이상의 일치 항목이 발견되면 오류가 발생합니다. 

둘 이상의 요소가 예상되는 경우엔 queryAllBy를 사용합니다.

 

3. findBy

주어진 쿼리와 일치하는 요소가 발견되면 Promise를 반환합니다. 요소가 발견되지 않거나 기본 제한 시간인 1000ms후에 둘 이상의 요소가 발견되면 promise가 거부됩니다. 

둘 이상의 요소가 예상되는 경우엔 findAllBy를 사용합니다.

 

getBy와 queryBy는 오류 반환 형태를 제외하곤 상당 부분 일치하는 모습을 확인할 수 있습니다.

findBy getBy waitFor 사용하는 것과 같은 효과를 냅니다. waitFor 일정 시간동안 기다려야 사용되며 expect 통과할 까지 기다리기 위해 사용됩니다.

 

 

테스트 주도 개발(Test Driven Development)

테스트 주도 개발(Test Driven Development)에선 실제 코드를 작성하기 전에 테스트 코드를 먼저 작성합니다. 테스트 코드를 작성한 테스트 코드를 pass 있는 실제 코드를 작성합니다. 다음의 이미지는 TDD 프로세스를 설명하고 있습니다.

위의 그림을 통해 알 수 있는 TDD 프로세스를 정리하면 다음과 같습니다.

  1. 원하고자 하는 기능의 테스트 코드를 작성합니다. 이 때 테스트 코드는 fail이 발생할 때 까지 작성합니다.
  2. 테스트 코드에 맞는 실제 코드를 작성합니다. 결과는 실제로 테스트 코드를 동작시키며 확인하며 테스트 코드가 pass할 때 까지 진행합니다.
  3. 실제로 Test가 통과되면 코드의 동작 방식을 해치지 않는 선에서 리팩토링을 진행합니다.

위의 1~3 과정을 반복하여 진행하는 것을 테스트 주도 개발이라고 합니다. 

 

 

TDD의 장점 

그렇다면 테스트 주도 개발을 진행할 경우 얻게되는 이점은 무엇이 있을까요? 

다음은 TDD의 장점을 정리한 내용입니다.

  1. 실제로 많은 기능을 테스트하기에 소스 코드에 안정감이 부여됩니다.
  2. 실제 개발을 진행하면서 가장 많은 시간이 소요되는 부분은 디버깅 부분입니다. TDD를 사용하면 디버깅 시간이 줄어들고 실제 개발 시간도 줄어듭니다.
  3. 테스트를 통과하기 위해 소스 코드 하나하나를 더욱 신중하게 짤 수 있기 때문에 깨끗한 코드가 나올 확률이 높아집니다.

외에도 TDD 장점은 많이 있습니다. 하지만 테스트 코드를 작성하며 향후 작성할 코드의 동작 방식에 대해 이해할 있고, 이를 바탕으로 코드를 작성한다 점이 TDD 가지는 가장 장점이라고 생각됩니다. 

 

 

마치며

지금까지 React Test 진행할  알아야  React Testing Library Jest, 그리고 TDD 방법론에 대해 알아보았습니다.

 

저 또한 테스트에 대한 경험이 부족하기 때문에 React에 대한 개념이 있으신 분들이면 편하게 볼 수 있도록 포스팅을 구성했습니다.

다음 포스팅은 이번 포스팅에서 배운 개념을 바탕으로 TDD 방식을 이용하여 간단한 Counter App을 구현할 예정입니다. 최근 TDD에 대한 중요성이 강조되고 있기 때문에 평소보다는 느린 템포로 내용을 전달해드릴 예정입니다. 

 

React Test 시리즈는 총 6~7개의 포스팅을 계획하고 있습니다. React Test 시리즈가 마무리 되는대로 Jest에 대한 본격적인 포스팅과 함께 실제 TDD 방법론을 적용한 프로젝트 회고를 포스팅할 예정입니다. 

 

 

참고

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8 

 

따라하며 배우는 리액트 테스트 [2023.11 업데이트] - 인프런 | 강의

이 강의를 통해 리액트 애플리케이션을 테스트하는 법을 배우게 됩니다., 리액트 테스팅을 위한 A to Z! 한 단계 앞선 리액트 개발자로 거듭나세요. 리액트 애플리케이션 테스트, 왜 배워야 할까

www.inflearn.com