728x90
러닝 리엑트을 요약한 내용입니다.
- 코드의 품질을 유지하기 위한 방법중 하나는 단위 테스팅이다.
- 단위 테스팅을 통해 애플리케이션이 의도대로 작동하는지 검증할 수 있다.
10.1 ESLint
- 자바스크립트를 분석하는 과정을 힌팅(hinting) 또는 **린팅(Linting)**이라고 부른다.
- JSHint와 JSLint는 원래 자바스크립트를 분석하고 형식에 대한 제안을 제공하는 도구였다.
- ESLint는 최신 자바스크립트 문법을 지원하는 코드 린터다.
- ESLint는 플러그인을 추가할 수 있다.
- 기능을 확장하기 위해 플러그인을 만들어서 공유할수 있다.
- 다른 사람이 만든 플러그인을 활용할 수 있다.
- create-react-app을 사용하면 ESLint를 즉시 사용할 수 있다.
- eslint-plugin-react라는 플러그인을 사용한다.
- 자바스크립트 외에 JSX와 리액트 문법을 분석해준다.
- eslint를 개발 의존 관계로 설치
- npm install eslint --save-dev # 또는 yarn add eslint --dev
- ESLint를 사용하기 전에 먼저 함께 따라야 할 몇 가지 문법 규칙을 설정해야 한다.
- 프로젝트의 최상위 디렉터리에 설정 파일을 만들고 규칙을 지정할 수 있다.
- 설정 파일은 JSON 이나 YAML로 작성할 수 있다.
- YANML은 JSON과 비슷한 데이터 직렬화 형식이지만 문법이 덜 복잡하고 사람이 읽기 조금 더 쉽다.
- ESLint 설정을 만들려면 eslint —init을 실행하고 코딩 스타일에 대한 몇가지 질문에 답변해야 한다.
- npx eslint --init
- npx eslint —init을 실행하면 3가지 일이 벌어진다.
- eslint-plugin-react가 ./node_modules 아래에 로컬 설치된다.
- package.json 파일에 자동으로 의존 관계가 추가된다.
- 프로젝트 최상위 디렉터리에 .eslintrc.json 설정 파일을 만든다.
- npx eslint --init ? How would you like to use ESLint? … (ESLint를 어떻게 설정하겠습니까?) To check syntax only To check syntax and find problems To check syntax, find problems, and enforce code style ❯ To check syntax and find problems ? What type of modules does your project use? … (프로젝트에서 어떤 모듈을 사용합니까?) JavaScript modules (import/export) CommonJS (require/exports) None of these ❯ JavaScript modules (import/export) ? Which framework does your project use? … (프로젝트에서 어떤 프레임워크를 사용합니까? React Vue.js None of these ❯ React ? Does your project use TypeScript? › No / Yes (프로젝트에서 TypeScript를 사용합니까?) ❯ No ? Where does your code run? … (Press <space> to select, <a> to toggle all, <i> to invert selection) (코드를 어디서 실행합니까? 스페이스 바를 눌러서 선택하거나, a를 눌러서 전체를 토글하거나, i를 눌러서 선택을 반전시킬 수 있음) Browser Node ❯ Browser ? What format do you want your config file to be in? … (설정 파일을 어떤 형식으로 저장하겠습니까?) JavaScript YAML JSON ❯ JSON ? Would you like to install them now with npm? › No / Yes (npm으로 eslint를 설치하겠습니까?) ❯ Yes
- .eslintrc.json 파일의 내용
- extends에 eslint와 react를 초기화 해둔 것을 볼 수 있다.
{ "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:react/recommended" ], "parserOptions": { "ecmaFeatures": { "jsx": true }, "ecmaVersion": 13, "sourceType": "module" }, "plugins": [ "react" ], "rules": { } }
- sample.js 파일을 만들어서 ESLint 설정과 규칙을 테스트해보자.
- const gnar = "gnarly" const info = ({ file = __filename, dir = __dirname }) => ( <p> {dir}: {file} </p> ) switch (gnar) { default: console.log("gnarly") break }
- ESLint를 실행해서 앞에서 정한 규칙에 따라 어떤 피드백을 바등ㄹ 수 있는지 살펴보자.
- 프로퍼티 검증과 관련 오류가 있다
- ESLint가 자동으로 Node.js 전역 변수를 포함시키지 않기 때문에 __filename과 __dirname에 대한 오류가 표시된다.</aside>
- <aside> 💡 내꺼에서는 발생 안함....
- JSX를 사용하기 위해서는 React가 범위 안에 있어야 한다는 사실을 알려준다.
npx eslint src/sample.js 2:7 error 'info' is assigned a value but never used no-unused-vars 'info'에 값을 지정했지만 사용하지 않음 3:12 error '__filename' is not defined no-undef 'filename'이 정의되지 않음 4:11 error '__dirname' is not defined no-undef 'dirname'이 정의되지 않음 6:5 error 'React' must be in scope when using JSX react/react-in-jsx-scope JSX를 사용하려면 'React'가 영역에 들어 있어야 함
- eslint . 이라는 명령은 전체 디렉터리를 검사한다.
- .eslintignore 파일에 ESLint가 무시할 파일과 디렉터리를 적을 수 있다.
- assets 폴더를 무시하지 않으면 ESLint가 클라이언트의 bundle.js 파일을 분석해서 엄청나게 많은 지적사항을 볼수 있을 것이다.
dist/assets/ sample.js
10.1.1 ESLint 플러그인
- ESLint에 추가해서 코드 작성시 도움을 받을 수 있는 프러그인이 많이 있다.
- 리액트 프로젝트의 경우 eslint-plugin-react-hooks를 꼭 설치해야 한다.
- 훅스와 관련된 규칙을 추가해준다.
- 리액트 팀이 훅스 사용과 관련한 버그를 수정할 떄 도움이 될수 있도록 배포했다.
npm install eslint-plugin-react-hooks --save-dev # 또는 yarn add eslint-plugin-react-hooks --dev
- .eslintrc.json을 열어서 다음을 추가하자.
- use라는 단어로 시작하는 함수가 훅스 관련 규칙을 만족하는지 검사한다.
{ "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:react/recommended" ], "parserOptions": { "ecmaFeatures": { "jsx": true }, "ecmaVersion": 13, "sourceType": "module" }, "plugins": [ "react", **"react-hooks"** ], "rules": { **"react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn"** } }
- 추가한 플러그인이 잘 동작하는지 테스트하는 코드
- function gnar() { const [nickname, setNickname] = useState( "dude" ) return <h1>gnarly</h1> }
- 오류 확인
- 컴포넌트나 훅이 아닌 함수 내부에서 useState를 사용하려고 시도한다는 오류 메시지
4:37 error React Hook "useState" is called in function "gnar" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter react-hooks/rules-of-hooks
- npx eslint src/sample.js 3:10 error 'gnar' is defined but never used no-unused-vars 4:12 error 'nickname' is assigned a value but never used no-unused-vars 4:22 error 'setNickname' is assigned a value but never used no-unused-vars 4:37 error React Hook "useState" is called in function "gnar" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter react-hooks/rules-of-hooks
- 유용한 ESLint 플러그인으로는 eslint-plugin-jsx-a11y가 있다
- a11y는 숫자를 사용한 줄임말으로 a와 y 사이에 11글자가 들어 있는 표현이다
- accessibility(접근성)의 줄임말
- 웹사이트, 도구, 기술 등이 얼마나 쉽게 장애인이 사용할 수 있냐를 뜻한다.
- 설치를 위해서는 npm이나 yarn을 사용한다.
- npm install eslint-plugin-jsx-a11y # 또는 yarn add eslint-plugin-jsx-a11y
- 설정 파일 .eslintrc.json을 추가하자
- { "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:react/recommended", **"plugin:jsx-a11y/recommended"** ], "parserOptions": { "ecmaFeatures": { "jsx": true }, "ecmaVersion": 13, "sourceType": "module" }, "plugins": [ "react", "react-hooks", **"jsx-a11y"** ], "rules": { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" } }
- a11y 플러그인을 테스트 해보자
- jsx/a11y 플러그인이 알려주는 오류를 볼 수 있다.
- npx eslint src/sample.js 1:10 error 'Image' is defined but never used no-unused-vars 2:12 error img elements must have an alt prop, either with meaningful text, or an empty string for decorative images jsx-a11y/alt-text 2:12 error 'React' must be in scope when using JSX
- image 태그가 린트 검사를 통과하려면 alt 프로퍼티가 있거나, 이미지가 없으도 내용을 이애하는 데 문제가 없으면 alt 프로퍼티의 내용이 빈 문자열이여야만 한다.
- 2:12 error img elements must have an alt prop, either with meaningful text, or an empty string for decorative images jsx-a11y/alt-text
- function Image() { return <img src="/img.png" /> }
10.2 프리티어
- 프리티어(Prettier)는 다양한 프로젝트에서 사용할 수 있는 옵션 선택이 가능한 코드 형식화기 이다.
- 프리티어가 ESLint와 함께 동작하게 하려면 프로젝트 설정을 좀 더 다듬어야 한다.
- 시작하려면 프리티어를 전역으로 설치해야 한다.
- sudo npm install -g prettier
10.2.1 프로젝트에서 프리디어 설정하기
- 프리티어 설정 파일을 프로젝트에 추가하려면 .prettierrc 파일을 만들면 된다.
- { "semi": true, "trailingComma": "none", "singleQuote": false, "printWidth": 80 }
- sample.js 파일에 형식화가 필요한 코드를 추가하자.
- console.log("Prettier Test")
- 프리티어 검사 진행
- Code style issues found in the above file(s). Forgot to run Prettier? 메시지를 표시한다.
- CLI로부터 프리티어가 코드를 형식화하도록 하려면 write 플래그를 전달한다.
- prettier --write src/sample.js
- 파일의 내용이 바뀌었음을 확인 하자
- console.log("Prettier Test");
- prettier --check src/sample.js Checking formatting... [warn] src/sample.js [warn] Code style issues found in the above file(s). Forgot to run Prettier?
10.2.2 VSCode에서 프리티어 사용하기
- VSCode를 사용중이라면 프리티어를 편집기 안에 설정하기를 권장한다.
- 빠르게 설정을 할수 있으며, 코드를 작성하면서 많은 시간을 절약할 수 있다.
- 프리티어 VSCode 확장 플러그인을 설치한다.
- 설정에 접근하려면 Code → Preference → Settings → Extensions → Prettier
- 오른쪽 상단에 작은 종이 모양 아이콘을 클릭하면 JSON으로 설정할 수 있다.
- { "editor.formatOnSave": true }
- 파일을 저장할 때마다 프리디어가 .prettierrc 디폴트 값에 따라 파일을 형식화해주낟.
- 프로젝트 안에 .prettierrc 파일이 없더라도 기본 값으로 설정할 수 있다.
10.3 리액트 애플리케이션을 위한 타입 검사
- 큰 애플리케이션을 다루는 경우 어떤 유형의 버그를 정확히 잡아내기 위해 타입 검사를 포함시키고 싶을 수도 있다.
- 리액트 앱에서 타입 검사를 수행하는 데는 prop-types 라이브러리, 플로우, 타입스크립트 라는 3가지 방법이 있다.
10.3.1 PropTypes
- PropTypes는 코어 리액트 라이브러리의 일부분이였으며, 리액트 앱의 타입을 검사할 때 권장되는 방법이 었다.
- 요즘은 플로우나 타입스크립트 같은 다른 해법이 대두되면서 리액트 번들 크기를 줄이기 위해서 PropTypes 기능이 별도 라이브러리로 분리됐다.
- 하지만 PropTypes도 널리 쓰이고 있다.
- npm install prop-types --save-dev
- 테스트 코드
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( , document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals> reportWebVitals();
- import './App.css'; import PropTypes from "prop-types" function App({ name }) { return ( <div> <h1>{ name }</h1> </div> ); } App.propTypes = { name: PropTypes.string } export default App;
- 여러 값을 넘길 수 있다.
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( , document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals> reportWebVitals();
- import './App.css'; import PropTypes from "prop-types" function App({ name, using }) { return ( <div> <h1>{ name }</h1> <p> {using ? "used here" : "not used here"} </p> </div> ); } App.propTypes = { name: PropTypes.string, using: PropTypes.bool } export default App;
- 사용할 수 있는 타입들
- PropTypes.array
- PropTypes.object
- PropTypes.bool
- PropTypes.func
- PropTypes.number
- PropTypes.string
- PropTypes.symbol
- 값이 들어 있는지 확인하고 싶다면 isRequired를 붙이면 된다.
ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
- 콘솔에 나오는 오류
index.js:1 Warning: Failed prop type: The prop `name` is marked as required in `App`, but its value is `undefined`.
- App.propTypes = { name: PropTypes.string.isRequired, using: PropTypes.bool }
- 값만 제공되면 제공된 값의 타입이 무엇이든 상관 없는 경우도 있다.
- bool, string, number 등 다양한 타입이 들어갈 수 있다.
- undefined가 아니면 된다.
App.propTypes = { name: PropTypes.any.isRequired, }
- 특정한 값이면 되야하는 경우
- import './App.css'; import PropTypes from "prop-types" function App({ status }) { return ( <div> <h1> We`re {status === "Open" ? "Open!" : "Closed!"} </h1> </div> ); } App.propTypes = { status: PropTypes.oneOf(["Open", "Closed"]) } export default App;
<aside> 💡 콘솔에서만 오류가 발생하는 것 같음...
</aside>
10.3.2 플로우
- 플로우는 페이스북 오픈 소스에 의해 유지되는 타입검사 라이브러리
- 정적 타입 애너테이션을 사용해 오류를 검사하는 도구
- 프로젝트 생성
- npx create-react-app in-th-flow
- 플로우를 프로젝트에 추가
- npm install --save flow-bin
- package.json의 scripts 키에 프로퍼티를 추가 하자
- { "name": "in-th-flow", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", "web-vitals": "^1.1.2" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", **"flow": "flow"** }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
- 이후 .flowconfig 파일을 작성해야한다. 다음 명령어를 통해 만들어보자<aside> 🔥 여기부터 따라해도 안됨
- </aside>
- npm run flow init
- 플로우에서 가장 멋진 기능은 플로우를 점진적으로 적용할 수 있다는 점이다.
- 전체 프로젝트에서 타입 검사를 한꺼번에 적용하는 일은 너무 큰 일이다. 하지만 플로우를 사용하면 꼭 프로젝트 전체에 타입 검사를 적용할 필요가 없다.
- 타입 검사를 하고 싶은 파일 맨 위에 //@flow라는 줄을 추가하면 된다.
- VSCode extention for Flow라는 확장 프로그램을 설치해서 코드 완성과 파라미터 힌터를 제공하게 할 수도 있다.
- index.js 파일을 수정 해보자
- //@flow import React from 'react'; import ReactDOM from 'react-dom'; function App(props) { return ( <div> <h1>{props.item}</h1> </div> ) } ReactDOM.render( <App item="jacket" />, document.getElementById('root') );
- 프로퍼티의 타입을 정의
- //@flow import React from 'react'; import ReactDOM from 'react-dom'; type Props = { item: string } function App(props: Props) { return ( <div> <h1>{props.item}</h1> </div> ) } ReactDOM.render( <App item="jacket" />, document.getElementById('root') );
???? 여기부터는 실행이 안되니...
10.3.3 타입스크립트
- 타입스크립트는 리액트 애플리케이션을 타입체킹하고 싶을 때 쓸수 있는 또 다른 유명한 도구이다.
- 타입스크립트는 자바스크립트의 하위집합이면 오픈 소스 언어이다.
- 마이크로소프트가 타입스크립트를 만들었다.
- 타입스크립트를 만든 목적은 큰 프로젝트에서 개발자가 좀 더 빠르게 버그를 찾고 더 빠르게 개발 이터레이션이 가능하게 돕는 것이다.
- create-react-app은 타입스크립트 템플릿을 제공한다.
- 프로젝트 생성
- npx create-react-app my-type --template typescript
- 프로젝트 구성 요소 확인
- src 디렉터리 안에 있는 파이들의 확장자가 .ts나 .tsx라는 점에 유의하자.
- .tsconfig.json 파일을 볼 수 있다.
- 타입 스크립트 관련 설정이 담겨있다.
- package.json 파일을 보면 타입스크립트 라이브러와 Jest, 리액트, 리액트DOM 등을 위한 정의와 타입스크립트와 관련있는 새로운 의존 관계가 추가 됬음을 알 수 있다.
- @types/ 로시작하는 의존 관계 라이브러리의 타입 정의를 뜻한다.
- 이전 코드를 넣어 보자
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <App item="jacket"/> </React.StrictMode>, document.getElementById('root') ); reportWebVitals();
- 아래와 같은 오류를 볼 수 있다.
Parameter 'props' implicitly has an 'any' type. TS7006
- import './App.css'; function App(props) { return ( <div> <h1>{props.item}</h1> </div> ); } export default App;
- App 컴포넌트에 타입 규칙을 추가
- import './App.css'; function App(props: AppProps) { return ( <div> <h1>{props.item}</h1> </div> ); } type AppProps = { item: string } export default App;
- 필요하다면 props를 구조 분해할 수도 있다.
- import './App.css'; function App({ item }: AppProps) { return ( <div> <h1>{item}</h1> </div> ); } type AppProps = { item: string } export default App;
- 타입의 규칙을 깰 수 있다.
- 아래와 같은 오류 발생
/Users/chihwan/Documents/workspaces/react/testing/my-type/src/index.tsx TypeScript error in /Users/chihwan/Documents/workspaces/react/testing/my-type/src/index.tsx(9,10): Type 'number' is not assignable to type 'string'. TS2322
- 오류가 발생하면 어디서 문제가 발생했는지 정확히 알 수 있다.
- 디버깅시 유용하다.
- import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <App item={1}/> </React.StrictMode>, document.getElementById('root') ); reportWebVitals();
- 타입스크립트를 사용하면 프로퍼티 검증 외에도 여러 가지로 편리하다.
- 타입스크립트의 타입추론을 사용하면 훅 값에 대한 타입검사를 쉽게 할 수 있다.
- fabricColor의 상태 값이 처음에 purple이라고 가정하자.
- fabricColor의 타입이 정의 되지 않았다 하지만 타입스크립트는 최초 상태의 타입과 일치해야 한다고 추론한다.
import { useState } from 'react' function App({ item }: AppProps) { const [fabricColor, setFabricColor] = useState("purple") return ( <div> <h1>{fabricColor}</h1> <button onClick={() => setFabricColor("blue")}> Make the Jacket Blue </button> </div> ); } type AppProps = { item: string } export default App;
- 문자가 아닌 숫자를 입력하면 오류가 발생한다.
/Users/chihwan/Documents/workspaces/react/testing/my-type/src/App.tsx TypeScript error in /Users/chihwan/Documents/workspaces/react/testing/my-type/src/App.tsx(8,45): Argument of type 'number' is not assignable to parameter of type 'SetStateAction<string>'. TS2345 6 | <div> 7 | <h1>{fabricColor}</h1> > 8 | <button onClick={() => setFabricColor(3)}> | ^ 9 | Make the Jacket Blue 10 | </button> 11 | </div>
- import { useState } from 'react' function App({ item }: AppProps) { const [fabricColor, setFabricColor] = useState("purple") return ( <div> <h1>{fabricColor}</h1> <button onClick={() => setFabricColor(3)}> Make the Jacket Blue </button> </div> ); } type AppProps = { item: string } export default App;
- 타입스크립트를 사용하면 이런 값에 대한 노력을 기울이지 않고도 타입으 ㄹ검사할 수 있다.
- 더 자세한 타입을 지정할 수도 있다.
10.4 테스트 주도 개발
- 테스트 주도 개발(TDD)는 기술이 아니다.
- 모든 개발 과정을 테스트 중심으로 진행해 나가는 습관이라 할 수 있다.
TDD 연습 방법
- 테스트를 먼저 작성한다.
- 테스트를 실행하고 실패한다.(빨간색)
- 테스트를 통과하기 위해 필요한 최소한의 코드를 작성한다.(녹색)
- 코드와 테스트를 함께 리팩터링한다.(황금색)
- TDD는 훅을 테스트할 떄 유용하다.
- 훅을 테스트할 때 유용 하다.
- 훅을 직접 작성하기 전에 미리 훅이 어떻게 동작해야 하는지에 대해 생각하는 편이 더 쉽다
- TDD를 연습해서 습관으로 삼으면 UI와 관계없이 애플리케이션이나 어떤 기능에 대한 전체 데이터 구조를 구축하고 검증할 수 있다.
10.4.1 TDD와 학습
- TDD를 처음 접하면 테스트를 작성하는 일이 상당히 어려운 일임을 알 수 있을 것이다.
- TDD의 요령을 파악하기 전에는 테스트를 작성하기 전에 코드를 작성해도 괜찮다.
- 작은 분량을 대상으로 개발을 진행하고, 그 코드에 대해 테스트를 몇가지 작성하는 방식을 반복하자.
- 언어에 대한 감을 잡고 테스트에 대해 감을 잡으면 테스트를 먼저 쓰는 게 훨씬 쉬워질 것이다.
10.5 제스트 사용하기
- TDD 프레임워크는 아무것이나 사용해도 되지만 공식 리액트 문서에는 제스트를 권장한다.
- 제스트는 JSDOM을 통해 DOM에 접근할 수 있는 자바스크립트 테스트 러너이다.
- 리액트가 렌더링한 내용이 맞는지 검사하려면 DOM에 접근할 수 있는게 중요하다.
10.5.1 create-react-app과 테스트
- create-react-app으로 초기화된 프로젝트에는 기본적으로 jest 패키지가 설치된다.
- npx create-react-app testing
- src 폴더에 functions.js와 functions.test.js 라는 2가지 파일을 만들자
- functions.test.js
- 첫번째 인자는 테스트의 이름이다.
- 두번째 인자는 테스트 대상 함수이며 세번째 인자(없어도 됨)는 타임아웃을 지정한다.
- 디폴트 타임아웃은 5초이다.
test("Multiplies by two", () => { expect(); });
- functions.js
export default function tiemsTwo() { }
- functions.test.js
- timesTwo를 테스트하는 코드
- functions.test.js
- 함수를 테스트할 때는 .toBe 매처를 사용한다.
import { tiemsTwo } from "./functions" test("Multiplies by two", () => { expect(tiemsTwo(4)).toBe(8); });
npm test # or npm run test
- 오류 발생
- 테스트 실패에 대한 자세한 내용을 표시해준다.
FAIL src/functions.test.js ✕ Multiplies by two (3 ms) ● Multiplies by two expect(received).toBe(expected) // Object.is equality Expected: 8 Received: undefined 2 | 3 | test("Multiplies by two", () => { > 4 | expect(tiemsTwo(4)).toBe(8); | ^ 5 | }); 6 | 7 | at Object.<anonymous> (src/functions.test.js:4:25) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 2.794 s Ran all test suites related to changed files. Watch Usage: Press w to show more.
- functions.test.js
- 테스트를 통과하기
- 테스트 통과
PASS src/functions.test.js ✓ Multiplies by two (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.233 s Ran all test suites related to changed files. Watch Usage: Press w to show more.
- export default function tiemsTwo(a) { return a * 2; }
- .toBe 매처는 주어진 한 가지 값과 결과가 일치하는지 테스트 해준다.
- 객체나 배열을 테스트하고 싶다면 .toEqual을 사용할 수 있다.
- 배열을 테스트하는 코드
- 라스베가스의 가이 피에리 레스토랑의 메뉴가 있다. 고객이 원하는 음식을 먹고 값을 지불하게 하려면 주문한 메뉴에 해당하는 객체의 목록을 만드는 일이 중요하다.
- 테스트를 먼저 채워 넣자
import { tiemsTwo, order } from "./functions" test("Multiplies by two", () => { expect(tiemsTwo(4)).toBe(8); }); const menuItems = [ { id: "1", name: "Tatted Up Turkey Burger", price: 19.5 }, { id: "2", name: "Lobster Lollipops", price: 16.5 }, { id: "3", name: "Motley Que Pulled Pork Sandwich", price: 21.5 }, { id: "4", name: "Trash Can Nachos", price: 19.5 } ]; test("Build an order object", () => { const result = { orderItems: menuItems, total: 77 } expect(order(menuItems)).toEqual(result) })
- 함수를 채워 넣자
export function tiemsTwo(a) { return a * 2; } export function order(items) { const total = items.reduce((price, item) => price + item.price, 0) return { orderItems: items, total } }
- describe로 테스트들을 감싸면 테스트 러너가 테스트를 묶은 블록을 만들어준다.
- 테스트를 많이 작성할수록 describe 블록으로 테스트를 묶는 것이 더 유용한 개선이 될 것이다.</aside>
- <aside> ❓ 개별 테스트가 더 좋은것 아닌가? 테스트 단위를 적개 하기 위해서 다른 의견 : 테스트를 그룹핑 할수 있다. - 초기화 관련(beforEach 등) 작업을 할수 있기 때문에 의미 있음
export function tiemsTwo(a) { return a * 2; } export function sum(a, b) { return a + b; } export function subtract(a, b) { return a / b; }
import { tiemsTwo, sum, subtract } from "./functions" describe("Math functions", () => { test("Multiplies by two", () => { expect(tiemsTwo(4)).toBe(8); }) test("Adds two numbers", () => { expect(sum(4, 2)).toBe(6); }) test("Subtracts two numbers", () => { expect(subtract(4, 2)).toBe(2); }) })
- 테스트 결과
Math functions ✓ Multiplies by two (1 ms) ✓ Adds two numbers ✓ Subtracts two numbers
테스트를 먼저 작성하고, 테스트를 통과하기 위한 코드를 작성, 테스트에 통과하면 코드를 살펴보면서 성능향상이나 코드 가독성을 개선하기 위한 리팩터링을 한다.
10.6 리액트 컴포넌트 테스트하기
- 리액트 컴포넌트를 테스트 할 수 있는 방법을 알아보자.
- 리액트 컴포넌트는 DOM을 만들고 갱신할 때 리액트가 따라야 하는 명령을 제공한다.
- 렌더링하고 결과로 만들어지는 DOM을 검사하면 컴포넌트를 테스트 할 수 있다.
- 테스트를 브라우저에서 실행하지 않는다.
- 터미널에서 Node.js를 사용해 실행한다.
- Node.js에서는 표준적으로 DOM을 제공하는 브라우저와 달리 DOM API를 제공하지 않는다.
- 제스트에서는 jsdom이라는 npm 패키지가 함께 들어 있다.
- Star.js에 있는 Star 컴포넌트를 다시 살펴보자.
- index.js
import React from 'react'; import ReactDOM from 'react-dom'; import Star from './Star'; ReactDOM.render( <Star />, document.getElementById('root') );
- import { FaStar } from 'react-icons/fa' export default function Start({ selected = false }) { return ( <FaStar color={selected ? "red" : "grey"} id="star" /> ) }
- Star.test.js 파일에서 Star를 테스트 해보자
- div를 생성하고 Star 컴포넌트를 렌더링
- 생성된 div 안에 svg 엘리먼트를 선택하면 결과가 참으로 나오게 된다.
import React from "react"; import ReactDOM from "react-dom"; import Star from "./Star"; test("renders a star", () => { const div = document.createElement("div"); ReactDOM.render(<Star />, div) expect(div.querySelector("svg")).toBeTruthy() })
- 테스트가 실패하는 코드
test("renders a star", () => { const div = document.createElement("div"); ReactDOM.render(<Star />, div) expect(div.querySelector("notrealthing")).toBeTruthy() })
- create-react-app 프로젝트 생성시 @testing-library에 있는 몇가지 패키지가 설치 된다.
- { "name": "testing", "version": "0.1.0", "private": true, "dependencies": { **"@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3",** "react": "^17.0.2", "react-dom": "^17.0.2", "react-icons": "^4.3.1", "react-scripts": "4.0.3", "web-vitals": "^1.1.2" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
- 리액트 테스팅 라이브러리는 켄트 C, 도즈가 좋은 테스트 실무를 권장하고 리액트 생태계의 일부분으로 테스트 도구를 제공하기 위해 시작한 프로젝트이다.
- 테스팅 라이브러리는 Vue, Svelte, Reason, Angular 등 여러 테스트 패키지들을 모아둔 프로젝트다
- 테스팅 라이브러리를 추가하여 더 좋게 만들자
- toBeTruthy 대신 toHaveAttribute를 사용해보자.
<aside> ❓ 에러가 나는데.... 라이브러리 및 import를 변경해보자import React from "react"; import ReactDOM from "react-dom"; import Star from "./Star"; import { toHaveAttribute } from "@testing-library/jest-dom" expect.extend({ toHaveAttribute }) test("renders a star", () => { const div = document.createElement("div"); ReactDOM.render(<Star />, div) expect( div.querySelector("svg") ).toHaveAttribute("id", "hotdog") })
- 커스텀 매처를 둘 이상 사용하려면 아래와 같이 하면 된다.
import { toHaveAttribute, toHaveClass } from "@testing-library/jest-dom" expect.extend({ toHaveAttribute, toHaveClass }) expect(you).toHaveAttribute("evenALittle")
- 커스텀 매처를 전체 추가하려면 extend-expect 라이브러리를 추가하자
<aside> ❓ 이것도 에러...import React from "react"; import ReactDOM from "react-dom"; import Star from "./Star"; import "@testing-library/jest-dom/extend-expect" test("renders a star", () => { const div = document.createElement("div"); ReactDOM.render(<Star />, div) expect( div.querySelector("svg") ).toHaveAttribute("id", "hotdog") })
- create-react-app을 사용하면 테스트 파일안에서 extend-expect를 임포트할 필요 없다
- setuTest.js에 import '@testing-library/jest-dom/extend-expect'; 추가 되어 있음
- </aside>
- </aside>
- </aside>
10.6.1 쿼리
- 쿼리 테스트를 위해 Star 컴포넌트에 제목을 포함시키자
- import { FaStar } from 'react-icons/fa' export default function Start({ selected = false }) { return ( <> <h1>Great Star</h1> <FaStar color={selected ? "red" : "grey"} id="star" /> </> ) }
- h1안에 제대로 텍스트가 들어갔는지 테스트 해보자
<aside> ❓ 이것도 에러...import React from "react"; import Star from "./Star"; import { render } from '@testing-library/react' test("renders an h1", () => { const { getByText } = render(<Star />) const h1 = getByText(/Great Star/); expect(h1).toHaveAttribute("Great Star") })
- </aside>
- 리액트 테스팅 라이브러이에 있는 render라는 함수를 쓰면 테스트에 도움이 된다.
10.6.2 이벤트 테스트
- Checkbox 컴포넌트의 이벤트를 테스트 해보자
- import { useReducer } from "react"; export function Checkbox() { const [checked, setChecked] = useReducer( checked => !checked, false ) return ( <> <label> {checked ? "checked" : "not checked"} <input type="checkbox" value={checked} onChange={setChecked} /> </label> </> ) }
- 테스트를 통해 체크박스를 클릭하고 checked의 값이 디폴트 값인 true에서 false로 바뀌는지 살펴보는 것이다.
- getByLabelText를 사용해서 checkbox 엘리먼트를 찾아 내자
- fireEvent를 사용해 checkbox를 클릭하고 checked 프로퍼티가 true로 변했는지 확인
- 이벤트를 다시 발생시키고 false로 변경이 되었는지 확인
import React from "react" import { render, fireEvent } from "@testing-library/react" import { Checkbox } from "./Checkbox" test("Selecting the ceckbox should change the value of checked to true", () => { const { getByLabelText } = render(<Checkbox />) const checkbox = getByLabelText(/not checked/) fireEvent.click(checkbox) expect(checkbox.checked).toEqual(true) fireEvent.click(checkbox) expect(checkbox.checked).toEqual(false) })
- 레이블이 있을 경우 쉽게 엘리먼트를 찾고 테스트를 할수 있었다.
- DOM 엘리먼트를 쉽게 찾을수 없을 경우를 위해 DOM 엘리먼트를 쉽게 찾을 수 있도록 애트리뷰트를 하나 추가할 수 있다.
import { useReducer } from "react"; export function Checkbox() { const [checked, setChecked] = useReducer( checked => !checked, false ) return ( <input type="checkbox" value={checked} onChange={setChecked} data-testid="checkbox" /> ) }
- getByTestId라는 함수를 써서 엘리먼트를 쿼리 할 수 있다.
- import React from "react" import { render, fireEvent } from "@testing-library/react" import { Checkbox } from "./Checkbox" test("Selecting the ceckbox should change the value of checked to true", () => { const { getByTestId } = render(<Checkbox />) const checkbox = getByTestId("checkbox") fireEvent.click(checkbox) expect(checkbox.checked).toEqual(true) fireEvent.click(checkbox) expect(checkbox.checked).toEqual(false) })
- DOM 엘리먼트에 접근하기 위한 방법이 마땅치 않은 경우에 아주 유용하다.
10.6.3 코드 커버리지 사용하기
- 코드 커버리지는 테스트가 소스 코드 중 몇 줄을 실제로 테스트하는지 보고하는 과정을 말한다.
- 코드 커버리지는 충분히 테스트를 작성했는지 보여주는 지표가 될 수 있다.
- 제스트에는 이스탄불이라는 자바스크립트 도구가 들어 있다.
- 이스탄불은 테스트를 분석해서 문장, 분기, 함수, 줄을 테스트 했는지 표시해준다.
- 제스트를 실행하면서 코드 커버리지를 검사하려면 coverage 플래그를 추가 해야한다.
--------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s --------------|---------|----------|---------|---------|------------------- All files | 81.81 | 0 | 87.5 | 80 | Checkbox.js | 100 | 100 | 100 | 100 | Star.js | 0 | 0 | 0 | 0 | 4 functions.js | 100 | 100 | 100 | 100 | index.js | 0 | 100 | 100 | 0 | 5 --------------|---------|----------|---------|---------|-------------------
- jest --coverage # 또는 npm test -- --coverage
- 제스트는 브라우저에서 볼 수 있는 테스트 보고서도 만들어 준다.
- 프로젝트 Root에 coverage/lcov-report/index.html 파일이 생성됨
- 코드 커버리지는 100%달성하는 경우는 일반적이지 않다.
- 85%를 목표로 하는 편이 좋다
- 코드의 품질을 유지하기 위한 방법중 하나는 단위 테스팅이다.
- 단위 테스팅을 통해 애플리케이션이 의도대로 작동하는지 검증할 수 있다.
728x90