모던 리액트 deep dive 스터디 - 4주차 발표 정리 (8장)
*본 글은 발표 대본용이라 상세 정보는 들어가 있지 않습니다.
8장 - 좋은 리액트 코드 작성을 위한 환경 구축하기
- EsLint
- 리액트 테스트 라이브러리
ESLint
- 정적 코드 분석 도구
❓정적 코드 분석 도구란
코드의 실행과는 별개로 그 자체만으로 코드 스멜을 찾아내어 문제의 소지가 있는 코드를 사전에 수정한다.
ESLint를 자주 사용하지만, 어떤 방식으로 코드를 분석하는 것에 대해서는 잘 알지 못한다.
ESLint는 어떻게 코드를 분석할까?
- 자바스크립트 코드를 문자열로 읽는다.
- 자바스크립트 코드를 분석할 수 있는 파서로 코드를 구조화한다. => espree 사용
- 2번에서 구조화한 트리를 AST(추상구문트리)라 하며, 이 구조화된 트리를 기준으로 각종 규칙과 대조한다.
- 규칙과 대조했을 때 이를 위반한 코드를 알리거나 수정한다.
*espree는 변수, 함수 인지만 파악하는 것이 아니라 코드의 정확한 위치와 같은 아주 세세한 정보도 분석해서 알려준다.
타입스크립트 => @typescript-eslint/typescript-estree 라는 espree 기본 파서를 이용하여 코드를 분석하고 구조화한다.
EsLint 규칙
이러한 espree로 코드를 분석한 결과를 바탕으로 어떠한 코드가 잘못된 코드인지? 어떻게 수정해야하는지를 정의한 것
plugins
이러한 EsLint 특정한 규칙들의 모음
EsLint가 리포트하는 과정 예시)
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow use of the new Date()',
recommended: false,
},
fixable: null,
schema: [],
messages: {
message: "Unexpected 'debugger' statement.",
},
},
create: (context) {
return {
DebuggerStatement(node) {
context.report({
node,
messageId: 'unexpected',
})
},
}
},
}
meta : 해당 규칙과 관련된 메타정보
messages: 규칙을 어겼을 때 반환하는 경고 문구
docs: 문서화에 필요한 정보
fixable: eslint --fix로 수정했을 때 수정 가능한지 여부
create: 실제로 코드에서 문제점을 확인하는 곳, espree로 만들어진 AST트리를 실제로 순회해 조건에 맞는 코드를 찾는 작업을 반복.
eslint-plugin과 eslint-config
eslint-plugin
- 리액트, import와 같이 특정 프레임워크나 도메인과 관련된 규칙을 묶어서 제공하는 패키지
- 네이밍 규칙 eslint-plugin라는 접두사 준수, 뒤에 이어지는 단어는 한 단어까지만 가능
eslint-config
- eslint-plugin을 한데 묶어 한 세트로 제공하는 패키지
- 네이밍 규칙 eslint-config라는 접두사 준수, 뒤에 이어지는 단어는 한 단어까지만 가능
ex)
- eslint-config-airbnb
- 수많은 개발자들의 유지보수
- 압도적인 다운로드 수
- @titicaca/triple-config-kit
- 테스트 코드가 존재
- CI/CD 환경, 카나리 배포 등 일반적인 npm 라이브러리 구축 및 관리를 위한 시스템 구축이 잘되어있음.
- eslint-config-next
- JS 코드 뿐만 아니라 페이지나 JSX 구문 및 _app, document에서 작성돼 있는 HTML 코드 또한 정적 분석 대상임.
- Next.js 환경에서 강력 추천
커스텀 Eslint 규칙
EsLint 규칙을 직접 만들어보자
module.export = {
rules: {
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'react',
importNames: ['default'],
message: '규칙 어긋남',
},
],
},
},
},
}
'no-restricted-imports' : 어떠한 모듈을 import하는 것을 금하기 위한 규칙
paths: 금지시킬 모듈을 추가
name: 모듈명
importNames: 모듈의 이름
message: 경고 메시지
🚨 EsLint 주의할 점
EsLint와 같이 많이 사용되는 코드의 포매팅을 도와주는 도구 Prettier
Prettier와의 충돌
- EsLint와 유사하지만 지향하는 목표는 다르다.
- EsLint는 코드의 잠재적인 문제가 될 수 있는 부분을 분석
- Prettier는 포매팅과 관련된 작업들이 주 목적이다. 즉, 줄바꿈, 들여쓰기, 작은따옴표와 큰따옴표등을 담당한다.
이는 JS 뿐만 아니라 HTML, CSS, 마크다운, JSON 등 다양한 언어에 적용됨.
- EsLint에서도 Prettier에서 처리하는 작업을 처리할 수 있기때문에 이 둘은 충돌할 수 있음.
💁♂️ 해결법
1. 서로 규칙이 충돌되지 않게 서로 잘 정의 (서로 중복되지 않게)
2. JS, TS는 EsLint, 나머지는 Prettier에게 맡기기.
만약, JS에 추가적으로 필요한 Prettier 관련된 규칙은 eslint-plugin-prettier를 사용하기
규칙에 대한 예외처리가 필요하다면?
1. 특정 줄만 제외
eslint-disable-line no-console
2. 다음 줄 제외
eslint-disable-next-line no-console
3. 특정 여러 줄 제외
eslint-disable no-console
~
eslint-disable no-console
4. 파일 전체에서 제외
eslint-disable no-console
eslint-disable-line no-exhaustive-deps
- useEffect, useMemo와 같이 의존 배열이 필요한 훅에 의존성 배열을 제대로 선언했는지 확인하는 역할
개발을 하다보면 이곳에 가장 많이 사용이 될 것이다.
❌ 하지만, 이는 명심하자.
- 의존성 배열에 값이 없어도 괜찮다고 임의로 판단 금지. (버그 야기할 위험성 높음)
- 의존성 배열이 너무 긴경우 => useEffect를 쪼개는게 더 낫다
- 마운트 시점에 한 번만 실행하고 싶은 경우 => 클래스형 컴포넌트에서 주로 사용되던 생명주기 형태의 접근 방법으로, 함수형 컴포넌트의 패러다임과는 맞지 않을 수 있다. 또한, 상태 불일치가 일어날 수도 있음.
‼️ eslint-disable을 많이 사용하고 있다면, 그렇게 무시하는게 정말 옳은 일인지를 면밀하게 판단할 필요가 있다.
EsLint 버전 충돌
두개의 다른 EsLint가 설치되어 충돌되는 상황을 방지하기 위하여 EsLint를 peerDependencies로 설정해 두기를 권장
peerDependencies란?
- 종속석을 표현, 패키지 매니저에게 특정 패키지가 특정 버전과만 동작한다는 정보를 표시해주는 것이다.
이 peerDependencies마저 준수하지 못한 패키지를 설치할 수도 있으니 주의해야한다.
리액트 테스트 라이브러리
테스트.
정의 - 범위에 따른 분류
- 단위 테스트
- 통합 테스트
- 인수 테스트
- 기능 테스트
- 비기능 테스트
- 명세 기반 테스트 (Black box test)
- 구조 기반 테스트 (White box test)
크기에 따른 분류 (구글)
- 작은 테스트
- 중간 테스트
- 큰 테스트
백엔드 테스트
- 화이트박스 테스트
- 작성한 코드가 의도대로 작동하는지 확인
- GUI가 아닌 AUI(응용 프로그램 사용자 인터페이스)에서 주로 수행
- 백엔드에 대한 이해가 필요
프론트엔드 테스트
- 프론트엔드에 대한 이해가 없어도 가능
- 블랙박스 형태
- 코드가 어떻게 됐든 상관없이 의도한 대로 작동하느냐에 더 초점이 맞춰져 있음.
- 다양한 시나리오를 고려해야하기 때문에 테스팅하기 비교적 번거로운 편에 속함
개발자가 작성하는 테스트
- 개발에 도움이 되어야
- 비용 대비 실익이 커야
- 지속 가능해야
모든 버그를 잡기 위해 많은 비용을 들이지 마라
*구름 COMMIT 프론트엔드 테스팅과 설계에 대한 내용 추가 첨부
리액트 테스팅 라이브러리
- DOM Testing Library를 기반
- 브라우저를 직접 실행하지 않고도 리액트 컴포넌트가 원하는 대로 렌더링 되고 있는지 확인 가능
- 컴포넌트 뿐만 아니라 Provider, 훅 등 리액트를 구성하는 다양한 요소들을 테스트 할 수 있음.
DOM Testing Library는 jsdom을 기반
jsdom?
- 순수하게 JS로만 작성되어있으며, 자바스크립트 환경에서도 HTML을 사용할 수 있도록 해주는 라이브러리
jsdom => DOM Testing Library => React Testing Library
이 리액트 테스팅 라이브러리를 사용하여 테스트를 작성해보자
테스트 코드를 작성하는 방식
- 테스트할 함수나 모듈을 선정한다
- 함수나 모듈이 반환하길 기대하는 값을 적는다
- 함수나 모듈의 실제 반환 값을 적는다
- 3번의 기대에 따라 2번의 결과가 일치하는지 확인한다.
- 기대하는 결과를 반환한다면 테스트는 성공, 반대라면 에러를 던진다.
이러한 작동을 해주는 라이브러리들을 어설션 라이브러리라고 한다.
대표적으로 assert, should.js, expect.js, chai 등 이있다.
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
위의 과정은 무슨 테스트를 어떻게 수행했는지 등 테스트에 관한 실제 정보는 알 수 없다.
이러한 정보들은 테스팅 프레임워크를 사용하면 된다. ex) Jest, Mocha, Karma, Jasmine 등
이미지 출처: https://m.blog.naver.com/sssang97/222009530791
위 프레임워크들을 사용하면 무엇을 테스트했는지, 소요된 시간은 어느정도인지, 무엇을 성공하고 실패했는지 등 다양한 결과를 알 수 있다.
리액트 컴포넌트 테스트 방식
- 컴포넌트를 렌더링한다.
- 필요하다면 컴포넌트에서 특정 액션을 수행한다.
- 컴포넌트 렌더링과 2번의 액션을 통해 기대하는결과와 실제 결과를 비교한다.
리액트 컴포넌트 테스트 종류로는 다음과 같다.
- 정적 컴포넌트
- 동적 컴포넌트
- 비동기 이벤트가 발생하는 컴포넌트
- 사용자 정의 훅
정적 컴포넌트
beforEach(() => {
render(<StaticComponent />)
})
- 각 테스트(it)를 실행하기 전에 실행하는 함수
- 테스트에 사용될 컴포넌트를 렌더링
describe('네이버 링크 테스트', () => {
it('네이버 링크가 존재', () => {
const naverLink = screen.getByText('네이버')
expect(naverLink).toBeVisible()
})
- describe는 비슷한 속성을 가진 테스트를 하나로 묶는 역할. (중첩 가능)
- it은 곧 test. it 명칭은 test의 축약어이며 가독성을 위해 사용.
- testId는 get 등의 선택자로 선택하기 어렵거나 곤란한 요소를 선택하기 위해 사용.
ex) HTML의 DOM 요소에 testId 데이터셋을 선언
*데이터셋
- HTML의 특정 요소와 관련된 임의 정보를 추가할 수 있는 HTML 속성을 말한다.
동적 컴포넌트
import { fireEvent, render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { InputComponent } from '.'
describe('InputComponent 테스트', () => {
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함수
- 내부에서 컴포넌트를 렌더링하고 테스트에 필요한 input, button을 반환한다.
it('영문과 숫자만 입력된다.', () => {
const { input } = setup()
const inputValue = '안녕하세요123'
userEvent.type(input, inputValue)
expect(input.value).toEqual('123')
})
userEvent.type은 사용자가 타이핑하는 것과 동일한 작동을 만들 수 있다.
@testing-library/react에서 제공하는 fireEvent을 이용하여 userEvent는 사용자의 작동을 좀 더 자세하게 흉내낼 수 있다.
ex) fireEvent.mouseOver, fireEvent.mouseMove, fireEvent.mouseDown...
it('버튼을 클릭하면 alert가 해당 아이디로 뜬다.', () => {
const alertMock = jest
.spyOn(window, 'alert')
.mockImplementation((_: string) => undefined)
const { button, input } = setup()
const inputValue = 'helloworld'
userEvent.type(input, inputValue)
fireEvent.click(button)
expect(alertMock).toHaveBeenCalledTimes(1)
expect(alertMock).toHaveBeenCalledWith(inputValue)
})
jest.spyOn은 실행이 됐는지, 또 어떤 인수로 실행됐는지 등 실행과 관련된 정보만 얻고 싶을 때 사용
mockImplementation은 해당 메서드에 대한 모킹 구현을 도와준다.
예를 들어, Node.js 환경에서 사용할 수 없는 window.alert에 대한 테스트를 진행할 수 있음.
비동기 이벤트 발생 컴포넌트
fetch의 테스트 케이스는 다양하다.
요청이 성공했을때, 에러가 났을때, headers를 설정하고나 text()로 파싱하는 등등
각 테스트 마다 테스트 값인 ok, status, json의 모든 값을 바꿔 가면서 모킹을 하는 것은 매우 번거로운 일.
이러한 문제를 해결하기위해 MSW라는 라이브러리를 사용해보자.
이는 브라우저에서 서비스 워커를 활용해 실제 네트워크 요청을 가로채는 방식으로 모킹을 구현하며,
REST API 모킹과 GraphQL API 모킹을 모두 지원한다.
사용자 정의 훅 테스트
react-hooks-testing-library을 통해 테스트
위 라이브러리를 사용하면 굳이 테스트를 위한 컴포넌트를 만들지 않아도 훅을 간편하게 테스트 할 수 있다.
'프로그래밍언어 > React' 카테고리의 다른 글
모던 리액트 deep dive 스터디 - 6주차 발표 정리 (0) | 2024.04.13 |
---|---|
모던 리액트 deep dive 스터디 - 5주차 발표 정리 (0) | 2024.04.06 |
모던 리액트 deep dive 스터디 - 3주차 발표 정리 (1) | 2024.03.22 |
모던 리액트 deep dive 스터디 - 1주차 발표 정리 (2) | 2024.03.09 |
React 잘못된 경로 페이지 만들기 (Router) (0) | 2022.06.07 |