장경은
Study

[8장] 좋은 리액트 코드 작성을 위한 환경 구축하기

ESLint를 활용한 정적 코드 분석

  • 자바스크립트 생태계에서 가장 많이 사용되는 정적 코드 분석 도구는 바로 ESLint
  • ESLint는 기본값으로 espree를 사용한다.
  • eslint-plugin이라는 접두사로 시작하는 플러그인은 앞서 언급했던 규칙을 모아놓은 패키지다.
  • eslint-config는 이러한 eslint-plugin을 한데 묶어서 완벽하게 한 세트로 제공하는 패키지라 할 수 있다.
  • new Date()는 기기에 의존적이다. 이를 해결하려면 서버의 시간에 의존하도록 해야한다.
  • Prettier와 ESLint가 충돌할 때 해결할 수 있는 방법 중 하나는 자바스크립트나 타입스크립트는 ESLint에, 그 외의 파일은 모두 Prettier에게 맡기는 것이다.

규칙에 대한 예외 처리

  • 리액트 개발자라면 이러한 규칙을 가장 많이 사용하는 곳 중 하나가 바로 // eslint-disable-line no-exhaustive-deps일 것이다.
  • 그러나 일반적으로 리액트 개발자들은 개발 시 이 의존성 배열이 너무 길어지거나, 혹은 빈 배열을 넣어서 컴포넌트가 마운트되는 시점에 한 번만 강제로 실행되게 하고 싶을 때, 혹은 임의로 판단해 없어도 괜찮다고 생각될 때 등에 사용한다.
  • 그러나 이것은 대부분의 경우에 위험한 발상이며, 잠재적인 버그를 야기할 수 있다.

정리

  • ESLint를 잘 쓰고 있는 개발자라고 하더라도 꼭 프로젝트에 설치돼 있는 eslint-config는 무엇인지, 왜 이것을 문제가 있는 코드로 간주하는지 반드시 한 번씩 살펴보길 바란다.

리액트 팀이 권장하는 리액트 테스트 라이브러리

  • 테스트를 통해 개발자들은 처음에 설계한 대로 프로그램이 작동하는지 확인할 수 있고, 버그를 사전에 방지할 수도 있으며, 이후에 잘못된 작동으로 인해 발생하는 비용을 줄일 수도 있다.
  • React Testing Library(이하 리액트 테스팅 라이브러리)란 DOM Testing Library를 기반으로 만들어진 테스팅 라이브러리로, 리액트를 기반으로 한 테스트를 수행하기 위해 만들어졌다.
  • jsdom을 사용하면 마치 HTML이 있는 것처럼 DOM을 불러오고 조작할 수 있다. 리액트 테스팅 라이브러리는 jsdom을 기반으로 하는 DOM Testing Library이다.
  • Node.js는 assert?26??라는 모듈을 기본적으로 제공하며, 이 모듈을 사용하면 위와 같이 작동하도록 만들 수 있다.

RTL의 개념들

beforeEach

  • 각 테스트(it)를 수행하기 전에 실행하는 함수다.
  • 여기서는 각 테스트를 실행하기에 앞서 Static Component를 렌더링한다.

describe

  • 비슷한 속성을 가진 테스트를 하나의 그룹으로 묶는 역할을 한다.
  • 정의에서도 알 수 있듯, 이 describe는 꼭 필요한 메서드는 아니다. 그러나 테스트 코드가 많아지고 관리가 어려워진다면 describe로 묶어서 관리하는 것이 편리하다.
  • describe 내부에 describe를 또 사용할 수 있다.

it

  • test와 완전히 동일하며, test의 축약어(alias)다.
  • it이라는 축약어를 제공하는 이유는 테스트 코드를 좀 더 사람이 읽기 쉽게 하기 위해서다
  • describe ... it (something)과 같은 형태로 작성해 두면 테스트 코드가 한결 더 문어체 같이 표현되어 읽기 쉬워진다.

testId

  • testId는 리액트 테스팅 라이브러리의 예약어로, get 등의 선택자로 선택하기 어렵거나 곤란한 요소를 선택하기 위해 사용할 수 있다.

  • HTML의 DOM 요소에 testId 데이터셋을 선언해 두면 이후 테스트 시에 getByTestId, findByTestId 등으로 선택할 수 있다.

  • 웹에서 사용하는 querySelector([data-testid="${yourId}"])와 동일한 역할을 한다.

  • dataset이 무엇인가요?

    • 데이터셋이란 HTML의 특정 요소와 관련된 임의 정보를 추가할 수 있는 HTML 속성이다.
    • HTML의 특정 요소에 data-로 시작하는 속성은 무엇이든 사용할 수 있다. 앞의 예제에서는 HTML에 data-testid를 추가해 getByTestId를 사용했다.
    • 이는 특정 시나리오에서 매우 유용하게 사용할 수 있다.

userEvent.type

  • userEvent.type은 사용자가 타이핑하는 것을 흉내 내는 메서드다.
  • userEvent.type을 사용하면 사용자가 키보드로 타이핑하는 것과 동일한 작동을 만들 수 있다.
  • userEvent는 @testing-library/react에서 제공하는 fireEvent와 차이가 있다. 기본적으로 userEvent는 fireEvent의 여러 이벤트를 순차적으로 실행해 좀 더 자세하게 사용자의 작동을 흉내 낸다.

jest.spyOn

  • Jest가 제공하는 spyOn은 어떠한 특정 메서드를 오염시키지 않고 실행이 됐는지, 또 어떤 인수로 실행됐는지 등 실행과 관련된 정보만 얻고 싶을 때 사용한다.
  • jest.spyOn으로 calc 객체의 add 메서드를 관찰하는 것을 볼 수 있다. spyOn으로 관찰한 덕분에 한 번 호출됐는지(toBeCalledTimes(1)), 원하는 인수와 함께 호출됐는지(toBeCalledWith(1, 2))를 확인할 수 있다.
  • 그리고 spyOn으로 관찰은 했지만 calc.add의 작동 자체에는 영향을 미치지 않은 것을 확인할 수 있다.

mockImplementation

  • 해당 메서드에 대한 모킹 구현을 도와준다.

MSW

  • MSW는 Node.js나 브라우저에서 모두 사용할 수 있는 모킹 라이브러리로, 브라우저에서는 서비스 워커를 활용해 실제 네트워크 요청을 가로채는 방식으로 모킹을 구현한다.
  • 그리고 Node.js 환경에서는 https나 XMLHttpRequest의 요청을 가로채는 방식으로 작동한다.
  • 즉, Node.js나 브라우저에서는 fetch 요청을 하는 것과 동일하게 네트워크 요청을 수행하고, 이 요청을 중간에 MSW가 감지하고 미리 준비한 모킹 데이터를 제공하는 방식이다.

사용자 정의 훅 테스트하기

  • react-hooks-testing-library를 활용하면 훅을 더욱 편리하게 테스트할 수 있다.
  • 프로젝트가 리액트 18 버전 미만을 사용한다면 @testing-library/react 대신 @testing-library/react-hooks를 사용해야 한다. 리액트 18부터는 @testing-library/react에 통합됐다.
  • 같은 컴포넌트에서 훅을 두 번 호출하려면 renderHook이 반환하는 객체의 값 중 하나인 rerender 함수를 사용해야 한다.
  • rerender 외에도 unmount라는 함수를 반환하는데, 이름 그대로 이 함수를 실행하면 컴포넌트를 언마운트한다.

정리

  • 테스트 커버리지는 단순히 얼마나 많은 코드가 테스트되고 있는지를 나타내는 지표일 뿐, 테스트가 잘되고 있는지를 나타내는 것은 아니다. 그러므로 절대 테스트 커버리지를 맹신해서는 안 된다.
  • TDD(Test Driven Development; 테스트 주도 개발)라고 하는 개발 방법론을 차용해서 테스트를 우선시하더라도 서버 코드와는 다르게 프런트엔드 코드는 사용자의 입력이 매우 자유롭기 때문에 이러한 모든 상황을 커버해 테스트를 작성하기란 불가능하다.
  • 실무에서는 테스트 코드를 작성하고 운영할 만큼 여유로운 상황이 별로 없다. 때로는 테스트를 QA(Quality Assurance)에 의존해 개발을 빠르게 진행해야 할 수도 있고, 이후에 또 개발해야 할 기능이 산적해 있을 수도 있다.
  • 테스트 코드를 작성하기 전에 생각해 봐야 할 최우선 과제는 애플리케이션에서 가장 취약하거나 중요한 부분을 파악하는 것이다.
  • 테스트 코드는 개발자가 단순 코드 작성만으로는 쉽게 이룰 수 없는 목표인 소프트웨어 품질에 대한 확신을 얻기 위해 작성하는 것이다.
  • 프런트엔드는 무작위 사용자가 애플리케이션에서 갖가지 작업을 할 수 있으므로 이를 테스트하기 위한 여러 가지 방법이 있다.
    • 유닛 테스트(Unit Test): 각각의 코드나 컴포넌트가 독립적으로 분리된 환경에서 의도된 대로 정확히 작동하는지 검증하는 테스트
    • 통합 테스트(Integration Test): 유닛 테스트를 통과한 여러 컴포넌트가 묶여서 하나의 기능으로 정상적으로 작동하는지 확인하는 테스트
    • 엔드 투 엔드(End to End Test): 흔히 E2E 테스트라 하며, 실제 사용자처럼 작동하는 로봇을 활용해 애플리케이션의 전체적인 기능을 확인하는 테스트
  • 리액트 테스팅 라이브러리는 유닛 테스트 내지는 통합 테스트를 도와주는 도구이며, E2E 테스트를 수행하려면 Cypress 같은 다른 라이브러리의 힘을 빌려야 한다.
  • 테스트할 수 있는 방법은 여러 가지가 있지만 테스트가 이뤄야 할 목표는 애플리케이션이 비즈니스 요구사항을 충족하는지 확인하는 것 한 가지뿐이다.