🀑

18. Jest (TDD)

Β 

Jestλž€?

μ œμŠ€νŠΈλŠ” ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ°Ύκ³ , ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜κ³ , μ‹€νŒ¨μΈμ§€ 성곡인지λ₯Ό νŒλ‹¨ν•˜λŠ” ν…ŒμŠ€νŠΈ λŸ¬λ„ˆμž…λ‹ˆλ‹€.
facebook μ—μ„œ κ°œλ°œν•˜κ³  κ΄€λ¦¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. (https://jestjs.io/)
제슀트만의 νŠΉμ§•μ€ Html μš”μ†Œλ₯Ό νƒμƒ‰ν•˜λŠ”λ° μ ‘κ·Όμ„± 마컀 즉, ariaλ₯Ό μ΄μš©ν•©λ‹ˆλ‹€. TDD λ₯Ό μž‘μ„±ν•¨μœΌλ‘œμ„œ μžμ—°μŠ€λŸ½κ²Œ 접근성을 ν–₯μƒμ‹œν‚€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€. μ œμŠ€νŠΈκ°€ μš”μ†Œλ₯Ό ariaλ₯Ό 톡해 찾을 수 μžˆλ‹€λŠ”κ²ƒμ€ 슀크린 리더듀도 찾을 수 μžˆλ‹€λŠ” μ–˜κΈ°μž…λ‹ˆλ‹€.
Β 

1. 제슀트λ₯Ό μ‚¬μš©ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

  1. npx λͺ…λ Ήμ–΄λ₯Ό 톡해 λ¦¬μ—‘νŠΈ 앱을 μƒμ„±ν•΄λ΄…μ‹œλ‹€.
npx create-react-app color-button
Β 
  1. react μ„€μΉ˜κ°€ μ™„λ£Œλ˜κ³  ν”„λ‘œμ νŠΈκ°€ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 터미널을 톡해 μ œμ•ˆμ„ ν•΄μ£ΌλŠ”κ΅°μš” πŸ™‚
notion imagenotion image
Β 
  1. μ„€μΉ˜κ°€ μ™„λ£Œλ˜μ—ˆμœΌλ©΄ npm testλ₯Ό ν•΄λ΄…μ‹œλ‹€.
μ œμŠ€νŠΈκ°€ μ™€μΉ˜λͺ¨λ“œλ‘œ μ‹€ν–‰λ˜λŠ”κ²ƒμ„ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
μ™€μΉ˜λͺ¨λ“œλŠ” νŒŒμΌμ— μˆ˜μ • 사항이 감지될 경우 μžλ™μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄μ£ΌλŠ” μƒνƒœλ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.
πŸ’‘
λ§Œμ•½ 제슀트 μ‹€ν–‰ μ‹œ λ…Έλ“œ 버전 이슈둜
notion imagenotion image
이런 μ—λŸ¬κ°€ λ°œμƒν•œλ‹€λ©΄ μ œμŠ€νŠΈμ™€ κ΄€λ ¨λœ νŒ¨ν‚€μ§€λ₯Ό μ„€μΉ˜ ν•΄μ£Όμ…”μ•Ό ν•©λ‹ˆλ‹€.
npm i -D --exact jest-watch-typeahead@0.6.5
Β 
  1. a λ₯Ό 눌러 ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄λ΄…λ‹ˆλ‹€.
μ•„λ¬΄λŸ° ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ μ•Šμ•˜μ§€λ§Œ λ­”κ°€ μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€! γ…Žγ…Ž
일단 qλ₯Ό 눌러 test μƒνƒœμ—μ„œ λΉ μ Έλ‚˜κ°‘λ‹ˆλ‹€.
Β 
Β 

2. 자 이제 λ‚΄λΆ€μ μœΌλ‘œ μ–΄λ–»κ²Œ ν…ŒμŠ€νŠΈκ°€ μ‹€ν–‰λ˜μ—ˆλŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

Β 
  1. μ œμŠ€νŠΈκ°€ μ‹€ν–‰ μ½”λ“œλŠ” App.test.js 에 μžˆμŠ΅λ‹ˆλ‹€.
import { render, screen } from '@testing-library/react'; import App from './App'; test('renders learn react link', () => { render(<App />); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); });
  • Test ν•¨μˆ˜ : κΈ€λ‘œλ²Œ ν•¨μˆ˜. 두 가지 인자λ₯Ό κ°€μ§‘λ‹ˆλ‹€. 첫번째 μΈμžλŠ” λ¬Έμžμ—΄λ‘œ ν…ŒμŠ€νŠΈμ˜ μ„€λͺ…μž…λ‹ˆλ‹€. μžμŠ€λ―Όμ—μ„œλŠ” describe μž…λ‹ˆλ‹€. λ‘λ²ˆμ§Έ μΈμžλŠ” ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜λŠ” ν…ŒμŠ€νŠΈ ν•¨μˆ˜μž…λ‹ˆλ‹€.
  • Render ν•¨μˆ˜ : 인자둜 λ°›λŠ” JSX의 가상돔을 μƒμ„±ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” app μ»΄ν¬λ„ŒνŠΈλ₯Ό μ „λ‹¬λ°›μŠ΅λ‹ˆλ‹€.
  • Screen : μƒμ„±λœ 가상돔에 μ ‘κ·Όν•˜κΈ° μœ„ν•œ μ „μ—­ κ°μ²΄μž…λ‹ˆλ‹€.
πŸ’‘
render와 screen은 λͺ¨λ‘ import { render, screen } from '@testing-library/react'; μ—μ„œ μ™”μŠ΅λ‹ˆλ‹€.
  • getByText ν•¨μˆ˜ : 인자둜 μ „λ‹¬λœ ν…μŠ€νŠΈλ₯Ό κ°€μ§€λŠ” 돔 μ•ˆμ˜ μš”μ†Œλ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” μ •κ·œν‘œν˜„μ‹μ„ μ‚¬μš©ν–ˆκ΅°μš”. /learn react/i 뒀에 뢙은 iλŠ” λŒ€μ†Œλ¬Έμžλ₯Ό κ΅¬λΆ„ν•˜μ§€ μ•Šκ² λ‹€λŠ” μ˜λ―Έμž…λ‹ˆλ‹€.
Β 
  • expect ν•¨μˆ˜ : μ΅μˆ™ν•˜μ£ ? μ œμŠ€λ―Όμ—λ„ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. κΈ°λŒ€ν•œ κ²°κ³Όκ°€ 성곡인지 μ‹€νŒ¨μΈμ§€ νŒλ‹¨ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.
  • .toBeInTheDocument : matcher ν•¨μˆ˜μž…λ‹ˆλ‹€. 제슀민의 .toBe() ν•¨μˆ˜κ°€ κΈ°μ–΅λ‚˜μ‹œλ‚˜μš”? μ œμŠ€νŠΈμ—λ„ μ‘΄μž¬ν•©λ‹ˆλ‹€.
Β 
  1. μ΄λ²ˆμ—λŠ” ν…ŒμŠ€νŠΈλ₯Ό μ‹€νŒ¨ ν•΄λ΄…μ‹œλ‹€. App.js 의 ν…μŠ€νŠΈλ₯Ό β€˜Learn View’ 둜 변경해보고 npm testλ₯Ό μ‹€ν–‰ ν•΄λ΄…λ‹ˆλ‹€.
Β 
  1. μˆ˜μ • ν–ˆλ˜ μ½”λ“œλ₯Ό 원상 λ³΅κ΅¬ν•΄μ„œ μ™€μΉ˜ λͺ¨λ“œκ°€ 잘 μž‘λ™ν•˜λŠ”μ§€λ„ 확인해 λ΄…μ‹œλ‹€. πŸ™‚
Β 
Β 

3.κ°„λ‹¨ν•œ κΈ°λŠ₯ν…ŒμŠ€νŠΈλ₯Ό 직접 ν•΄λ΄…μ‹œλ‹€.

기본적인 문법과 λ‚΄μš©μ— λŒ€ν•΄ μ‚΄νŽ΄λ΄€μŠ΅λ‹ˆλ‹€. 이제 κ°„λ‹¨ν•œ κΈ°λŠ₯을 ν…ŒμŠ€νŠΈ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€!
Β 
  1. App.js μ•ˆμ˜ return ν‚€μ›Œλ“œ μ•ˆμ˜ λ‚΄μš©λ“€κ³Ό App.test.js μ•ˆμ˜ ν…ŒμŠ€νŠΈ ν•¨μˆ˜ μ•ˆμ˜ λ‚΄μš©μ„ λͺ¨λ‘ μ§€μ›λ‹ˆλ‹€.
Β 
  1. λˆ„λ₯΄λ©΄ 색이 λ°”λ€ŒλŠ” λ²„νŠΌμ„ λ§Œλ“€κ³  ν…ŒμŠ€νŠΈ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. App.test.js νŒŒμΌμ— ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
test('λ²„νŠΌμ΄ μ œλŒ€λ‘œ 잘 μž‘λ™ν•˜κ³  μžˆμŠ΅λ‹ˆκΉŒ?', () => { render(<App />); const button = screen.getByRole('button', { name: 'change to blue!' }); expect(button).toHaveStyle({ backgroundColor: 'red' }); });
  • μ½”λ“œμ—μ„œ μš”μ†Œλ₯Ό 찾을 λ•Œ getByRole 둜 μ°ΎλŠ” 것이 λ³΄μž…λ‹ˆλ‹€. role은 ariaμ—μ„œ μ‚¬μš©ν•˜λŠ” μš”μ†Œμ˜ 역할을 μ˜λ―Έν•˜λŠ” μ†μ„±μž…λ‹ˆλ‹€. νŠΉμ • μš”μ†ŒλŠ” role 을 λͺ…μ‹œν•˜μ§€ μ•Šμ•„λ„Β  μ•”λ¬΅μ μœΌλ‘œ 가지고 μžˆλŠ” κ²½μš°λ„ μžˆμŠ΅λ‹ˆλ‹€.(https://www.w3.org/TR/html-aria/#docconformance)
  • role 값이 틀렸을 경우 μΉœμ ˆν•˜κ²Œ μ œμŠ€νŠΈμ—μ„œ μ œμ•ˆμ„ 해주기도 ν•©λ‹ˆλ‹€.
  • 암묡적인 role이 μ—†λŠ” μš”μ†Œμ˜ 경우 role 속성을 직접 λͺ…μ‹œν•΄μ€λ‹ˆλ‹€.
πŸ’‘
role의 μ’…λ₯˜μ— λŒ€ν•΄ κΆκΈˆν•˜λ‹€λ©΄ μ—¬κΈ°λ₯Ό μ°Έκ³ ν•΄λ³΄μ„Έμš”. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles
  • getByRoleν•¨μˆ˜λŠ” λ‘λ²ˆμ§Έ 인자둜, μ°Ύμ•„μ•Όν•  μš”μ†Œ μ•ˆμ˜ ν…μŠ€νŠΈλ₯Ό 지정할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • toHaveStyle ν•¨μˆ˜ : μš”μ†Œκ°€ νŠΉμ •ν•œ CSS μŠ€νƒ€μΌμ„ 가지고 μžˆλŠ”μ§€ μ²΄ν¬ν•©λ‹ˆλ‹€.
πŸ’‘
이런 matcher ν•¨μˆ˜λ“€μ˜ μ’…λ₯˜κ°€ κΆκΈˆν•˜λ‹€λ©΄ μ—¬κΈ°λ₯Ό μ°Έκ³ ν•΄ λ³΄μ„Έμš”.
Β 
  1. 이제 ν…ŒμŠ€νŠΈλ₯Ό 진행해 λ³΄κ² μŠ΅λ‹ˆλ‹€. μ–΄λ–€ κ²°κ³Όκ°€ λ³΄μ΄μ‹œλ‚˜μš”?
Β 
  1. 이제 App.jsλ₯Ό μˆ˜μ •ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œλ₯Ό μΆ”κ°€ν•΄ λ΄…μ‹œλ‹€.
<div> <button style={{ backgroundColor: 'red' }}>change to blue!</button> </div>
πŸ’‘
Jsx μ—μ„œ λ§ˆν¬μ—…μ— ν‘œν˜„μ‹μ„ λ„£κ³ μ‹Άλ‹€λ©΄ μ€‘κ΄„ν˜Έλ₯Ό μ‚¬μš©ν•΄μ•Όν•©λ‹ˆλ‹€.
Β 
  1. λ‹€μ‹œ ν…ŒμŠ€νŠΈλ₯Ό λŒλ €λ΄…μ‹œλ‹€.
Β 
  1. 이제 κΈ°λŠ₯을 μΆ”κ°€ν•΄λ΄…μ‹œλ‹€. λ²„νŠΌμ„ ν΄λ¦­ν–ˆμ„ λ•Œ 색이 λ³€ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” ν…ŒμŠ€νŠΈμž…λ‹ˆλ‹€.
import { render, screen, fireEvent } from '@testing-library/react';
  • import ꡬ문에 fireEventλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€. κ°€μƒλ”κ³Όμ˜ μƒν˜Έμž‘μš©μ΄ κ°€λŠ₯ν•˜λ„λ‘ ν•˜λŠ” κ°μ²΄μž…λ‹ˆλ‹€. κ³„μ†ν•΄μ„œ μ•„λž˜ μ½”λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
fireEvent.click(button); expect(button).toHaveStyle({ backgroundColor: 'blue' }); expect(button.textContent).toBe('change to red!');
Β 
  1. ν…ŒμŠ€νŠΈμ— μ‹€νŒ¨ν•©λ‹ˆλ‹€.
  • 아직 λ²„νŠΌμ„ ν΄λ¦­ν–ˆμ„ λ•Œμ˜ κΈ°λŠ₯을 μΆ”κ°€ν•˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. useState ν•¨μˆ˜λ₯Ό 톡해 ν˜„μž¬μ˜ λ²„νŠΌ 색을 μ €μž₯ν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€. App.js 상단에 import ꡬ문을 μΆ”κ°€ν•©λ‹ˆλ‹€.
import { useState } from 'react';
  • useState ν•¨μˆ˜λŠ” 두 가지 μš”μ†Œλ₯Ό κ°€μ§€λŠ” 배열을 λ°˜ν™˜ν•©λ‹ˆλ‹€. 첫번째 μš”μ†ŒλŠ” state의 κ°’, λ‘λ²ˆμ§ΈλŠ” state의 값을 μ„€μ •ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.
const [buttonColor, setColor] = useState('red');
  • β€˜red’ λ¬Έμžμ—΄μ΄ buttonColor μƒμˆ˜μ˜ 값이 λ©λ‹ˆλ‹€. setColor λŠ” buttonColor μƒμˆ˜μ˜ 값을 λ°”κΏ€ 수 μžˆλŠ” ν•¨μˆ˜λ₯Ό μ°Έμ‘°ν•©λ‹ˆλ‹€.
Β 
  1. 이제 이벀트λ₯Ό λ‹¬μ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.
const [buttonColor, setColor] = useState('red'); const newColor = buttonColor === 'red' ? 'blue' : 'red'; return ( <div> <button style={{ backgroundColor: buttonColor }} onClick={() => setColor(newColor)}>change to {newColor}!</button> </div> );
Β 
  1. ν…ŒμŠ€νŠΈλ₯Ό 돌렀보고 κ²°κ³Όλ₯Ό ν™•μΈν•©λ‹ˆλ‹€.
Β 
  1. 이제 npm start둜 μ‹€μ œλ‘œ 잘 λ™μž‘ν•˜λŠ”μ§€ 확인해볼 λ‹¨κ³„μž…λ‹ˆλ‹€.
Β