/React_Test

Primary LanguageJavaScript

๐Ÿ’ซ React_Test(Jest)


# npm install
yarn install ๋˜๋Š” yarn

# npm i <package> --save
yarn add <package>

# npm i <package> --save-dev
yarn add <package> --dev : --dev ์˜ต์…˜์€ -D ์™€ ๊ฐ™๋‹ค.

# ํŒจํ‚ค์ง€ ์‚ญ์ œ
yarn remove <package>

# dependencies์™€ devDependencies ๋ชจ๋‘ (package.json ์— ๋ช…์‹œ๋œ) version rule ์— ๋”ฐ๋ผ ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ.
# ๋งŒ์•ฝ ์–ด๋–ค ํŒจํ‚ค์ง€๊ฐ€ semantic versioning([segVer](https://github.com/semver/semver/blob/master/semver.md))๋ฅผ
# ๋”ฐ๋ฅด์ง€ ์•Š๋Š”๋‹ค๋ฉด, version rule์ด ๋ฌด์ƒ‰ํ•ด์ ธ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์ด ๋ณด์žฅ๋˜์ง€ ์•Š๋Š” ์—…๊ทธ๋ ˆ์ด๋“œ์ผ ์ˆ˜๋„ ์žˆ๋‹ค.
yarn upgrade

# ํŠน์ • ํŒจํ‚ค์ง€๋ฅผ ํŠน์ • ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ
yarn upgrade <package>@<version>

# ๋ชฉ๋ก๋“ค ์ค‘์—์„œ ์›ํ•˜๋Š” ํŒจํ‚ค์ง€๋งŒ ์ตœ์‹ ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” interactive terminal ui ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
yarn upgrade-interactive

# production ํ™˜๊ฒฝ์„œ ํ•„์š”ํ•œ dependencies ๋งŒ ์„ค์น˜
NODE_ENV=production yarn install ๋˜๋Š” yarn install --production


๐Ÿ›  1. ํ…Œ์ŠคํŒ…์˜ ์ด์œ 


  • โœ”๏ธ ์™œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ TEST ํ•ด์•ผ ํ• ๊นŒ?

    • ๊ฐ„๋‹จํ•˜๊ฒŒ ๋” ์•ˆ์ •์ ์ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•ด์„œ๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์ค˜์•ผ ๋” ์•ˆ์ •์ ์ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • โœ”๏ธ ํ…Œ์ŠคํŒ…์œผ๋กœ ์–ป๋Š” ์ด์ ์€ ๋ฌด์—‡์ผ๊นŒ?

    (1) ๋””๋ฒ„๊น… ์‹œ๊ฐ„์„ ๋‹จ์ถ•! ๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜๋ชป ๋‚˜์™”๋‹ค๋ฉด ๊ทธ๊ฒƒ์ด UI์˜ ๋ฌธ์ œ์ธ์ง€ DB์˜ ๋ฌธ์ œ์ธ์ง€๋“ฑ ์ „๋ถ€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ด์„œ ์ฐพ์•„์•ผ ํ•˜๋Š”๋ฐ ํ…Œ์ŠคํŒ… ํ™˜๊ฒฝ์ด ๊ตฌ์ถ•๋˜์–ด์žˆ๋‹ค๋ฉด ์ž๋™ํ™” ๋œ ์œ ๋‹› ํ…Œ์ŠคํŒ…์œผ๋กœ ํŠน์ • ๋ฒ„๊ทธ๋ฅผ ์‰ฝ๊ฒŒ ์ฐพ์•„ ๋‚ผ ์ˆ˜์žˆ์Šต๋‹ˆ๋‹ค.

    (2) ๋”์šฑ ์•ˆ์ •์ ์ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜! ๋งŽ์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ž‘์„ฑ๋œ ์ฝ”๋“œ์˜ ์–ดํ”Œ ๋ฆฌ์ผ€์ด์…˜์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ์•ˆ์ •์ ์ธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฉ๋‹ˆ๋‹ค.

    (3) ์ด๋ฐ–์—๋„ ์žฌ์„ค๊ณ„ ์‹œ๊ฐ„์˜ ๋‹จ์ถ•, ์ถ”๊ฐ€๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋” ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๋•Œ ๋” ์šฉ์ดํ•˜ ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋“ฑ์˜ ์ด์ ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.


  • โœ”๏ธ React Testing Library๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

    • Enzyme -> ๊ตฌํ˜„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ(Implementation Driven Test)

    • React Testing Library -> ํ–‰์œ„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ(Behavior Driven Test)

    • React Testing Library๋Š” React ๊ตฌ์„ฑ ์š”์†Œ ์ž‘์—…์„ ์œ„ํ•œ API๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ DOM Testing Library ์œ„์— ๊ตฌ์ถ•๋ฉ๋‹ˆ๋‹ค. DOM Testing Library๋ž€ Dom ๋…ธ๋“œ(Node)๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ๋งค์šฐ ๊ฐ€๋ฒผ์šด ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. Create React App์œผ๋กœ ์ƒ์„ฑ๋œ ํ”„๋กœ์ ํŠธ๋Š” ์ฆ‰์‹œ React Testing Library๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด npm์„ ํ†ตํ•ด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


      - npm install --save-dev @testing-library/react
      

    • React Testing Library๋Š” ์—์–ด๋น„์•ค๋น„์—์„œ ๋งŒ๋“  Enzyme์„ ๋Œ€์ฒ˜ํ•˜๋Š” ์†”๋ฃจ์…˜ ์ž…๋‹ˆ๋‹ค. Enzyme์ด ๊ตฌ์„ฑ ์š”์†Œ์˜ ๊ตฌํ˜„ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋Œ€์‹  React Testing Library ๊ฐœ๋ฐœ์ž๋ฅผ React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‚ฌ์šฉ์ž ์ž…๋‹ˆ๋‹ค.

    • ๊ตฌํ˜„ ์ฃผ๋„ ํ…Œ์ŠคํŠธ(Enzyme)์—์„œ๋Š” ์œ„์˜ UI๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์ฃผ๋กœ <p> ํƒœ๊ทธ๊ฐ€ ์“ฐ์˜€๊ณ  Edit ๋“ฑ์˜ ๋ฌธ์ž๊ฐ€ ๋“ค์–ด๊ฐ”๋‹ค๋Š”๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋งŒ์•ฝ <p>ํƒœ๊ทธ๋ฅผ <h2> ํƒœ๊ทธ๋กœ ๋ฐ”๋€Œ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ–‰๋™ ์ฃผ๋„ ํ…Œ์ŠคํŠธ(React Testing Library)์—์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ์ž…์žฅ์—์„œ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ์— <p>ํƒœ๊ทธ๊ฐ€ ์“ฐ์ด๋˜ <h3> ํƒœ๊ทธ๊ฐ€ ์“ฐ์—ฌ์„œ ๊ธ€์„ ํ‘œํ˜„ํ•˜๋Š”์ง€๊ฐ€ ์ค‘์š”ํ•œ์ง€ ๋ณด๋‹ค ์–ด๋– ํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ์„๋•Œ ํ™”๋ฉด์ด ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”๊ฐ€ ๋˜๋Š”์ง€ ๊ฐ™์€ ํ…Œ์ŠคํŠธ๊ฐ€ ๋” ์ฃผ๋ฅผ ์ด๋ฃจ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค

  • โœ”๏ธ Create React App์œผ๋กœ ๋ฆฌ์•กํŠธ ์‹œ์ž‘ํ•˜๊ธฐ

    npx create-react-app <ํ”„๋กœ์ ํŠธ ์ด๋ฆ„>
    • ์›๋ž˜ create-react-app์„ ํ• ๋–„ npm install -g create-react-app ์ด๋ ‡๊ฒŒ ํ–ˆ์—ˆ๋‹ค. global ๋””๋ ‰ํ† ๋ฆฌ์— ๋‹ค์šด๋ฐ›์Œ

    • ์ด์ œ๋Š” npx๋ฅผ ์ด์šฉํ•˜์—ฌ ๋‹ค์šด ๋ฐ›์ง€ ์•Š๊ณ  ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.


  • โœ”๏ธ Jest๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

    • FaceBook์— ์˜ํ•ด์„œ ๋งŒ๋“ค์–ด์ง„ ํ…Œ์ŠคํŒ… ํ”„๋ ˆ์ž„ ์›Œํฌ์ž…๋‹ˆ๋‹ค. ์ตœ์†Œํ•œ์˜ ์„ค์ •์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ Test Case ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๊ฐ€ ์ž˜ ๋Œ์•„๊ฐ€๋Š”์ง€ ํ™•์ธํ•ด์ค๋‹ˆ๋‹ค. ๋‹จ์œ„ (Unit) ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„œ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

    • Jest๊ฐ€ Test ํŒŒ์ผ์„ ์ฐพ๋Š” ๋ฐฉ๋ฒ• : {filename}.test.js / {filename}.spec.js / All files inside "tests" folders


  • โœ”๏ธ Jest ํŒŒ์ผ ๊ตฌ์กฐ & ์‚ฌ์šฉ๋ฒ•

    • "describe" : ์—ฌ๋Ÿฌ ๊ด€๋ จ ํ…Œ์ŠคํŠธ๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ๋ธ”๋ก์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    • "it" : ๊ฐœ๋ณ„ ํ…Œ์ŠคํŠธ(test)๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ณณ. ๊ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์€ ๋ฌธ์žฅ์ฒ˜๋Ÿผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

    • "expect" : expect ํ•จ์ˆ˜๋Š” ๊ฐ’์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  expect ํ•จ์ˆ˜ ํ˜ผ์ž์„œ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉ ๋˜์ง€ ์•Š์œผ๋ฉฐ matcher์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    • "matcher" : ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ’์„ ํ…Œ์ŠคํŠธ ํ•˜๋„๋ก "matcher"๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

    • "render" : DOM์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋žœ๋”๋งํ•˜๋Š” ํ•จ์ˆ˜ / ์ธ์ž๋กœ ๋žœ๋”๋งํ•  React ์ปดํฌ๋„ŒํŠธ( <App /> )๊ฐ€ ๋“ค์–ด๊ฐ


  • โœ”๏ธ Jest ์ฟผ๋ฆฌํ•จ์ˆ˜ & ์‚ฌ์šฉ๋ฒ•

    • ์ฟผ๋ฆฌ ํ•จ์ˆ˜ : ํŽ˜์ด์ง€์—์„œ ์š”์†Œ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ• / ์—ฌ๋Ÿฌ ์œ ํ˜•์˜ ์ฟผ๋ฆฌ("get", "find", "query") => ์ด๋“ค ๊ฐ„์˜ ์ฐจ์ด์ ์€ ์š”์†Œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์œผ๋ฉด ์ฟผ๋ฆฌ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ๋˜๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜๋Š”์ง€ ์—ฌ๋ถ€

      • "getBy..." : ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์ผ์น˜ํ•˜๋Š” ๋…ธ๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ผ์น˜ํ•˜๋Š” ์š”์†Œ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋‘˜ ์ด์ƒ์˜ ์ผ์น˜๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด ์„ค๋ช… ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค(๋‘˜ ์ด์ƒ์˜ ์š”์†Œ๊ฐ€ ์˜ˆ์ƒ๋˜๋Š” ๊ฒฝ์šฐ ๋Œ€์‹  "getAllBy..." ์‚ฌ์šฉ).

      • "queryBy..." : ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์ผ์น˜ํ•˜๋Š” ๋…ธ๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ผ์น˜ํ•˜๋Š” ์š”์†Œ๊ฐ€ ์—†์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์š”์†Œ๋ฅผ ์–ด์„ค์…˜ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‘˜ ์ด์ƒ์˜ ์ผ์น˜ ํ•ญ๋ชฉ์ด ๋ฐœ๊ฒฌ๋˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค(ํ™•์ธ๋œ ๊ฒฝ์šฐ ๋Œ€์‹  "queryAllBy"... ์‚ฌ์šฉ).

      • "findBy..." : ์ฃผ์–ด์ง„ ์ฟผ๋ฆฌ์™€ ์ผ์น˜ํ•˜๋Š” ์š”์†Œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด Promise๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์š”์†Œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ๊ธฐ๋ณธ ์ œํ•œ ์‹œ๊ฐ„์ธ 1000 ํ›„์— ๋‘˜ ์ด์ƒ์˜ ์š”์†Œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด ์•ฝ์†์ด ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. ๋‘˜ ์ด์ƒ์˜ ์š”์†Œ๋ฅผ ์ฐพ์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ "findAllBy..."๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. / findBy = getBy + waitFor

      • "waitFor" : ์ผ์ • ๊ธฐ๊ฐ„ ๋™์•ˆ ๊ธฐ๋‹ค๋ ค์•ผ ํ•  ๋•Œ waitFor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋Œ€๊ฐ€ ํ†ต๊ณผํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


[Jest_์ฟผ๋ฆฌํ•จ์ˆ˜]




๐Ÿ”จ 2. ์‹คํ–‰๋ฐฉ๋ฒ•

  • npm5 ๋ถ€ํ„ฐ๋Š” --save ์˜ต์…˜์„ ๊ธฐ๋ณธ ์˜ต์…˜์œผ๋กœ ์ ์šฉํ•œ๋‹ค. ์ฆ‰, --save๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ dependencies์— ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€๋œ๋‹ค.
โœ”๏ธ npm
JavaScript์šฉ Package Manager



โœ”๏ธ npm init
ํŒจํ‚ค์ง€ ์˜์กด์„ฑ์„ ๊ด€๋ฆฌํ•  package.json ํŒŒ์ผ์„ ๋งŒ๋“œ๋Š” ๋ช…๋ น์–ด



โœ”๏ธ npm install (plugin)
npm์œผ๋กœ ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์— ํŒจํ‚ค์ง€(plugin)๋ฅผ ์„ค์น˜



โœ”๏ธ npm install (plugin) --save
ํŒจํ‚ค์ง€(plugin)๋ฅผ ./node_modules ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์„ค์น˜ํ•˜๊ณ  ./package.json ํŒŒ์ผ์˜ dependencies ํ•ญ๋ชฉ์— ํŒจํ‚ค์ง€ ์ •๋ณด๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
--production ๋นŒ๋“œ ์‹œ ํ•ด๋‹น ํŒจํ‚ค์ง€๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.


โœ”๏ธ npm install (plugin) --save-dev
ํŒจํ‚ค์ง€(plugin)์„ ./node_modules ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์„ค์น˜ํ•˜๊ณ  ./package.json ํŒŒ์ผ์˜ devDependencies ํ•ญ๋ชฉ์— ํŒจํ‚ค์ง€ ์ •๋ณด๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
--production ๋นŒ๋“œ ์‹œ ํ•ด๋‹น ํŒจํ‚ค์ง€๋Š” ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๐Ÿ”ฅ npx create-react-app react-test-app

  • ๐Ÿ”ฅ npm install jest --save-dev

  • ๐Ÿ”ฅ npm test

  • โœ”๏ธ Eslint ์„ค์ • ํŒŒ์ผ ์ƒ์„ฑ

    1. package.json์— eslintConfig ๋ถ€๋ถ„ ์ œ๊ฑฐ

    2. .eslintrc.json ํŒŒ์ผ ์ƒ์„ฑ

    3. Plugins : eslint์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ๋‹ค์–‘ํ•œ ๊ทœ์น™์„ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      • eslint: ESLint ์ฝ”์–ด

      • eslint-plugin-react: React ๊ด€๋ จ ๋ฆฐํŠธ ์„ค์ •์„ ์ง€์›

      • eslint-plugin-react-hooks: React Hooks์˜ ๊ทœ์น™์„ ๊ฐ•์ œํ•ด์ฃผ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ

      • eslint-plugin-import: ES2015+์˜ import/export ๊ตฌ๋ฌธ์„ ์ง€์›

      • eslint-plugin-jsx-a11y: JSX ๋‚ด์˜ ์ ‘๊ทผ์„ฑ ๋ฌธ์ œ์— ๋Œ€ํ•ด ์ฆ‰๊ฐ์ ์ธ AST๋ฆฐํŒ… ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณต

      • eslint-config-prettier: prettier์™€ eslint์˜ ์ถฉ๋Œ์„ ์ผ์œผํ‚ค๋Š” ESLint ๊ทœ์น™๋“ค์„ ๋น„ํ™œ์„ฑํ™”์‹œ์ผœ์ฃผ๋Š” config

      • eslint-plugin-prettier: prettier์—์„œ ์ธ์‹ํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ESLint๊ฐ€ ์ถœ๋ ฅ // ์‚ฌ์šฉํ•ด๋„ ๋˜์ง€๋งŒ ๋น„์ถ”

      • eslint-config-airbnb: airbnb์‚ฌ์˜ ์ฝ”๋”ฉ๊ทœ์น™์„ ์‚ฌ์šฉ

      • npm eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier --save-dev

      • ๋‚ด๋ถ€ ์„ค์ •

        • env : ์‚ฌ์ „ ์ •์˜๋œ ์ „์—ญ ๋ณ€์ˆ˜ ์‚ฌ์šฉ์„ ์ •์˜ / ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์„ค์ •์œผ๋กœ๋Š” browser, node

        • parserOptions : ESLint ์‚ฌ์šฉ์„ ์œ„ํ•ด ์ง€์›ํ•˜๋ ค๋Š” Javascript ์–ธ์–ด ์˜ต์…˜์„ ์ง€์ •

        • plugins : ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€ => ์ถ”๊ฐ€ ํ•  ๋•Œ, eslint-plugin- ๋ถ€๋ถ„ ์ƒ๋žต ๊ฐ€๋Šฅ

        • extends : ๊ทœ์น™ ์„ค์ • => ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ ํ•œ ํ›„์— ๊ทœ์น™์„ ์ •ํ•ด์ค˜์•ผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ / react๋ฅผ ์œ„ํ•œ ๊ทœ์น™ recommended๋Š” ์ถ”์ฒœ์ด ๋˜๋Š” ๊ฑธ ์‚ฌ์šฉ

        • rule : ๊ทœ์น™ ๋ณ€๊ฒฝ

      • ESLint ์„ค์ • ์‚ดํŽด๋ณด๊ธฐ

      • [React] ESLint Prettier ์„ค์ •๋ฐฉ๋ฒ•

    4. React_Test(Jest)์ผ๋•Œ

      • eslint-plugin-testing-library : render๋กœ Dom ๊ทธ๋ฆฌ๋Š” ๋ถ€๋ถ„

      • eslint-plugin-jest-dom : expect-matcher๋กœ ํ…Œ์ŠคํŠธ

      • npm install eslint-plugin-testing-library eslint-plugin-jest-dom --save-dev

      // src/eslintrc.json
      
      {
         "plugins": ["testing-library", "jest-dom"],
         "extends": [
         "react-app",
         "react-app/jest",
         "plugin:testing-library/react",
         "plugin:jest-dom/recommended"
         ]
      }

  • โœ”๏ธ Prettier ์„ค์น˜ ๋ฐ ์„ค์ •

    1. npm์œผ๋กœ ์„ค์น˜ : ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ์ž์™€ ๊ฐ™์€ ํฌ๋งท ์œ ์ง€์— ๋” ์ข‹์Œ
    {
    "singleQuote": false, // ๋ฌธ์ž์—ด์€ ๋”ฐ์˜ดํ‘œ๋กœ formatting
    "semi": true, // ์ฝ”๋“œ ๋งˆ์ง€๋ง‰์— ์„ธ๋ฏธ์ฝœ๋ฅธ์ด ์—†๊ฒŒ formatting
    "useTabs": false, // ํƒญ์˜ ์‚ฌ์šฉ์„ ๊ธˆํ•˜๊ณ  ์ŠคํŽ˜์ด์Šค๋ฐ” ์‚ฌ์šฉ์œผ๋กœ ๋Œ€์ฒดํ•˜๊ฒŒ formatting
    "tabWidth": 2, // ๋“ค์—ฌ์“ฐ๊ธฐ ๋„ˆ๋น„๋Š” 2์นธ
    "trailingComma": "all", // ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด ํ‚ค:๊ฐ’ ๋’ค์— ํ•ญ์ƒ ์ฝค๋งˆ๋ฅผ ๋ถ™ํžˆ๋„๋ก formatting
    "printWidth": 80, // ์ฝ”๋“œ ํ•œ์ค„์ด maximum 80์นธ
    "arrowParens": "avoid", // ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๊ฐ€ ํ•˜๋‚˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์„ ๋•Œ ๊ด„ํ˜ธ๋ฅผ ์ƒ๋žตํ•˜๊ฒŒ formatting
    "endOfLine": "auto" // EoF ๋ฐฉ์‹, OS๋ณ„๋กœ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์ด ๋‹ค๋ฆ„
    }
    1. VSCODE ์ต์Šคํ…์…˜์œผ๋กœ ์„ค์น˜ : ํ˜ผ์ž์„œ ํŽธํ•˜๊ฒŒ ์„ค์น˜ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹์Œ

      • ์„ค์ • : Extension Settings ์—์„œ ์„ค์ • ๋ณ€๊ฒฝ



๐Ÿ’ก 3. TDD

  • โœ”๏ธ TDD : Test Driven Development

    • ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑ

    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ํ›„ ๊ทธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ Pass ํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ

  • โœ”๏ธ TDD ์žฅ์ 

    1. TDD๋ฅผ ํ•˜๋ฏ€๋กœ ์ธํ•ด ๋งŽ์€ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ์— ์†Œ์Šค ์ฝ”๋“œ์— ์•ˆ์ •๊ฐ์ด ๋ถ€์—ฌ๋œ๋‹ค.

    2. ์‹ค์ œ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜๋Š” ๋ถ€๋ถ„์€ ๋””๋ฒ„๊น… ๋ถ€๋ถ„์ด๊ธฐ์— TDD๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋””๋ฒ„๊น… ์‹œ๊ฐ„์ด ์ค„์–ด๋“ค๊ณ  ์‹ค์ œ ๊ฐœ๋ฐœ ์‹œ๊ฐ„๋„ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

    3. ์†Œ์Šค ์ฝ”๋“œ ํ•˜๋‚˜ํ•˜๋‚˜๋ฅผ ๋”์šฑ ์‹ ์ค‘ํ•˜๊ฒŒ ์งค ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊นจ๋—ํ•œ ์ฝ”๋“œ๊ฐ€ ๋‚˜์˜ฌ ํ™•๋ฅ ์ด ๋†’์Šต๋‹ˆ๋‹ค.




โš™๏ธ 4. react-counter-test

  • ๐Ÿ”ฅ FireEvent API : ์œ ์ €๊ฐ€ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์•ก์…˜(์ด๋ฒคํŠธ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ
  • FireEvent API ์‚ดํŽด๋ณด๊ธฐ
// App.js

import { useState } from "react";
import "./App.css";

function App() {
  const [counter, setCounter] = useState(0);
  const [disabled, setDisabled] = useState(false);

  return (
    <div className="App">
      <header className="App-header">
        <h3 data-testid="counter">{counter}</h3>
        <div>
          <button
            data-testid="minus-button"
            onClick={() => setCounter((count) => count - 1)}
            disabled={disabled}
          >
            -
          </button>
          <button
            data-testid="plus-button"
            onClick={() => setCounter((count) => count + 1)}
            disabled={disabled}
          >
            +
          </button>
        </div>
        <div>
          <button
            data-testid="on/off-button"
            style={{ backgroundColor: "blue" }}
            onClick={() => setDisabled((prev) => !prev)}
          >
            on/off
          </button>
        </div>
      </header>
    </div>
  );
}

export default App;

// App.test.js

import { render, screen, fireEvent } from "@testing-library/react";
import App from "./App";

test("the counter starts at 0", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const counterElement = screen.getByTestId("counter");
  // id๊ฐ€ counter์ธ ์—˜๋ ˆ๋ฉ˜ํŠธ์˜ ํ…์ŠคํŠธ๊ฐ€ 0์ธ์ง€ ํ…Œ์ŠคํŠธ
  expect(counterElement).toHaveTextContent(0);
});

test("minus button has correct text", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const minusButtonElement = screen.getByTestId("minus-button");
  // id๊ฐ€ minus-button์ธ ์—˜๋ ˆ๋ฉ˜ํŠธ์˜ ํ…์ŠคํŠธ๊ฐ€ -์ธ์ง€ ํ…Œ์ŠคํŠธ
  expect(minusButtonElement).toHaveTextContent("-");
});

test("plus button has correct text", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const plusButtonElement = screen.getByTestId("plus-button");
  // id๊ฐ€ plus-button์ธ ์—˜๋ ˆ๋ฉ˜ํŠธ์˜ ํ…์ŠคํŠธ๊ฐ€ +์ธ์ง€ ํ…Œ์ŠคํŠธ
  expect(plusButtonElement).toHaveTextContent("+");
});

test("When the + button is pressed, the counter changes to 1", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const buttonElement = screen.getByTestId("plus-button");
  // fireEvent๋Š” ์œ ์ €๊ฐ€ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์•ก์…˜(์ด๋ฒคํŠธ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ
  fireEvent.click(buttonElement);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const counterElement = screen.getByTestId("counter");
  // id๊ฐ€ counter์ธ ์—˜๋ ˆ๋ฉ˜ํŠธ์˜ ํ…์ŠคํŠธ๊ฐ€ 1์ธ์ง€ ํ…Œ์ŠคํŠธ
  expect(counterElement).toHaveTextContent(1);
});

test("When the - button is pressed, the counter changes to -1", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const buttonElement = screen.getByTestId("minus-button");
  // fireEvent๋Š” ์œ ์ €๊ฐ€ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์•ก์…˜(์ด๋ฒคํŠธ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ
  fireEvent.click(buttonElement);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const counterElement = screen.getByTestId("counter");
  // id๊ฐ€ counter์ธ ์—˜๋ ˆ๋ฉ˜ํŠธ์˜ ํ…์ŠคํŠธ๊ฐ€ -1์ธ์ง€ ํ…Œ์ŠคํŠธ
  expect(counterElement).toHaveTextContent(-1);
});

test("on/off button has blue color", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const buttonElement = screen.getByTestId("on/off-button");
  // on/off ๋ฒ„ํŠผ color๋ฅผ blue ํ™•์ธ
  expect(buttonElement).toHaveStyle({ backgroundColor: "blue" });
});

test("Prevent the -,+ button from being pressed when the on/off button is cliecked", () => {
  // App ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง
  render(<App />);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const onOffButtonElement = screen.getByTestId("on/off-button");
  // fireEvent๋Š” ์œ ์ €๊ฐ€ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์•ก์…˜(์ด๋ฒคํŠธ)์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ
  fireEvent.click(onOffButtonElement);
  //screen object๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ์—˜๋ ˆ๋ฉ˜ํŠธ์— ์ ‘๊ทผ(์ ‘๊ทผํ•  ๋•Œ  ID๋กœ)
  const plusButtonElement = screen.getByTestId("plus-button");
  // on/off-button ํด๋ฆญํ•  ๋•Œ, -, + ๋ฒ„ํŠผ์„ ๋ชป๋ˆ„๋ฅด๊ฒŒ ๋ง‰๊ธฐ
  expect(plusButtonElement).toBeDisabled();
});




[Query_์‚ฌ์šฉ์šฐ์„ ์ˆœ์œ„]




  • ์ด์ „์— ํ…Œ์ŠคํŒ…์—์„œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ fireEvent API๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ fireEvent๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์คฌ์ง€๋งŒ userEvent API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. fireEvent.click(element) < userEvent.click(element)

  • userEvent

    • userEvent๋Š” fireEvent ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. userEvent์˜ ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด fireEvent๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์—˜๋ฆฌ๋จผํŠธ์˜ ํƒ€์ž…์— ๋”ฐ๋ผ์„œ Label์„ ํด๋ฆญํ–ˆ์„ ๋•Œ, checkbox, radio ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ๊ทธ ์—˜๋ฆฌ๋จผํŠธ ํƒ€์ž…์— ๋งž๋Š” ๋”์šฑ ์ ์ ˆํ•œ ๋ฐ˜์‘์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

    • ์˜ˆ๋ฅผ ๋“ค์–ด์„œ fireEvent ๋กœ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด fireEvent.click(button) ๋ฒ„ํŠผ์ด focus ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ userEvent๋กœ ํด๋ฆญํ•˜๋ฉด userEvent.click(button) ๋ฒ„ํŠผ์ด focus ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” ์œ ์ €๊ฐ€ ๋ณด๊ธฐ์— ์‹ค์ œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋Š” ํ–‰์œ„๊ฐ€ ๋” ์ž˜ ํ‘œํ˜„๋˜๊ธฐ์— userEvent๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ๋” ์ถ”์ฒœ๋˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.


[userEvent]