8장 좋은 리액트 코드 작성을 위한 환경 구축하기
React Deep Dive ·8.2 리액트 팀이 권장하는 리액트 테스트 라이브러리
- 테스트란 개발자가 만든 프로그램이 코딩을 한 의도대로 작동하는지 확인하는 일련의 작업을 의미한다. 테스트를 통해 개발자들은 처음에 설계한 대로 프로그램이 작동하는지 확인할 수 있고, 버그를 사전에 방지할 수 도 있으며, 이후에 잘못된 작동으로 인해 발생하는 비용을 줄일 수도 있다. 그리고 이러한 일련의 테스트를 거친 프로그램은 사용자에게 버그가 최소화된 안정적인 서비스를 제공할 수 있는 원동력이 된다.
- 프런트엔드와 백엔드 모두 테스팅이 중요하지만 테스트하는 방법과 방법론은 사뭇 다르다. 백엔드의 테스트는 일반적으로 서버나 데이터베이스에서 원하는 데이터를 올바르게 가져올 수 있는지, 데이터 수정 간 교착 상태나 경쟁 상태가 발생하지는 않는지, 데이터 손실은 없는지, 특정 상황에서 장애가 발생하지 않는지 등을 확인하는 과정이 주를 이룬다. 이러한 백엔드 테스트는 일반적으로 화이트박스 테스트로, 작성한 코드가 의도대로 작동하는지 확인해야 하며, 이는 GUI가 아닌 AUI(Application User Interface; 응용 프로그램 사용자 인터페이스)에서 주로 수행해야 하기 때문에 어느 정도 백엔드에 대한 이해가 있는 사람만 가능하다.
- 반면 프런트엔드는 일반적인 사용자와 동일하거나 유사한 환경에서 수행된다. 사용자가 프로그램에서 수행할 주요 비즈니스 로직이나 모든 경우의 수를 고려해야 하며, 이 과정에서 사용자는 굳이 프런트엔드 코드를 알 필요는 없다. 즉, 블랙박스 형태로 테스트가 이뤄지며, 코드가 어떻게 됐든 상관없이 의도한 대로 작동하는지를 확인하는 데 좀 더 초점이 맞춰져 있다. 그리고 시나리오가 어느 정도 정해져 있는 백엔드와는 다르게, 프런트엔드는 사용자에게 완전히 노출된 영역이므로 어떻게 작동할지 최대한 예측해서 확인해야 한다. 사용자는 개발자의 의도대로만 사용하지 않기 때문이다.
- 프런트엔드 개발은 HTML, CSS와 같이 디자인 요소뿐만 아니라 사용자의 인터랙션, 의도치 않은 작동 등 브라우저에서 발생할 수 있는 다양한 시나리오를 고려해야 하기 때문에 일반적으로 테스팅하기가 매우 번거롭고 손이 많이 가는 작업이다. 그리고 이러한 특징 때문에 제공되는 테스팅 라이브러리도 상당히 다양한 편이다. 단순히 함수나 컴포넌트 수준에서 유닛 테스트를 할 수도 있고, 사용자가 하는 작동을 모두 흉내 내서 테스트할 수도 있다.
- 이번 절에서는 리액트로 개발된 애플리케이션을 테스팅하는 방법, 특히 가장 널리 사용되는 React Testing Library 위주로 살펴본다. 리액트 개발 환경에서 테스트를 수행하는 방법을 살펴보고, 어떤 도움을 얻을 수 있는지 알아보자.
8.2.1 React Testing Library란?
- React Testing Library(이하 리액트 테스팅 라이브러리)란 DOM Testing Librar를 기반으로 만들어진 테스팅 라이브러리로, 리액트를 기반으로 한 테스트를 수행하기 위해 만들어졌다. 리액트 테스팅 라이브러리를 이해하려면 먼저 리액트 테스팅 라이브러리가 기반으로 하는 DOM Testing Library에 대해 먼저 알아둬야 한다. DOM Testing Library는 jsdom을 기반으로 하고 있다. jsdom이란 순수하게 자바스크립트로 작성된 라이브러리로, HTML이 없는 자바스크립트만 존재하는 환경, 예를 들어 Node.js 같은 환경에서 HTML과 DOM을 사용할 수 있도록 해주는 라이브러리다. jsdom을 사용하면 자바스크립트환경에서도 HTML을 사용할 수 있으므로 이를 기반으로 DOM Testing Library에서 제공하는 API를 사용해 테스트를 수행할 수 있다. 다음 예제 코드를 보자.
const jsdom = require('jsdom')
const { JSDOM } = jsdom
const dom = new JSDOM('<!DOCTYPE html><p>Hello world</p>')
console.log(dom.window.docuinent.querySelector('p').textcontent) // "Hello world"
- 이처럼 jsdom을 사용하면 마치 HTML이 있는 것처럼 DOM을 불러오고 조작할 수 있다.
- jsdom을 사용해 자바스크립트 환경에서 HTML을 사용할 수 있는 DOM Testing Library를 기반으로, 동일한 원리로 리액트 기반 환경에서 리액트 컴포넌트를 테스팅할 수 있는 라이브러리가 바로 리액트 테스팅 라이브러리다. 리액트 테스팅 라이브러리를 활용하면 실제로 리액트 컴포넌트를 렌더링하지 않고도, 즉 브라우저를 직접 실행해 눈으로 확인하지 않아도 리액트 컴포넌트가 원하는 대로 렌더링되고 있는지 확인할 수 있다.
8.2.2 자바스크립트테스트의기초
- 본격적으로 리액트 테스트 코드를 작성하기에 앞서, 먼저 자바스크립트에서 테스트 코드는 어떻게 작성하는지에 대해 먼저 알아보자. 만약 인수 두 개의 합을 더하는 함수를 만들었다고 가정해 보자.
function sum(a, b) {
return a + b
}
- 이 함수에 대한 테스트 코드를 작성한다면 어떻게 작성해야 할까? 테스트 코드를 작성하기에 잎•서 테스트 코드가 무엇인지 상기하자. 테스트 코드란 내가 작성한 코드가 내가 코드를 작성했던 당시의 의도와 목적에 맞는지 확인하는 코드를 의미한다. 그런 의미에서 sum 함수에 대해서는 다음과 같은 테스트 코드를 작성해 볼 수 있을 것이다.
// 테스트 1
// 함수를 실행했을 때의 실제 결과
let actual = suin(l, 2)
// 함수를 실행했을 때 기대하는 결과
let expected = 3
if (expected != actual) {
throw new Error('${expected} is not equal to ${actual}')
}
// 테스트 2
actual = sum(2, 2)
expected = 4
if (expected !== actual) {
throw new Error('${expected} is not equal to ${actual}')
}
-
테스트 코드를 작성하는 방식, 테스트 코드에 사용되는 인수 등 코드의 세세한 부분에는 조금씩 차이가 있겠지만 기본적인 테스트 코드를 작성하는 방식은 다음과 같은 과정을 거친다는 점에서 비슷할 것이다.
- 1) 테스트할 함수나 모듈을 선정한다.
- 2) 함수나 모듈이 반환하길기대하는 값을 적는다.
- 3) 함수나 모듈의실제반환 값을 적는다.
- 4) 3번의 기대에 따라 2 번의 결과가 일치하는지 확인한다.
- 5) 기대하는 결과를 반환한다면 테스트는 성공이며. 만약 기대와 다른 결과를 반환하면 에러를 던친다.
-
이를 위해 가장 먼저 필요한 것이 “작성한 코드가 예상대로 작동한다면 성공했다는 메시지가 출력되고, 실패하면 에러를 던진다”라는 작동을 대신해 주는 라이브러리다. 다행히도 Node.js는 assert라는 모듈을 기본적으로 제공하며, 이 모듈을 사용하면 위와 같이 작동하도록 만들 수 있다.
const assert = require('assert')
function suni(a, b) {
return a + b
}
assert.equal(sum(l, 2), 3)
assert.equal(sum(2, 2), 4)
assert.equal(sum(l, 2), 4) // AssertionError [ERR_ASSERTION] [ERR_ASSERTION]: 3
- 일반적으로 테스트 코드와 실제 코드는 분리해 작성한다. 테스트 결과를 확인할 수 있도록 도와주는 라이브러리를 어설션(assertion) 라이브러리라고 한다. 이러한 어설션 라이브러리에는 Node.js가 제공하는 assert 외에도 should, js, expect, js, chai 등 다양하다. 테스트 코드를 작성하는 개발자들은 이러한 어설션을 활용해 다양한 시나리오를 작성하고, 이 시나리오상에서 코드가 올바르게 작동하는지 확인할 수 있다.
- 그렇다면 이 어설션 라이브러리만 존재한다면 테스트 코드를 작성할 준비가 다 끝난 것일까? 그렇지 않다. 테스트 코드는 가능한 한 사람이 읽기 쉽게, 그리고 테스트의 목적이 분명하게 작성되는 것이 중요하다. 앞에서 직성한 sum을 테스트하는 assert 모듈과 assert.equal 함수만으로도 어느 정도 테스트 코드의 목적을 달성했다고 볼 수도 있다. 그러나 테스트 코드가 실행되는 것을 지켜보는 입장에서는 다르다. 예를 들어, 앞의 테스트 코드가 CI 환경에서 자동으로 실행되게 만들었다고 가정해 보자. 테스트 코드가 정상적으로 작동하고, 테스트도 모두 통과하겠지만 무엇을 테스트했는지, 무슨 테스트를 어떻게 수행했는지 등 테스트에 관한 실제 정보를 알 수는 없다. 즉, 좋은 테스트 코드는 다양한 테스트 코드가 작성되고 통과하는 것뿐만 아니라 어떤 테스트가 무엇을 테스트하는지 일목요연하게 보여주는 것도 중요하다.
- 이러한 테스트의 기승전결을 완성해 주는 것이 바로 테스팅 프레임워크다. 테스팅 프레임워크들은 어설션을 기반으로 테스트를 수행하며, 여기에 추가로 테스트 코드 작성자에게 도움이 될 만한 정보를 알려주는 역할도 함께 수행한다. 자바스크립트에서 유명한 테스팅 프레임워크로는 Jest, Mocha, Karma, Jasmine 등이 있다. 그리고 리액트 진영에서는 리액트와 마찬가지로 메타에서 작성한 오픈소스 라이브러리인 Jest”가 널리 쓰이고 있다. Jest의 경우 자체적으로 제작한 expect 패키지를 사용해 어설션을 수행한다.
- Node.js의 assert만 사용했을 때는 단순히 실패에 대해서만 단편적인 정보로 알 수 있었지만Jest를 비롯한 테스트 프레임워크를 사용하면 무엇을 테스트했는지. 소요된 시간은 어느 정도 인지, 무엇이 성공하고 실패했는지, 전체 결과는 어떤지에 대한 자세한 정보를 확인할 수 있다. 어설션 라이브러리를 내장한 테스트 프레임워크를 사용하면 테스트 코드를 작성하는 것뿐만 아니라 테스트에 대한 결과와 관련 정보를 일목요연하게 확인할 수 있다.
const { sum } = require('./math')
test('두 인수가 덧셈이 되어야 한다.', () => {
expect(sum(l, 2)).toBe(3)
})
test('두 인수가 덧셈이 되어야 한다.', () => {
expect(sum(2, 2)).toBe(3) // 에러
})
- 앞의 테스트 코드에서 볼 수 있는 특별한 점은 test, expect 등의 메서드를 import나 require 같은 모듈을 불러오기 위해 사용하는 구문 없이 바로 사용했다는 것, 그리고 node가 아닌 jest(nptii run test)로 실행했다는 것이다. 만약 해당 코드를 jest가 아닌 node로 바로 실행했다면 에러가 발생했을 것이다. 그 이유는 test와 expect 모두 Node.js 환경의 global, 즉 전역 스코프에 존재하지 않는 메서드이기 때문이다. 이러한 메서드가 실행될 수 있는 비밀은 Jest CLI에 있다. Jest를 비롯한 테스팅 프레임워크에는 이른바 글로벌(global) 이라 해서 실행 시에 전역 스코프에 기본적으로 넣어주는 값들이 있다. 그리고 Jest는 이 값을 실제 테스트 직전에 미리 전역 스코프에 넣어준다. 이렇게 하면 일일이 테스트에 관련한 정보를 임포트하지 않고도 사용할 수 있게 되는 것이다. 이는 간결하고 빠른 테스트 코드 작성에 도움을 준다. 이 전역 스코프에 삽입되는 값은 Jest 공식 문서에서 확인할 수 있다.
8.2.3 리액트컴포넌트테스트 코드 작성하기
- 자바스크립트에서 이뤄지는 테스트 코드에 대해 어느 정도 살펴봤으니 이제 본격적으로 리액트 컴포넌트를 테스트하는 방법을 알아보자. 기본적으로 리액트에서 컴포넌트 테스트는 다음과 같은 순서로 진행된다.
- 1) 컴포넌트를렌더링한다
- 2) 필요하다면컴포넌트에서특정액션을 수행한다.
- 3) 컴포넌트 렌더링과 2번의 액션을 통해 기대하는 결과와 실제 결과를 비교한다.
프로젝트 생성
- 테스트 코드를 작성하기에 앞서, 먼저 create-react-app으로 예제 프로젝트를 생성한다. create-react-app에는 이미 react-testing-library가 포함돼 있으므로 별도로 설치할 필요가 없다.
npx create-react-app react-test --template typescript
- 이렇게 생성된 프로젝트를 살펴보면 App.test.tsx 파일이 생성돼 있는 것을 확인할 수 있다.
import React from 'react'
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()
})
- 이 App.test.tsx가 테스트하는 App 컴포넌트는 다음과 같이 구성돼 있다.
import React from 'react'
import logo from './logo.svg'
import './App.css'
function App() {
return (
<div className="App">
<header className= "App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://react]s.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}
export default App
- 코드 내용을 종합하면 App.test.tsx가 App.tsx에서 테스트하는 내용은 다음과 같이 요약할 수 있다.
- 1)
을 렌더링한다. - 2) 렌더링하는 컴포넌트 내부에서 “learn react”라는 문자열을 가진 DOM 요소를 찾는다.
- 3) expect(linkElement).toBeInTheDocument()라는 어설션을 활용해 2번에서 찾은 요소가 document 내부에 있는지 확인한다.
- 1)
- 위와 같이 리액트 컴포넌트에서 테스트하는 일반적인 시나리오는 특정한 무언가를 지닌 HTML 요소가 있는지 여부다. 이를 확인하는 방법은 크게 3가지가 있다.
- getBy…: 인수의 조건에 맟는 요소를 반환하며. 해당 요소가 없거나 두 개 이상이면 에러를 발생시킨다. 복수 개를 찾고 싶다면 getAllBy.. .를 사용하면 된다.
- findBy…: getBy와 거의 유사하나 한 가지 큰 차이점은 Promise를 반환한다는 것이다. 즉, 비동기로 찾는다는 것을 의미하며. 기본값으로 1000ms의 타임아웃을 가지고 있다. 마찬가지로 두 개 이상이면 에러를 발생시키지만 복수 개를 찾고 싶다면 findAUBy.. .를 사용하면 된다. 이러한특징 때문에 findBy는 비동기 액션 이후에 요소를 찾을 때 사용한다.
- queryBy…: 인수의 조건에 맞는 요소를 반환하는 대신. 찾지 못한다면 null을 반환한다. getBy.. .와 findBy.. .는 찾지 못하면 에러를 발생시키기 때문에 찾지 못해도 에러를 발생시키지 않고 싶다면 queryBy., .를 사용하면 된다. 마찬가지로 복수 개를 찾았을 때는 에러를 발생시키며. 복수 개를 찾고 싶다면 queryAllBy.. .를 사용하면 된다.
-
그리고 컴포넌트를 테스트하는 파일은 App.tsx, App.test.tsx의 경우와 마찬가지로 같은 디렉터리상에 위치하는 것이 일반적이다. 이름 규칙인 *.test.{t s}jsx만 준수한다면 디렉터리 내부에서 명확하게 구별되고, 대부분의 프레임워크가 이러한 이름으로 된 파일은 번들링에서 제외하므로 유용하게 사용할 수 있다. - 테스트를 위해 사용할 수 있는 기본적인 메서드에 대해 알아봤으니 이제 본격적으로 컴포넌트를 테스트하는 방법을 살펴보자.
정적 컴포넌트
- 정적 컴포넌트, 즉 별도의 상태가 존재하지 않아 항상 같은 결과를 반환하는 컴포넌트를 테스트하는 방법은 크게 어렵지 않다. 테스트를 원하는 컴포넌트를 렌더링한 다음, 테스트를 원하는 요소를 찾아 원하는 테스트를 수행하면 된다. 먼저 다음과 같은 컴포넌트가 있다고 가정해 보자.
- beforeEach; 각 테스트(it)를 수행하기 전에 실행하는 함수다.
- describe: 비슷한 속성을 가진 테스트를 하나의 그룹으로 묶는 역할을 한다. 정의에서도 알 수 있듯, 이 describe는 꼭 필요한 메서드는 아니다. 그러나 테스트 코드가 많아지고 관리가 어려워진다면 describe로 묶어서 관리하는 것이 편리하다. describe 내부에 describe를 또 사용할 수 있다.
- it: test와 완전히 동일하며. test의 축약어(alias) 다. it이라는 축약어를 제공하는 이유는 테스트 코드를 좀 더 사람이 읽기 쉽게 하기 위해서다. describe … it (something)과 같은 형태로 작성해두면 테스트 코드가 한결 더 문어체 같이 표현되어 읽기 쉬워진다.
- testid: testid는 리액트 테스팅 라이브러리의 예약어로, get 등의 선택자로 선택하기 어렵거나 곤란한 요쇼를 선택하기 위해 사용할 수 있다. HTML의 DOM 요소에 testid 데이터셋을 선언해 두면 이후 테스트 시에 getByTestId, findByTestId 등으로 선택할 수 있다. 웹에서 사용하는 querySelector([data-testid=”${yourIdr’])와 동일한 역할을 한다.
- 요약하자면 각 테스트를 수행하기 전에 StaticComponent를 렌더링하고, describe로 연관된 테스트를 묶어서 it으로 it 함수 내부에 정의된 테스트를 수행하는 테스트 파일이라고 정의할 수 있다.
동적 컴포넌트
- 아무런 상태값이 없는 완전히 순수한 무상태(stateless) 컴포넌트는 테스트하기가 매우 간편하다. 하지만 상태값이 있는 컴포넌트, 예를 들어 usestate를 사용해 상태값을 관리하는 컴포넌트는 어떨까? 사용자의 액션에 따라 state 값이 변경된다면? 이러한 변경에 따라 컴포넌트가 다르게 렌더링돼야 한다면? 일반적으로 테스트해야 하는 컴포넌트는 정적인 경우보다 이렇게 동적인 경우가 훨씬 더 많을 것이다.
사용자가 usestate를 통해 입력을 변경하는 컴포넌트
- 리액트로 작성하는 컴포넌트 중 가장 흔히 볼 수 있는 것은 사용자의 입력을 usestate로 받아서 처리하는 컴포넌트일 것이다.
const setup =()=>{
const screen = render(<InputComponent />)
const input = screen.getByLabelText('input') as HTMLInputElement
const button = screen.getByText(/제출하기/i) as HTMLButtonElement
return {
input,
button.
...screen.
}
}
- setup 함수: setup 함수는 내부에서 컴포넌트를 렌더링하고, 또 테스트에 필요한 button과 input을 반환한다. 이 파일에서 수행하는 모든 테스트는 렌더링과 button, input을 필요로 하므로 이를 하나의 함수로 묶어 두었다.
- userEvent.type: userEvent.type은 사용자가 타이핑하는 것을 흉내 내는 메서드다. userEvent.type을 사용하면 사용자가 키보드루 타이핑하는 것과 동일한 작동을 만들 수 있다. 니serEvent는 @testing-library/react에서 제공하는 fireEvent와 차이가 있다. 기본적으로 userEvent는 fireEvent의 여러 이벤트를 순차적으로 실행해 좀 더 자세하게 사용자의 작동을 흉내 낸다. 예를 들어, userEvent.click을 수행하면 내부적으로 다음과 같은 fireEvent가 실행된다.
- fireEvent.mouseOver
- fireEvent.mouseMove
- fireEvent.mouseDown
- fireEvent.mouseUp
- fireEvent.click
- 요약하자면. 대부분의 이벤트를 테스트할 때는 fireEvent로 충분하고 훨씬 더 빠르다. 단, 특별히 사용자의 이벤트를 흉내 내야 할 때만 userEvent를 사용하면 된다.
- jest.spyOn: Jest가 제공하는 spyOn은 어떠한 특정 메서드를 오염시키지 않고 실행이 됐는지, 또 어떤 인수로 실행됐는지 등 실행과 관련된 정보만 얻고 싶을 때 사용한다. 여기서는 (window, ‘alert’)라는 인수와 함께 사용됐는데. 이는 window 객체의 메서드 alert를 구현하지 않고 해당 메서드가 실행됐는지만 관찰하겠다는 뜻이다.
const calc = {
add: (a, b) => a + b,
}
const spyFn = jest.spyOn(calc, 'add')
const result = calc.add(l, 2)
expect(spyFn).toBeCalledTimes(l)
expect(spyFn).toBeCalledWith(l, 2)
expect(result).toBe(3)
- 위 코드에서는 jest.spyOn으로 calc 객체의 add 메서드를 관찰하는 것을 볼 수 있다. spyOn으로 관찰한 덕분에 한번 호출됐는지(toBeCaUedTimes(l)), 원하는 인수와 함께 호출됐는지(toBeCalledWith(l, 2))를 확인할 수 있다. 그리고 spyOn으로 관찰은 했지만 calc.add의 작동 자체에는 영향을 미치지 않은 것을 확인할수 있다.
- mockimplementation: 해당 메서드에 대한 모킹(mocking) 구현을 도와준다. 현재 Jest를 실행하는 Node.js 환경에서는 window.alert가 존재하지 않으므로 해당 메서드를 모의 함수(mock)로 구현해야 하는데, 이것이 바로 mockimplementation의 역할이다.
8.2.5 테스트를 작성하기에앞서고려해야 할 점
- 소프트웨어의 테스트에 대해 논할 때 테스트 커버리지라고 해서 해당 소프트웨어가 얼마나 테스트됐는지를 나타내는 지표에 대해 들어본 적이 있을 것이다. 흔히들 알고 있는 사실 중 하나는 테스트 커버리지가 높을수록 좋고 꾸준히 테스트 코드를 작성하라는 것이다. 그러나 테스트 커버리지가 만능은 아니다. 먼저 테스트 커버리지는 단순히 얼마나 많은 코드가 테스트되고 있는지를 나타내는 지표일 뿐, 테스트가 잘되고 있는지를 나타내는 것은 아니다. 그러므로 절대 테스트 커버리지를 맹신해서는 안 된다.
- 또 한 가지 알아둬야 할 점은 테스트 커버리지를 100%까지 끌어올릴 수 있는 상황은 생각보다 드물다는 것이다. 이른바 TDD(Test Driven Development: 테스트 주도 개발)라고 하는 개발 방법론을 차용해서 테스트를 우선시하더라도 서버 코드와는 다르게 프런트엔드 코드는 사용자의 입력이 매우 자유롭기 때문에 이러한 모든 상황을 커버해 테스트를 작성하기란 불가능하다. 그리고 실무에서는 테스트 코드를 작성하고 운영할만큼 여유로운 상황이 별로 없다. 때로는 테스트를 QA(Quality Assurance) 에 의존해 개발을 빠르게 진행해야 할 수도 있고, 이후에 또 개발해야 할 기능이 산적해 있을 수도 있다.
- 따라서 테스트 코드를 작성하기 전에 생각해 봐야 할 최우선 과제는 애플리케이션에서 가장 취약하거나 중요한 부분을 파악하는 것이다. 예를 들어, 전자상거래 애플리케이션을 만든다고 가정해 보자. 애플리케이션의 모든 부분이 우선순위를 가릴 수 없이 중요하겠지만 가장 신경 써야 할 것은 바로 결제일 것이다. 결제에 대해 테스트하기로 마음먹었다면 해당 결제와 연관되어 테스트가 필요한 코드를 파악해야 할 것이다. 그리고 해당 코드들이 어떻게 순차적으로 실행되는지 확인한 다음, 그 순차적인 프로세스에 맞춰 테스트 코드를 작성하면 된다.
- 이처럼 애플리케이션에서 가장 핵심이 되는 부분부터 먼저 테스트 코드를 하나씩 작성해 나가는 것이 중요하다. 테스트 코드는 소프트웨어의 코드를 100% 커버하기 위해, 혹은 테스트 코드가 모두 그린 사인(테스트가모두 통과했다)을 보기 위해 작성하는 것이 아니다. 테스트 코드는 개발자가 단순 코드 작성만으로는 쉽게 이룰 수 없는 목표인 소프트웨어 품질에 대한 확신을 얻기 위해 작성하는 것이다. 무작정 테스트 코드를 작성하기보다는 반드시 이 점을 명심하고 테스트 코드를 작성해야 한다.
8.2.6 그 밖에 해볼 만한 여러 가지 테스트
- 이번 절의 내용은 create-react-app과 함께 제공되는 리액트 테스팅 라이브러리를 위주로 작성됐지만 프런트엔드 개발에 있어 테스트를 수행할 수 있는 방법은 굉장히 다양하다. 사용자도 한정적이고, 사용할 수 있는 케이스도 어느 정도 제한적인 백엔드에 비해 프런트엔드는 무작위 사용자가 애플리케이션에서 갖가지 작업을 할 수 있으므로 이를 테스트하기 위한 여러 가지 방법이 있다.
- 유닛 테스트(Unit Test): 각각의 코드나 컴포넌트가 독립적으로 분리된 환경에서 의도된 대로 정확히 작동하는지 검증하는 테스트
- 통합 테스트(Integration Test): 유닛 테스트를 통과한 여러 컴포넌트가 묶여서 하나의 기능으로 정상적으로 작동하는지 확인하는 테스트
- 엔드 투 엔드(End to End Test): 흔히 E2E 테스트라 하며, 실제 사용자처럼 작동하는 로봇을 활용해 애플리케이션의 전체적인 기능을 확인하는 테스트
- 리액트 테스팅 라이브러리는 유닛 테스트 내지는 통합 테스트를 도와주는 도구이며, E2E 테스트를 수행하려면 Cypress 같은 다른 라이브러리의 힘을 빌려야 한다. 각 테스트 설명에서도 알 수 있지만 유닛 테스트에서 통합 테스트, 엔드 투 엔드 테스트.루 갈수록 테스트가 실패할 지점이 많아지고, 테스트 코드도 복잡해지며. 테스트해야 할 경우의 수도 많아지고, 테스트 자체를 구축하는 것도 어려워진다. 그러나 유닛 테스트에서 통합 테스트, 엔드 투 엔드 테스트로 갈수록 개발자에게 있어 코드에 대한 자신감을 심어줄 수 있는 가능성 또한 커진다.
8.2.7 정리
- 지금까지 create-react-app에서 함께 제공되는 리액트 애플리케이션에서 가장 쉽게 접할 수 있는 테스팅 라이브러리인 리액트 테스트 라이브러리에 대해 알아봤다. . 테스트할 수 있는 방법은 여러 가지가 있지만 테스트가 이뤄야 할 목표는 애플리케이션이 비즈니스 요구사항을 충족하는지 확인하는 것 한 가지뿐이다. 의존해야 할 QA 여건이 부족하거나, 애플리케이션의 취약한 부분이 걱정된다면 조금씩 테스트 코드를 추가해보자. 한 번에 E2E 테스팅 라이브러리를 설치해 사용자의 작동을 흉내 내서 완벽한 자신감을 얻는 것도 좋은 방법이 될 수 있지만, 처음부터 너무 많은 준비가 필요한 E2E 테스트를 시작하려다 보면 테스트 코드를 작성하기도 전에 지치거나 혹은 다른 급한 일에 테스트 코드작성의 우선순위가 밀려날지도 모른다. 조금씩, 그러나 핵심적인 부분부터 테스트 코드를 작성하다 보면 소프트웨어의 품질에 확신을 갖게 될 것이다.