(๋ฒˆ์—ญ) Jest, React ๋ฐ Typescript๋ฅผ ์‚ฌ์šฉํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

์›๋ฌธ: Unit testing with Jest, React, and TypeScript

๐Ÿ’ก ์†Œํ”„ํŠธ์›จ์–ด ํ…Œ์ŠคํŠธ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

์†Œํ”„ํŠธ์›จ์–ด ํ…Œ์ŠคํŠธ(Software Testing)์€ ์†Œํ”„ํŠธ์›จ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‚˜ ์ œํ’ˆ์ด ์˜๋„๋œ ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ‰๊ฐ€ํ•˜๊ณ  ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŒ…์„ ํ†ตํ•ด ๋ฒ„๊ทธ ๋ฐฉ์ง€, ๊ฐœ๋ฐœ ๋น„์šฉ ์ ˆ๊ฐ, ์„ฑ๋Šฅ ํ–ฅ์ƒ ๋“ฑ์— ์žฅ์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์ž๋กœ์„œ ์ตœ์ข… ์‚ฌ์šฉ์ž๋ฅผ ์—ผ๋‘์— ๋‘๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธ ํ•จ์œผ๋กœ์จ ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํŒจํ•  ๊ฐ€๋Šฅ์„ฑ์„ ๋‚ฎ์ถ”๋Š” ๊ฒƒ์€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค” ์™œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋‚˜์š”?

  • ์ข‹์€ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ๊ณผ ์†Œํ”„ํŠธ์›จ์–ด ์ง€์› ๋น„์šฉ ์ ˆ๊ฐ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ์ตœ์ข… ์‚ฌ์šฉ์ž์˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑ์‹œํ‚ค๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์†Œํ”„ํŠธ์›จ์–ด์˜ ํ’ˆ์งˆ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ๋” ๋†’์€ ํ’ˆ์งˆ์˜ ์ œํ’ˆ์€ ๋” ๋งŽ์€ ๊ณ ๊ฐ ๋งŒ์กฑ์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค.
  • ๋” ๋งŽ์€ ๊ณ ๊ฐ ๋งŒ์กฑ์€ ๊ธฐ์—…์˜ ์ด์ต์„ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.

๐Ÿค– ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŒ…

์ž๋™ํ™”๋œ ํ…Œ์ŠคํŒ…์€ ์‚ฌ์ „์— ์ค€๋น„๋œ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์žฅ์น˜์— ์˜ํ•ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋Š” ํด๋ž˜์Šค์˜ ๋‹จ์ผ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ๋ถ€ํ„ฐ ์ผ๋ จ์˜ ๋ณต์žกํ•œ UI ์ž‘์—… ์ˆ˜ํ–‰์— ์žˆ์–ด ๋™์ผํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ๊นŒ์ง€ ๋ณต์žก์„ฑ์ด ํ›จ์”ฌ ๋‹ค์–‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ˆ˜๋™ ํ…Œ์ŠคํŠธ๋ณด๋‹ค ๋” ๊ฐ•๋ ฅํ•˜๊ณ  ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ–ฅ ํ…Œ์ŠคํŒ…์˜ ์ข…๋ฅ˜

1) ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Testing)

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๋งค์šฐ ๊ธฐ๋ณธ์ ์ด๋ฉฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์†Œ์Šค ์ฝ”๋“œ์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋Š” ์†Œํ”„ํŠธ์›จ์–ด์˜ ๋ฉ”์„œ๋“œ์™€ ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ ํด๋ž˜์Šค, ์ปดํฌ๋„ŒํŠธ ๋˜๋Š” ๋ชจ๋“ˆ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ž‘์—…์ด ์ˆ˜๋ฐ˜๋ฉ๋‹ˆ๋‹ค. ์ง€์†์  ํ†ตํ•ฉ ์„œ๋ฒ„๋Š” ๋น„๊ต์  ์ €๋ ดํ•œ ๋น„์šฉ์œผ๋กœ ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2) ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(Integration Testing)

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‹ค์–‘ํ•œ ์ปดํฌ๋„ŒํŠธ๋‚˜ ์„œ๋น„์Šค๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํ˜ธ ์ž‘์šฉ์„ ํ…Œ์ŠคํŠธํ•˜๊ฑฐ๋‚˜ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๊ฐ€ ์˜๋„ํ•œ ๋Œ€๋กœ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋„๋ก ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์—ฌ๋Ÿฌ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์ž‘๋™ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์ถ”๊ฐ€์ ์ธ ๋น„์šฉ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

3) ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ(Functional Testing)

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ ์‚ฌํ•ญ์€ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ์˜ ์ฃผ์š” ์ดˆ์ ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ˆ˜ํ–‰๋˜๋Š” ๋™์•ˆ ์‹œ์Šคํ…œ์˜ ์ค‘๊ฐ„ ์ƒํƒœ๋ฅผ ์ฒดํฌํ•˜์ง€ ์•Š๊ณ  ์ˆ˜ํ–‰์˜ ๊ฒฐ๊ณผ๋งŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

๋‘˜ ๋‹ค ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์™€ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ๋Š” ์ข…์ข… ํ˜ผ๋™๋˜๊ณค ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ œํ’ˆ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํŠน์ • ๊ฐ’์„ ์–ป์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•˜์ง€๋งŒ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ฟผ๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋งŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

4) ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ(End-to-end Testing - E2E)

์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ๋งฅ๋ฝ์—์„œ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค. ์›น ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ๋กœ๊ทธ์ธํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๊ฐ„๋‹จํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋‚˜ ์ด๋ฉ”์ผ ์•Œ๋ฆผ, ์˜จ๋ผ์ธ ๊ฒฐ์ œ์ฒ˜๋Ÿผ ๋‹ค์†Œ ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•ด์„œ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ์ž ํ๋ฆ„์ด ์˜๋„ํ•œ ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ๋Š” ๋งค์šฐ ์œ ์šฉํ•˜์ง€๋งŒ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๋น„์šฉ์ด ๋งŽ์ด ๋“ค๊ณ , ์ž๋™ํ™”๋œ ๊ฒฝ์šฐ์—๋Š” ๊ด€๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋น ๋ฅด๊ฒŒ ๋ฐœ๊ฒฌํ•˜๋ ค๋ฉด, ์ œํ•œ๋œ ์ˆ˜์˜ ์ค‘์š”ํ•œ ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ–๊ณ  ํ•˜์œ„ ์ˆ˜์ค€์˜ ํ…Œ์ŠคํŠธ(๋‹จ์œ„ ๋ฐ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ)์— ๋” ์˜์กดํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

5) ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ(Validation Testing)

์ธ์ˆ˜ ํ…Œ์ŠคํŠธ๋Š” ์‹œ์Šคํ…œ์ด ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ณต์‹์ ์œผ๋กœ ๊ฒฐ์ •ํ•˜๋Š” ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ๋™์•ˆ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ฉด์„œ ์‚ฌ์šฉ์ž ๋™์ž‘์„ ์žฌํ˜„ํ•˜๋Š” ๋ฐ ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค. ํ•œ ๋‹จ๊ณ„ ๋” ๋‚˜์•„๊ฐ€ ์‹œ์Šคํ…œ์˜ ํšจ์œจ์„ฑ์„ ํ‰๊ฐ€ํ•˜๊ณ  ํŠน์ •ํ•œ ๋ชฉํ‘œ๊ฐ€ ๋‹ฌ์„ฑ๋˜์ง€ ์•Š์œผ๋ฉด ๊ฐœ์„ ์„ ๊ฑฐ๋ถ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

6) ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ(Performance Testing)

์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ๋Š” ํŠน์ • ์›Œํฌ๋กœ๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ์˜ ๋Šฅ๋ ฅ์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ‰๊ฐ€๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹ ๋ขฐ์„ฑ, ์†๋„, ํ™•์žฅ์„ฑ ๋ฐ ์‘๋‹ต์„ฑ์„ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ๋Š” ๋งŽ์€ ์ˆ˜์˜ ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋  ๋•Œ ์‘๋‹ต ์‹œ๊ฐ„์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ฑฐ๋‚˜ ์‹œ์Šคํ…œ์ด ์ƒ๋‹นํ•œ ์–‘์˜ ๋ฐ์ดํ„ฐ์— ์–ด๋–ป๊ฒŒ ์‘๋‹ตํ•˜๋Š”์ง€ ํ‰๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์ด ์„ฑ๋Šฅ ํ‘œ์ค€์„ ์ถฉ์กฑํ•˜๋Š”์ง€ ํ‰๊ฐ€ํ•˜๊ณ , ๋ณ‘๋ชฉ ํ˜„์ƒ์„ ์ฐพ๊ณ , ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์„ ๋•Œ ์•ˆ์ •์„ฑ์„ ์ธก์ •ํ•˜๊ณ , ํ›จ์”ฌ ๋” ๋งŽ์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

7) ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ(Smoke Test)

์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ธฐ๋ณธ ์ž‘๋™์„ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ์ด ํ…Œ์ŠคํŠธ๋Š” ์‹ ์†ํ•˜๊ฒŒ ์ˆ˜ํ–‰๋˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ์ฃผ์š” ์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณ„ํš๋Œ€๋กœ ์ž‘๋™ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ํ™•์‹ ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์— ๋ชฉ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ๋Š” ๋” ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ ๋นŒ๋“œ๋ฅผ ๋งŒ๋“  ์งํ›„์—, ๋˜๋Š” ์ƒˆ๋กœ ๋ฐฐํฌ๋œ ํ™˜๊ฒฝ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋ฐฐํฌ ์งํ›„์— ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงช ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(TDD)

ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์—์„œ๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ์ฝ”๋“œ๋ณด๋‹ค ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์€ ๋น ๋ฅธ ๊ฐœ๋ฐœ ์ฃผ๊ธฐ๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๋ฐ ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ์ž๋™ํ™”๋œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค๊ณ„ ๋ฐ ์ข…์†์„ฑ์˜ ๋ฌด์ œํ•œ ๋ถ„๋ฆฌ๋ฅผ ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์˜ ๋ชจํ† ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๋นจ๊ฐ•(Red) : ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์‹คํŒจ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ๋…น์ƒ‰(Green) : ์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ๋“  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ํ†ต๊ณผ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ๋ฆฌํŒฉํ„ฐ๋ง(Refactor) : ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ์„ ์‚ฌ์šฉํ•  ๋•Œ, ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

โš™ ๊ธฐ๋ณธ ์„ค์ •

๋จผ์ € ํƒ€์ž… ์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ฐ˜์˜ ๋ฆฌ์•กํŠธ ์•ฑ์„ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

npx create-react-app jest-example --template typescript

์ด์ œ package.json์„ ์—ด๋ฉด jest-dom๊ณผ testing-library/react๊ฐ€ ์ด๋ฏธ ์ข…์†์„ฑ์œผ๋กœ ์„ค์น˜๋˜์–ด ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • jest-dom : ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” jest๋ฅผ ํ™•์žฅํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ปค์Šคํ…€ jest ๋งค์ฒ˜(Matcher)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ๋” ์„ ์–ธ์ ์ด๊ณ , ๋ช…ํ™•ํ•˜๊ฒŒ ์ฝ๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • testing-library/react : ๋ธŒ๋ผ์šฐ์ €์—์„œ React์— ์˜ํ•ด ๋ Œ๋”๋ง ๋œ ์‹ค์ œ DOM ํŠธ๋ฆฌ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ํ…Œ์ŠคํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ชฉํ‘œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์œ ์‚ฌํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋„๋ก ๋•๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

src ๋””๋ ‰ํ„ฐ๋ฆฌ์—๋Š” ๋ชจ๋“  ์•ฑ ์ฝ”๋“œ์™€ App.test.tsx๋ผ๋Š” ํŒŒ์ผ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์—๋Š” App ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งํฌ๋ฅผ ๋ Œ๋”๋ง ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์–ด๋–ค ์—ญํ• ์„ ํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

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

// ํ…Œ์ŠคํŠธ์˜ ๋ชฉ์ ์„ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
test('renders learn react link', () => {
  // ์ค€๋น„(Arrange): ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ๊ณผ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  render(<App />);

  // ์‹คํ–‰(Act): ์˜ˆ์ƒ๋˜๋Š” ๋งํฌ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.
  const linkElement = screen.getByText(/learn react/i);

  // ๊ฒ€์ฆ(Assert): ๋ฌธ์„œ์— ํ•„์š”ํ•œ ๋งํฌ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  expect(linkElement).toBeInTheDocument();
});

์ด์ œ ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

npm run test

๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด์ œ App.tsx์„ ๋ณ€๊ฒฝํ•˜๊ณ  ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ์‹คํŒจ์‹œํ‚ต๋‹ˆ๋‹ค. App.tsx ํŒŒ์ผ๋กœ ์ด๋™ํ•˜์—ฌ, โ€œLearn Reactโ€๋ฅผ โ€œLetโ€™s Learnโ€์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง€๋Š” ์ด์œ ๋Š” DOM์— โ€œlearn reactโ€ ๋ฌธ์ž์—ด์„ ํฌํ•จํ•˜๋Š” ์š”์†Œ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. โ€œlearn reactโ€ ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋œ ์ƒˆ๋กœ์šด ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์‹œ ์„ฑ๊ณตํ•ฉ๋‹ˆ๋‹ค.

โœ ์ฒซ ๋ฒˆ์งธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•ด๋ณด๊ธฐ

์ด์ œ App.tsx๋กœ ์ด๋™ํ•˜์—ฌ ์ƒˆ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

export function add(a: number, b: number): number {
  return a + b;
}

์ด์ œ App.test.tsx๋กœ ์ด๋™ํ•˜์—ฌ ์ƒˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

describe('add function', () => {
  describe('when given to integers', () => {
    it('should return a add result', () => {
      // ์ค€๋น„(Arrange): ์˜ˆ์ƒ๋˜๋Š” ๋ง์…ˆ ๊ฒฐ๊ณผ์™€ ํ•จ์ˆ˜ ์ธ์ˆ˜๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
      // ์ด ์˜ˆ์‹œ์—์„œ๋Š” 5 + 8์ด 13์ด ๋œ๋‹ค๋Š” ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค.
      const [a, b, expected] = [5, 8, 13];

      // ์—ฌ๊ธฐ์„œ๋Š”, ๋ฐฐ์—ด ๋น„๊ตฌ์กฐํ™”๋ฅผ ํ™œ์šฉํ•˜์—ฌ "a === 5," "b === 8," ๊ณผ "expected === 13"๊ณผ ๊ฐ™์ด ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

      // ์‹คํ–‰(Act): ์ฐธ์ธ ํ•จ์ˆ˜ ๊ฒฐ๊ณผ๋ฅผ ์–ป๊ธฐ์œ„ํ•ด add ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
      const result = add(a, b);

      // ๊ฒ€์ฆ(Assert): ์ด์ œ ํ•จ์ˆ˜์˜ ์ถœ๋ ฅ๊ณผ ์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
      expect(result).toEqual(expected);
    });
  });
});

์ด์ œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ›  ์ด์ œ ๋ช‡ ๊ฐ€์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•ด ๋ด…์‹œ๋‹ค

์ด์ œ ๋ช‡ ๊ฐ€์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค. App.tsx์—์„œ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

export function Login() {
  return (
    <div>
      <div>
        <input type="email" name="email" placeholder="email" />
      </div>
      <div>
        <input type="password" name="password" placeholder="password" />
      </div>

      <div>
        <button type="button">Sign In</button>
        <button type="button">Sign Up</button>
      </div>
    </div>
  );
}

์ด์ œ App.test.tsx๋กœ ์ด๋™ํ•˜์—ฌ ์ด ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

describe('Login component tests', () => {
  let container: HTMLDivElement;

  // beforeEach: ์ด ํŒŒ์ผ์˜ ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ด ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜๊ฐ€ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์ƒ์„ฑ์ž์ธ ๊ฒฝ์šฐ, jest๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ํ•ด๋‹น promise๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
  beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
    ReactDOM.render(<Login />, container);
  });

  // ํ…Œ์ŠคํŠธ๊ฐ€ ์„œ๋กœ ๋ฐฉํ•ด๋˜์ง€ ์•Š๋„๋ก ๋งˆ์ง€๋ง‰์— ๋ชจ๋“  ๊ฒƒ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
  afterEach(() => {
    document.body.removeChild(container);
    container.remove();
  });

  // ๊ฐ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  it('Renders all input fields correctly', () => {
    // ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
    const inputs = container.querySelectorAll('input');
    // ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    expect(inputs).toHaveLength(2);

    // ์ฒซ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ ๋ฐ ๋‘ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๊ฐ๊ฐ "์ด๋ฉ”์ผ" ๋ฐ "์•”ํ˜ธ"์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    expect(inputs[0].name).toBe('email');
    expect(inputs[1].name).toBe('password');
  });

  // ๊ฐ ๋ฒ„ํŠผ์„ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  it('Renders all buttons correctly', () => {
    const buttons = container.querySelectorAll('button');
    expect(buttons).toHaveLength(2);

    expect(buttons[0].type).toBe('button');
    expect(buttons[1].type).toBe('button');
  });
});

์™„์„ฑ๋œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import React from 'react';
import { render, screen } from '@testing-library/react';
import App, { add, Login } from './App';
import * as ReactDOM from 'react-dom';

// ํ…Œ์ŠคํŠธ์˜ ๋ชฉ์ ์„ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
test('renders learn react link', () => {
  // ์ค€๋น„(Arrange): ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ๊ณผ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  render(<App />);

  // ์‹คํ–‰(Act): ์˜ˆ์ƒ๋˜๋Š” ๋งํฌ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.
  const linkElement = screen.getByText(/learn react/i);

  // ๊ฒ€์ฆ(Assert): ๋ฌธ์„œ์— ํ•„์š”ํ•œ ๋งํฌ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  expect(linkElement).toBeInTheDocument();
});

describe('add function', () => {
  describe('when given to integers', () => {
    it('should return a add result', () => {
      // ์ค€๋น„(Arrange): ์˜ˆ์ƒ๋˜๋Š” ๋ง์…ˆ ๊ฒฐ๊ณผ์™€ ํ•จ์ˆ˜ ์ธ์ˆ˜๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
      // ์ด ์˜ˆ์‹œ์—์„œ๋Š” 5 + 8์ด 13์ด ๋œ๋‹ค๋Š” ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค.
      const [a, b, expected] = [5, 8, 13];

      // ์—ฌ๊ธฐ์„œ๋Š”, ๋ฐฐ์—ด ๋น„๊ตฌ์กฐํ™”๋ฅผ ํ™œ์šฉํ•˜์—ฌ "a === 5," "b === 8," ๊ณผ "expected === 13"๊ณผ ๊ฐ™์ด ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

      // ์‹คํ–‰(Act): ์ฐธ์ธ ํ•จ์ˆ˜ ๊ฒฐ๊ณผ๋ฅผ ์–ป๊ธฐ์œ„ํ•ด add ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
      const result = add(a, b);

      // ๊ฒ€์ฆ(Assert): ์ด์ œ ํ•จ์ˆ˜์˜ ์ถœ๋ ฅ๊ณผ ์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
      expect(result).toEqual(expected);
    });
  });
});

describe('Login component tests', () => {
  let container: HTMLDivElement;

  // beforeEach: ์ด ํŒŒ์ผ์˜ ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ด ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜๊ฐ€ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์ƒ์„ฑ์ž์ธ ๊ฒฝ์šฐ, jest๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ํ•ด๋‹น promise๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
  beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
    ReactDOM.render(<Login />, container);
  });

  // ํ…Œ์ŠคํŠธ๊ฐ€ ์„œ๋กœ ๋ฐฉํ•ด๋˜์ง€ ์•Š๋„๋ก ๋งˆ์ง€๋ง‰์— ๋ชจ๋“  ๊ฒƒ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
  afterEach(() => {
    document.body.removeChild(container);
    container.remove();
  });

  // ๊ฐ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  it('Renders all input fields correctly', () => {
    // ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
    const inputs = container.querySelectorAll('input');
    // ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    expect(inputs).toHaveLength(2);

    // ์ฒซ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ ๋ฐ ๋‘ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๊ฐ๊ฐ "์ด๋ฉ”์ผ" ๋ฐ "์•”ํ˜ธ"์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    expect(inputs[0].name).toBe('email');
    expect(inputs[1].name).toBe('password');
  });

  // ๊ฐ ๋ฒ„ํŠผ์„ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  it('Renders all buttons correctly', () => {
    const buttons = container.querySelectorAll('button');
    expect(buttons).toHaveLength(2);

    expect(buttons[0].type).toBe('button');
    expect(buttons[1].type).toBe('button');
  });
});

๐Ÿฅ‡ ์ตœ์ข… ๊ฒฐ๊ณผ

์ด์ œ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผ๋˜์—ˆ์œผ๋ฏ€๋กœ, ๋” ๋งŽ์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿค ๊ฒฐ๋ก 

๋Œ€๊ทœ๋ชจ ์•ฑ ์ผ์ˆ˜๋ก ํŠนํžˆ ์œ ์ง€ ๋ณด์ˆ˜ ๋ฐ ์†Œํ”„ํŠธ์›จ์–ด ์ง€์› ๋น„์šฉ์„ ์ ˆ๊ฐํ•˜๊ธฐ ์œ„ํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…Œ์ŠคํŠธ๋Š” ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. ์ด ๋ธ”๋กœ๊ทธ์—์„œ๋Š” Jest, React ๋ฐ Typescript๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ๋งŽ์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์œ„ํ•œ ๋” ๋งŽ์€ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. ์ „์ฒด ์†Œ์Šค ์ฝ”๋“œ๋Š” Github์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿš€ ํ•œ๊ตญ์–ด๋กœ ๋œ ํ”„๋ŸฐํŠธ์—”๋“œ ์•„ํ‹ฐํด์„ ๋น ๋ฅด๊ฒŒ ๋ฐ›์•„๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด Korean FE Article(https://kofearticle.substack.com/)์„ ๊ตฌ๋…ํ•ด์ฃผ์„ธ์š”!


[Ykss]
Written by@[Ykss]
๊ณ ์ด๊ฒŒ ๋‘์ง€ ์•Š๊ณ  ํ˜๋ ค๋ณด๋‚ด๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์ž.

GitHubInstagramLinkedIn