💻 Frontend

테스트 코드 문법 공부하기

category
💻 Frontend

테스트 코드 문법

최근에 과제를 하면서 단위 테스트를 작성해보는 시간을 가졌다. 처음부터 끝까지 테스트 코드를 작성하는 건 처음이라 이번 포스팅을 통해 테스트 코드 문법에 대해 정리해보려고 한다!
 
테스트 코드의 개념에 대해서는 이전 블로그를 참고해주세요.

Jest

Jest는 기본적으로 *.test.* 의 네이밍을 가진 파일을 테스트 파일로 인식하며, 해당 파일 안에 있는 코드를 실행합니다.
일반적으로 소프트웨어를 테스트 하는 과정을 생각하면
  1. 특정한 동작을 수행한다.
  1. 동작을 수행한 결과가 기대한 상황과 일치하는지 판단한다.
다음과 같은 과정을 거치는데요, 테스트 코드도 이와 같은 과정으로 작성합니다.
 
Jest에서 2번 과정처럼 기대한 상황과 일치하는지 판단하는 함수들을 matchers라고 표현합니다.
  1. 특정한 동작을 수행한다.
  1. matcher 함수를 통해 실제 결과와 기대값이 맞는지 검증한다.
이때 1번 과정처럼 특정 동작을 수행하기 위해서는 test() 또는 it() 함수를 활용합니다.
 
기본적으로 테스트 코드는 아래와 같은 구조를 가집니다.
test("2+2는 4다.", () => { expect(2 + 2).toBe(4); });
testit 함수의 첫번째 인자는 테스트 케이스에 대해 설명하는 것입니다. 두번째 인자는 콜백함수로 어떠한 것을 테스트하는지 로직을 담은 코드입니다.
expect는 테스트에서 어떠한 것을 검증할 것인지 검사하는 역할입니다. 하나의 콜백 안에 여러 expect를 수행할 수 있으며, 하나라도 기대 값과 일치하지 않을 경우 해당 테스트는 실패한 것으로 간주됩니다.
 

matcher

Jest에서 주로 사용되는 matcher는 아래와 같습니다.
  • toBe: expect의 인자가 toBe의 인자와 일치하는지 검사합니다.
  • toEqual: 객체가 동일한지 판단 해줍니다.
  • toBeNull, toBeUndefined
  • toContain: 특정한 요소를 포함하고 있는지 검사합니다.
  • not: matcher의 기대값을 반대로 변경해줍니다.
    • ex. not.toBe(4) → 4가 아니길 기대한다.
 

beforeEach, beforeAll, afterEach, afterAll

Jest에서 테스트 전/후 처리하는 문법을 통해 여러 테스트 케이스의 중복 코드를 줄일 수 있습니다.
  • before 키워드: 테스트 전 작업 수행
  • after 키워드: 테스트 후 작업 수행
 
  • beforeEach, afterEach
    • 테스트 마다 반복 실행이 필요한 작업
    • 동일 레벨 또는 하위 레벨의 테스트가 실행 될 때 마다 반복적으로 실행
  • beforeAll, afterAll
    • 딱 한번 실행이 필요한 작업
    • 동일 레벨 또는 하위 레벨의 테스트가 실행 될 때 딱 한번만 실행
beforeAll(() => { return initializeCityDatabase(); }); afterAll(() => { clearCityDatabase(); });
 
더 자세한 내용은 아래 블로그 참고 바랍니다.
 

RTL

Jest 만으로도 코드를 테스트할 수 있지만, React는 UI 라이브러리이기 때문에 순수 Jest 만드로 테스트 하기엔 어려움이 있습니다. 따라서 UI를 렌더링하는 부분과 동작하는 부분은 라이브러리를 통해 테스트를 수행해야 합니다.
많이 사용되는 라이브러리로는 Enzyme와 React-Testing-Library(RTL)이 있습니다.
Enzyme는 “구현"을 테스트하는 것에 초점이 맞춰져 있는 라이브러리이며, RTL은 “결과"를 테스트하는 것에 초점이 맞춰진 라이브러리입니다. 이 중 어떤 것을 사용해야한다는 정답은 존재하지 않으며, 테스트하고자 하는 목적에 따라서 두개를 적절하게 선택해서 사용하기도 합니다.
 
RTL에서 테스트 과정은 다음과 같습니다.
  1. 테스트할 화면을 렌더링 한다.
  1. 특정한 동작을 수행한다. (클릭 같은 이벤트 등)
  1. 동작을 수행한 결과가 기대한 상황과 일치하는지 판단한다.
 

render

1번 과정에서 테스트 할 화면을 렌더링하기 위해 render라는 주요 api가 있습니다.
예를 들어 <header /> 를 렌더링 하고 싶으면 아래와 같이 작성하면 됩니다.
import {render} from '@testing-library/react' render(<header />)
 

getBy, findBy, queryBy

2번 과정에서 특정한 동작을 수행하기 위해 요소를 가져오는 메서드들이 있습니다.
JS에서 DOM에 접근할 때 getElemtBy~~~ , querySelector 과 같이 렌더링 된 요소에게 접근할 수 있는 것과 같이 RTL에서는 크게 3가지 종류로 구분됩니다.
  • getBy~~~ : 해당 요소가 현재 DOM상에 있는지 동기적으로 확인합니다. 만약 찾는 요소가 없을 경우 예외를 던집니다.
  • findBy~~~ : 해당 요소가 현재 DOM상에 있는지 비동기적으로 확인합니다. 해당 요소를 찾기 위해 일정 시간을 기다리며, 시간이 지난 후에도 찾을 수 없는 경우 예외를 던집니다.
  • queryBy~~~ : getBy와 동일하게 동작하지만 찾는 요소가 없을 경우 예외를 던지는 것이 아닌 null을 반환합니다.
예시
  • getByRole
  • getByText
  • getByLabelText
  • getByPlaceholderText
  • getByDisplayValue
  • getByAltText
  • getByTitle
  • getByTestId
const buttonElement = screen.getByText(/퀴즈 풀러 가기/i);
 
더 자세한 설명은 해당 공식문서를 참고 바랍니다.
 

fireEvent, userEvent

또한 2번 과정에서 특정한 동작을 수행하기 위해 이벤트를 발생시키는 메소드가 있습니다.
fireEvent는 React 컴포넌트를 테스트할 때 사용자 동작을 모의하고 특정 이벤트를 발생시켜 컴포넌트의 상태 변화 및 렌더링을 확인하는 데 유용합니다.
userEvnet는 fireEvent와 마찬가지로 유저 상호작용을 컴포넌트에서 테스트하기 위해 제공되는 함수로 거의 똑같이 동작하지만, 실제 사용자가 수행하는 것과 보다 유사하게 이벤트를 발생시킨다고 합니다.
userEvent.click(buttonElement);
 
더 자세한 설명은 해당 블로그를 참고 바랍니다.
 

마무리

테스트 코드를 작성해야된다고 했을 때 막연하기만 했는데 막상 직접 작성해보니 감이 조금 잡혔다. 코드를 작성하면서 여러 matcher와 RTL에 대해 자세히 알게되었다.
여러 요소를 담으려고 하다보니 블로그 글로 대체한 부분이 많았다. 다음 포스팅은 모킹에 대해 작성해보고싶다.

References