본문 바로가기

Study

[모던 리액트 Deep Dive 스터디] 좋은 리액트 코드 작성을 위한 환경 구축하기

코드 작성 만큼 중요한 것은 무엇일까 ?

  • 좋은 코드를 작성할 수 있는 환경을 구축하는 것
  • 좋은 코드를 작성하기 위한 사전 작업 ⇒ ESLint, 테스트 라이브러리

8.1 ESLint를 활용한 정적 코드 분석

정적 코드 분석?

코드 자체만으로 잠재적 버그를 야기할 수 있는 문제를 찾아 사전에 수정하는 것을 의미

가장 많이 사용되는 정적 코드 분석 도구 ⇒ ESLint

ESLint는 어떻게 코드를 분석할까?

!https://tech.kakao.com/storage/2019-11-25__8.13.19-1024x300.png

  1. 자바스크립트 코드를 문자열로 읽음
  2. 자바스크립트 코드를 분석 할 수 있는 파서(parser)로 코드를 구조화
    1. ESLint에서 파서는 기본적으로 espree 사용
  3. 2번에서 구조화한 트리를 AST(Abstract Syntax Tree)라 하며, 이 구조화된 트리를 기준으로 각종 규칙과 대조함
  4. 규칙과 대조 시 위반한 코드를 알리거나(report) 수정(fix)

ESLint 코드 분석 예시

function hello(str) {}
{
  "type": "Program",
  "start": 0,
  "end": 22,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 22,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 14,
        "name": "hello"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 15,
          "end": 18,
          "name": "str"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 20,
        "end": 22,
        "body": []
      }
    }
  ],
  "sourceType": "module"
}

변수인지, 함수인지, 함수명이 무엇인지 뿐만 아닌 코드의 정확한 위치와 같은 세세한 정보도 담겨있다.

espree로 코드를 분석한 결과를 바탕으로 어떤 코드가 잘못된 코드이며 어떻게 수정해야 할지 정하는 것을 **ESLint 규칙(rules)**라고 한다. 또한, 특정 규칙의 모음을 plugins 라고 칭한다.

debugger 사용 금지 예시

{
  "type": "Program",
  "start": 0,
  "end": 9,
  "body": [
    {
      "type": "DebuggerStatement",
      "start": 0,
      "end": 9
    }
  ],
  "sourceType": "module"
}

type이 DebuggerStatement임을 확인할 수 있음.

module.export = {
	meta: {
		type: 'problem',
		docs: {
			description: 'Disallow the use of `debugger`,
			recommended: true,
			url: '<https://eslint.org/docs/rules/no-debugger>',
			},
		fixable: null,
		schema: [],
		messages: {
			unexpected: 'Unexpected 'debugger' statement.",
			}
		},
	},
	// 실제로 코드에서 문제점을 확인하는 곳
	create(context) {
		DebuggerStatement(node) {
			context.report({
				node,
				messageId: 'unexpected',
				})
			},
		}
	},
}
  • create 함수는 espree로 만들어진 AST 트리를 실제로 순회 한다.
  • 위 코드에서 선언한 특정 조건을 만족하는 코드를 찾고, 이 작업을 코드 전체에서 반복한다.
  • DebuggerStatement를 만나면 해당 노드를 리포트해 debugger를 사용했다는걸 알려준다.

ESLint를 설치하고 사용하려면 어떤것들을 준비해야 할까?

  • eslint-plugin
  • eslint-config

eslint-plugin

  • eslint-plugin 이라는 접두사로 시작하는 플러그인은 앞서 언급했던 규칙을 모아놓은 패키지
  • ex) eslint-plugin-import, eslint-plugin-react …

eslint-config

  • eslint-plugin이 특정 프레임워크나 도메인 관련 규칙 프레임워크라면, eslint-config는 이러한 eslint-plugin을 한데 묶어서 완벽하게 한 세트로 제공하는 패키지
  • 보통 개인이 직접 만드는 경우는 드물고 IT 기업들에서 공개한 잘 만들어진 eslint-config를 설치해서 사용
    • eslint-config-airbnb
      • 리액트 기반 프로젝트에서 쓰기 좋음
    • @titicaca/triple-config-kit
      • 자체적으로 정의한 규칙을 기반으로 운영됨
      • 외부로 제공하는 규칙에 대한 테스트 코드가 존재
      • CI/CD 환경, 카나리 배포 등 일반적인 npm 라이브러리 구축 및 관리를 위한 시스템이 잘 구축되어 있음
    • eslint-config-next
      • Next.js 프레임워크 사용 환경에서 사용 할 수 있음
      • 단순 JS 코드 정적분석 뿐만 아닌 JSX 구문 및 HTML 코드 또한 정적 분석 대상으로 분류해 제공
      • 웹 서비스 성능에 영향을 미치는 요소를 분석해 제공하는 기능도 포함

ESLint 사용 시 주의할 점

  1. Prettier와의 충돌
    • ESLint는 자바스크립트에서만 작동하지만 Prettier는 다양한 언어에도 적용 가능
    • ESLint에서도 Prettier에서 처리하는 작업(들여쓰기, 줄바꿈, 따옴표, 최대 글자 수) 등 처리 할 수 있기 때문에 두 가지 모두 자바스크립트 코드에서 실행하면 서로 충돌하는 규칙으로 에러 발생
    해결방법?
    • 서로 규칙이 충돌되지 않게 Prettier에서 제공하는 규칙은 ESLint에서 끄자.
    • 자바스크립트나 타입스크립트는 ESLint에 맡기고, 그 외 파일은 Prettier에 맡겨 물리적으로 분리하자.
  2. 규칙에 대한 예외 처리
    • eslint-disable- 주석 사용 시 특정 규칙을 임시로 무시 할 수 있음.
      • ex) 의존 배열이 필요한 훅에 의존성 배열을 제대로 선언했는지 확인하는 역할 비활성화 eslint-disable-line no-exhaustive-deps
    • eslint-disable를 많이 사용하고 있다면 이렇게 규칙을 무시하는 것이 옳은지, 규칙을 아예 제거하는게 옳은지 점검이 필요함.
  3. ESLint 버전 충돌
    • 두 개의 다른 ESLint 버전이 설치되어 에러가 발생
    해결방법?
    • ESLint를 peerDependencies로 설정해두길 권장

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

테스트

  • 개발자가 만든 프로그램이 코딩을 한 의도대로 작동하는지 확인하는 일련의 작업

DOM Testing Library

  • jsdom을 기반으로 테스트를 수행하는 라이브러리
    • jsdom? 순수하게 자바스크립트로 작성된 라이브러리로 Node.js 같은 환경에서도 HTML과 DOM을 사용할 수 있도록 해주는 라이브러리

React Testing Library

  • DOM Testing Library를 기반으로 만들어졌음. 리액트 기반 환경에서 리액트 컴포넌트를 테스트 할 수 있는 라이브러리
  • 해당 라이브러리를 사용하면 실제로 리액트 컴포넌트를 렌더링 하지 않고도 원하는 대로 렌더링 되는지 확인 가능

테스트 코드를 작성하는 과정

  1. 테스트 할 함수나 모듈 선정
  2. 함수나 모듈이 반환하길 기대하는 값 작성
  3. 함수나 모듈의 실제 반환 값 작성
  4. 3번 과정의 기대에 따라 2번의 결과가 일치하는지 확인
  5. 기대하는 결과 반환 시 테스트 성공, 다른 결과 반환시 에러 던짐

위 과정을 위해 가장 먼저 필요한 것?

“ 작성한 코드가 예상대로 동작 하면 성공 메시지 출력, 실패하면 에러 던지기 “

Node.js의 assert라는 모듈을 사용하면 위처럼 작동 가능!

const assert  = require('assert')

function sum(a, b) {
  return a + b
}

assert.equal(sum(1,2), 3) // 성공
assert.equal(sum(1,2), 4) // Assertion Error [ERR_ASSERTION] [ERR_ASSERTION]: 3 == 4

위처럼 테스트 결과를 확인할 수 있도록 도와주는 라이브러리어설션(assetion)라이브러리라고 한다.

단순히 동등을 비교하는 equal 외에도 객체 자체가 동일한지 확인하는 deepEqual, 같지 않은지 비교하는 notEqual, 에러 던지는 여부를 확인하는 throws 등 다양한 메서드를 제공한다.

좋은 테스트 코드

  • 어떤 테스트가 무엇을 테스트 하는지 일목 요연하게 기승전결을 보여주는 것테스팅 프레임워크 사용
  • 자바스크립트의 유명 테스트 프레임워크
    • Jest, Mocha, karma, Jasmine 등

리액트 컴포넌트 테스트 코드 작성하기

  1. 컴포넌트를 렌더링한다.
  2. 필요하면 컴포넌트에서 특정 액션을 수행한다.
  3. 컴포넌트 렌더링과 2번의 액션을 통해 기대하는 결과와 실제 결과를 비교한다.

리액트 컴포넌트에서의 테스트 코드 종류

  • 정적 컴포넌트
    • 별도의 상태가 존재하지 않아 항상 같은 결과를 반환하는 컴포넌트
    • 테스트를 원하는 컴포넌트를 렌더링 후 테스트 원하는 요소를 찾아 테스트를 수행하면 된다.
  • 동적 컴포넌트
    • 사용자 액션이 있는 동적 컴포넌트
    • jest에서 사용자 작동을 흉내내는 메서드들을 사용해서 테스트한다.
  • 비동기 이벤트가 발생하는 컴포넌트
    • Node.js나 브라우저에서 모두 사용할 수 있는 모킹 라이브러리 MSW(Mock Service Worker) 사용
    • 네트워크 요청을 수행하고 이 요청을 중간에 MSW가 감지하고 미리 준비한 모킹 데이터를 제공하는 방식으로 테스트한다.
  • 사용자 정의 훅
    • 훅 테스트를 편리하게 할 수 있도록 도와주는 react-hooks-testing-library 사용

테스트 작성하기에 앞서 고려할 점

  • 프론트엔드 코드는 사용자의 입력이 매우 자유로워 모든 상황을 커버하는 테스트 코드를 작성하기 불가능
  • 테스트 코드를 작성하기 전에 애플리케이션에서 가장 취약하거나 중요한 부분 파악이 우선
  • 테스트가 모두 통과 했다는 것을 보는 것이 아닌 소프트웨어 품질에 대한 확신을 얻기 위해 테스트 코드를 작성하는 것임을 명심하기!

그 밖에 여러가지 테스트들

  • 유닛 테스트
    • 각각의 코드나 컴포넌트가 독립적으로 분리된 환경에서 의도된 대로 작동하는지 검증하는 테스트
  • 통합 테스트
    • 유닛 테스트를 통과한 여러 컴포넌트가 묶여서 하나의 기능으로 작동하는지 확인하는 테스트
  • 엔드 투 엔드 테스트
    • 실제 사용자처럼 작동하는 로봇을 활용해 애플리케이션의 전체적인 기능을 확인하는 테스트