λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ“‚ Project/μš°μ•„ν•œν…Œν¬μ½”μŠ€

[μš°μ•„ν•œν…Œν¬μ½”μŠ€] μš°ν…Œμ½” 숫자 야ꡬ ν…ŒμŠ€νŠΈ μ½”λ“œ λΆ„μ„ν•˜κΈ°

by Dev. Ella 2023. 11. 24.

πŸ““ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό λΆ„μ„ν•˜λŠ” 이유

문제λ₯Ό ν’€μ–΄λ‚΄κΈ° μœ„ν•΄μ„œλŠ” κ·Έ λ¬Έμ œμ—μ„œ μš”κ΅¬ν•˜λŠ” λ°”λ₯Ό μ •ν™•νžˆ μ•Œμ•„μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. μ΅œμ’… μ½”λ”© ν…ŒμŠ€νŠΈ λ•Œ κΈ°λŠ₯ μš”κ΅¬ 사항과 λ”λΆˆμ–΄, ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œ μš”κ΅¬ν•˜λŠ” λ°”λ₯Ό μ •ν™•ν•˜κ³  λΉ λ₯΄κ²Œ μ•Œκ³ μž μ½”λ“œλ₯Ό 샅샅이 νŒŒν—€μ³λ³΄κ³ μž ν•œλ‹€. 더 λ‚˜μ•„κ°€, μ•žμœΌλ‘œ λ‹¨μœ„ ν…ŒμŠ€νŠΈλΏλ§Œ μ•„λ‹ˆλΌ 직접 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλ„ 짜보고 μ‹Άμ—ˆλ‹€.

 

ν”„λ¦¬μ½”μŠ€ κΈ°κ°„μ—λŠ” 'κ·Έλ ‡κ΅¬λ‚˜~' ν•˜κ³  λ„˜κ²Όλ˜ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό ν”„λ¦¬μ½”μŠ€ μ’…λ£Œ 후에 μ‹œκ°„μ„ λ‚΄μ–΄ 깊게 νŒŒλ³΄μ•˜λ‹€. λ”λΆˆμ–΄, Jestλ‚˜ ν…ŒμŠ€νŠΈ μ½”λ“œ κ΄€λ ¨ κ°œλ…λ„ ν•¨κ»˜ 곡뢀해 λ³΄μ•˜λ‹€. 참고둜 전체 ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” μš°μ•„ν•œν…Œν¬μ½”μŠ€ repoμ—μ„œλ„ λ³Ό 수 μžˆλ‹€.

 

 

1️⃣ μ‹œμž‘! 일단 뢈러였기

// App μ»΄ν¬λ„ŒνŠΈλ₯Ό 뢈러옴
import App from '../src/App.js'; 
// @woowacourse/mission-utils λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ MissionUtils 객체λ₯Ό 뢈러옴
import { MissionUtils } from '@woowacourse/mission-utils';

 

μš°μ„ , ν…ŒμŠ€νŠΈν•˜κ³ μž ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό 뢈러였고, μš°ν…Œμ½”μ—μ„œ μ œκ³΅ν•˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ MissionUtilsλΌλŠ” 객체λ₯Ό λΆˆλŸ¬μ˜¨λ‹€.

 

 

2️⃣ μ‚¬μš©μž μž…λ ₯값에 λŒ€ν•΄ ν…ŒμŠ€νŠΈν•˜κΈ° - mock μ΄λž€?

이 ν…ŒμŠ€νŠΈλŠ” λ‹¨μˆœνžˆ 'ν•˜λ‚˜μ˜ λ©”μ„œλ“œκ°€ 잘 μž‘λ™ν•˜λŠλƒ'λ₯Ό κ²€μ¦ν•˜λŠ” 것이 μ•„λ‹ˆλΌ, μ‚¬μš©μžκ°€ 인풋을 λ„£μ—ˆμ„ λ•Œ 그에 λ§žλŠ” 아웃풋이 λ‚˜μ˜€λŠ”μ§€λ₯Ό κ²€μ¦ν•˜λŠ” 것이닀. 그런데 μ—¬κΈ°μ„œ μ‚¬μš©μžμ˜ 인풋을 μ–΄λ–»κ²Œ λ§Œλ“€κΉŒ?

 

λ°”λ‘œ 'λͺ¨μ˜(mock)'ν•˜λŠ” 방법이 μžˆλ‹€. 즉, 흉내 λ‚΄μ–΄ μ‹€μ œμ²˜λŸΌ ν•΄λ³΄λŠ” 것이닀.

λͺ¨μ˜ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ ν•¨μˆ˜μ˜ μ‹€μ œ κ΅¬ν˜„μ„ μ‚­μ œν•˜κ³ , μ½”λ“œ κ°„μ˜ μƒν˜Έ μž‘μš©μ„ μ‰½κ²Œ ν…ŒμŠ€νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€. mock ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ 호좜 μ‹œ ν•¨μˆ˜μ— μ „λ‹¬λ˜λŠ” μΈμžμ™€ ν•¨κ»˜ newλ₯Ό 톡해 생성할 λ•Œ μΈμŠ€ν„΄μŠ€λ„ *μΊ‘μ²˜ν•  수 있으며, ν…ŒμŠ€νŠΈ κ³Όμ • 쀑 return 값을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (μ°Έκ³ : Jest κ³΅μ‹λ¬Έμ„œ Mock Functions)

 

β†ͺ️ μ—¬κΈ°μ„œ λ§ν•˜λŠ” *μΊ‘μ²˜λŠ” Jest κ³΅μ‹λ¬Έμ„œ λ²ˆμ—­ 말투라 이해가 μ–΄λ €μšΈ 수 μžˆλ‹€. μ‰½κ²Œ ν’€μ–΄ λ§ν•˜μžλ©΄ ν•¨μˆ˜κ°€ 호좜될 λ•Œ κ·Έ ν•¨μˆ˜μ— μ „λ‹¬λ˜λŠ” μΈμžλ“€μ„ κΈ°λ‘ν•˜κ³ , λ‚˜μ€‘μ— 이 μΈμžλ“€μ„ 검사할 수 있게 ν•˜λŠ” 것을 μ˜λ―Έν•œλ‹€κ³  보면 λœλ‹€. 

 

Jestμ—μ„œ λͺ¨μ˜ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ ν•΄λ‹Ή ν•¨μˆ˜μ˜ μ‹€μ œ κ΅¬ν˜„μ„ λŒ€μ²΄ν•˜κ³ , ν•¨μˆ˜ 호좜과 κ·Έ ν˜ΈμΆœμ— μ‚¬μš©λœ μΈμžλ“€μ„ μΆ”μ ν•œλ‹€. 이λ₯Ό 톡해 κ°œλ°œμžλŠ” ν•¨μˆ˜κ°€ μ˜ˆμƒλŒ€λ‘œ ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€, μ˜¬λ°”λ₯Έ μΈμžλ“€μ΄ μ „λ‹¬λ˜μ—ˆλŠ”μ§€ 등을 ν…ŒμŠ€νŠΈ μ½”λ“œ λ‚΄μ—μ„œ 확인할 수 μžˆλ‹€.

 

μˆ«μžμ•Όκ΅¬ ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œλ„ μ•„λž˜μ™€ 같이 μž…λ ₯값을 λͺ¨μ˜ν•˜λŠ” mockQuestions ν•¨μˆ˜λ₯Ό λ¨Όμ € μ •μ˜ν•œλ‹€.

 

// μž…λ ₯값을 λͺ¨μ˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό μ •μ˜
const mockQuestions = (inputs) => { 
  // MissionUtils.Console.readLineAsync ν•¨μˆ˜λ₯Ό κ°€μ§œ ν•¨μˆ˜λ‘œ λŒ€μ²΄
  MissionUtils.Console.readLineAsync = jest.fn(); 

  // readLineAsync ν•¨μˆ˜μ˜ λ™μž‘μ„ κ΅¬ν˜„ν•¨ - μž…λ ₯κ°’ λ°°μ—΄μ—μ„œ 순차적으둜 값을 κ°€μ Έμ˜΄
  MissionUtils.Console.readLineAsync.mockImplementation(() => { 
    const input = inputs.shift();
    return Promise.resolve(input);
  });
};

 

λͺ¨μ˜ ν•¨μˆ˜λ₯Ό μ •μ˜ν•˜κ³  λ‚˜μ„œλŠ” MissionUtils.Console.readLineAsync ν•¨μˆ˜λ₯Ό κ°€μ§œ ν•¨μˆ˜λ‘œ λŒ€μ²΄ν•œλ‹€. μ—¬κΈ°μ„œ μ‚¬μš©λ˜λŠ” jest.fn( )와 mockImplementation( )은 μ–΄λ–€ κΈ°λŠ₯을 ν• κΉŒ?

κ΅¬ν˜„μ΄ jest.fn()에 μ „λ‹¬λ˜λ©΄ μ˜¬λ°”λ₯Έ λͺ¨μ˜ 타이핑이 μΆ”λ‘ λ©λ‹ˆλ‹€. κ΅¬ν˜„μ΄ μƒλž΅λ˜λŠ” μ‚¬μš© 사둀가 많이 μžˆμŠ΅λ‹ˆλ‹€. μœ ν˜• μ•ˆμ „μ„±μ„ 보μž₯ν•˜κΈ° μœ„ν•΄ 일반 μœ ν˜• 인수λ₯Ό 전달할 수 μžˆμŠ΅λ‹ˆλ‹€. (μ°Έκ³ : Jest κ³΅μ‹λ¬Έμ„œ jest.fn)

λͺ¨μ˜ κ΅¬ν˜„μœΌλ‘œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ” ν•¨μˆ˜λ₯Ό ν—ˆμš©ν•©λ‹ˆλ‹€. λͺ¨μ˜ 개체 μžμ²΄λŠ” λ“€μ–΄μ˜€λŠ” λͺ¨λ“  호좜과 κ·Έ μžμ²΄μ—μ„œ λ‚˜μ˜€λŠ” μΈμŠ€ν„΄μŠ€λ₯Ό λͺ¨λ‘ κΈ°λ‘ν•©λ‹ˆλ‹€. μœ μΌν•œ 차이점은 λͺ¨μ˜ κ°œμ²΄κ°€ 호좜될 λ•Œ κ΅¬ν˜„λ„ μ‹€ν–‰λœλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. (μ°Έκ³ : Jest κ³΅μ‹λ¬Έμ„œ mockImplementation)

 

β†ͺ️ κ³΅μ‹λ¬Έμ„œμ—μ„œ λ²ˆμ—­ 말투둜 λ‹€μ†Œ μ–΄λ ΅κ²Œ μ„€λͺ…ν•˜κ³  μžˆμ§€λ§Œ, ν’€μ–΄ μ„€λͺ…ν•˜μžλ©΄ jest.fn( )은 Jestμ—μ„œ μ œκ³΅ν•˜λŠ” λͺ¨μ˜ ν•¨μˆ˜λ₯Ό μƒμ„±ν•˜λŠ” ν•¨μˆ˜λ‹€. 그리고 mockImplementation( )λŠ” λͺ¨μ˜ ν•¨μˆ˜μ˜ κ΅¬ν˜„μ„ μ •μ˜ν•˜κ³ , κ½€λ‚˜ λ³΅μž‘ν•œ λ‘œμ§κΉŒμ§€ λŒ€μ²΄ν•  수 μžˆλ‹€. μ΄λ ‡κ²Œ 되면 readLineAsync ν•¨μˆ˜ λŒ€μ‹ , κ°€μ§œ ν•¨μˆ˜κ°€ μ‚¬μš©λ˜μ–΄ ν…ŒμŠ€νŠΈ λ™μ•ˆ μ‹€μ œ μ‚¬μš©μž μž…λ ₯을 λ°›λŠ” λŒ€μ‹  κ°€μ§œ input 인자λ₯Ό λ°›μ•„ μ‚¬μš©ν•  수 있게 λœλ‹€.

 

MissionUtils.Console.readLineAsync.mockImplementation(() => { 
  const input = inputs.shift();
  return Promise.resolve(input);

 

β†ͺ️ μ΄ λΆ€λΆ„λ§Œ λ”°λ‘œ λ–Όμ–΄ μ™€μ„œ λ‹€μ‹œ 보겠닀. μš°μ„  readLineAsync ν•¨μˆ˜μ˜ κ°€μ§œ κ΅¬ν˜„μ„ μ„€μ •ν•˜λŠ”λ° inputs λ°°μ—΄μ—μ„œ shift( )λ₯Ό μ‚¬μš©ν•΄ 첫 번째 μš”μ†Œλ₯Ό μ œκ±°ν•˜κ³ , 이 값을 λ°˜ν™˜ν•œλ‹€. readLineAsyncλŠ” 비동기 ν•¨μˆ˜μ΄λ―€λ‘œ, λ°˜ν™˜λœ 값은 Promise.resolve(input)λ₯Ό μ‚¬μš©ν•˜μ—¬ ν”„λ‘œλ―ΈμŠ€λ‘œ 감싸져 λ°˜ν™˜λœλ‹€.

 

 

3️⃣ 예츑 κ°€λŠ₯ν•œ 결과둜 ν…ŒμŠ€νŠΈν•˜κΈ°

// 랜덀 숫자λ₯Ό λͺ¨μ˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό μ •μ˜
const mockRandoms = (numbers) => { 
  // MissionUtils.Random.pickNumberInRange ν•¨μˆ˜λ₯Ό κ°€μ§œ ν•¨μˆ˜λ‘œ λŒ€μ²΄
  MissionUtils.Random.pickNumberInRange = jest.fn();
  // pickNumberInRange ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ„ 순차적으둜 μ •μ˜
  numbers.reduce((acc, number) => { 
    return acc.mockReturnValueOnce(number);
  }, MissionUtils.Random.pickNumberInRange);
};

 

β†ͺ️ μˆ«μž 야ꡬ λ―Έμ…˜μ—μ„œλŠ” Random.pickNumberInRange (랜덀 숫자 생성 ν•¨μˆ˜)λ₯Ό μ‚¬μš©ν•˜λŠ”λ°, ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œλŠ” Jestλ₯Ό μ‚¬μš©ν•΄ 이 ν•¨μˆ˜λ₯Ό mock ν•œλ‹€. mockRandomsλΌλŠ” μ΄λ¦„μœΌλ‘œ μ„ μ–Έλœ ν•¨μˆ˜λŠ” ν…ŒμŠ€νŠΈ 쀑에 νŠΉμ • λ²”μœ„ λ‚΄μ—μ„œ 랜덀 숫자λ₯Ό μ„ νƒν•˜λŠ” MissionUtils.Random.pickNumberInRange ν•¨μˆ˜μ˜ λ™μž‘μ„ λŒ€μ²΄ν•œλ‹€. 

 

μœ„μ˜ MissionUtils.Console.readLineAsync ν•¨μˆ˜λ₯Ό κ°€μ§œ ν•¨μˆ˜λ‘œ λŒ€μ²΄ν•œ κ²ƒμ²˜λŸΌ, MissionUtils.Random.pickNumberInRange ν•¨μˆ˜ λ˜ν•œ Jest의 jest.fn()을 μ‚¬μš©ν•˜μ—¬ κ°€μ§œ ν•¨μˆ˜λ‘œ λŒ€μ²΄ν•œλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ μ›λž˜μ˜ pickNumberInRange ν•¨μˆ˜ λŒ€μ‹  κ°€μ§œ ν•¨μˆ˜κ°€ μ‚¬μš©λ˜μ–΄, 랜덀 숫자 생성을 λͺ¨μ˜ν•  수 μžˆλ‹€.

 

numbers.reduce((acc, number) => { 
  return acc.mockReturnValueOnce(number);
}, MissionUtils.Random.pickNumberInRange);

 

β†ͺ️ λͺ¨μ˜ ν•¨μˆ˜μ˜ 리턴 값을 μ„€μ •ν•˜λŠ” μœ„ λΆ€λΆ„λ§Œ λ”°λ‘œ λ–Όμ–΄ μ™€μ„œ λ‹€μ‹œ 보겠닀. μ—¬κΈ°μ„œλŠ” μœ„μ˜ mockRandoms ν•¨μˆ˜κ°€ 인자둜 받은 numbersλΌλŠ” 배열을 μ‚¬μš©ν•œλ‹€. 이 배열은 λͺ¨μ˜ν•  λ•Œ λ°˜ν™˜λ  랜덀 μˆ«μžλ“€μ„ ν¬ν•¨ν•œλ‹€.

 

μš°μ„  numbers  λΌλŠ” 배열을 μˆœνšŒν•˜λ©΄μ„œ reduce ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•œλ‹€. 이 κ³Όμ •μ—μ„œ MissionUtils.Random.pickNumberInRange ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ„ numbers λ°°μ—΄μ˜ 각 μš”μ†Œλ‘œ 순차적으둜 μ„€μ •ν•œλ‹€.


그리고 mockReturnValueOnce λ©”μ„œλ“œλŠ” ν•¨μˆ˜κ°€ 호좜될 λ•Œλ§ˆλ‹€ μ§€μ •λœ μˆœμ„œλŒ€λ‘œ ν•œ λ²ˆμ”© 값을 λ°˜ν™˜ν•˜λ„λ‘ μ„€μ •ν•œλ‹€. 예λ₯Ό λ“€μ–΄, 첫 번째 ν˜ΈμΆœμ—μ„œλŠ” numbers λ°°μ—΄μ˜ 첫 번째 μš”μ†Œλ₯Ό λ°˜ν™˜ν•˜κ³ , 두 번째 ν˜ΈμΆœμ—μ„œλŠ” 두 번째 μš”μ†Œλ₯Ό λ°˜ν™˜ν•˜λŠ” 식이닀.

 

μ •λ¦¬ν•˜μžλ©΄, μœ„ ν…ŒμŠ€νŠΈ μ½”λ“œμ˜ λͺ©μ μ€ 랜덀 숫자 생성 ν•¨μˆ˜μ˜ λ™μž‘μ„ ν†΅μ œν•˜κ³ , ν…ŒμŠ€νŠΈ 쀑에 예츑 κ°€λŠ₯ν•˜κ³  μΌκ΄€λœ κ²°κ³Όλ₯Ό μ–»κΈ° μœ„ν•¨μ΄λ‹€. '랜덀'이 λ‚΄ν¬ν•˜λŠ” λΆˆκ·œμΉ™ν•˜κ³ , 예츑 λΆˆκ°€λŠ₯ν•œ μ„±μ§ˆμ„ μ œκ±°ν•˜κ³  ν…ŒμŠ€νŠΈ μ½”λ“œ μ•ˆμ—μ„œ 만의 λ°©μ‹μœΌλ‘œ μ •μ˜ν•œλ‹€κ³  보면 λœλ‹€.

 

 

4️⃣ 좜λ ₯ ν•¨μˆ˜λ₯Ό κ°μ‹œν•˜κΈ°

// 둜그 좜λ ₯을 κ°μ‹œν•˜λŠ” ν•¨μˆ˜λ₯Ό μ •μ˜
const getLogSpy = () => { 
  // MissionUtils.Console.print ν•¨μˆ˜μ˜ ν˜ΈμΆœμ„ κ°μ‹œ
  const logSpy = jest.spyOn(MissionUtils.Console, 'print'); 
  // κ°μ‹œ 둜그 μ΄ˆκΈ°ν™”
  logSpy.mockClear();
  // κ°μ‹œ 객체 λ°˜ν™˜
  return logSpy;
};

 

κ·Έ λ‹€μŒμ—λŠ” 둜그 좜λ ₯ ν•¨μˆ˜λ₯Ό κ°μ‹œν•˜λŠ” getLogSpy ν•¨μˆ˜λ₯Ό μ •μ˜ν•œλ‹€. 이 ν•¨μˆ˜λŠ” 둜그 좜λ ₯을 μœ„ν•΄ μ‚¬μš©λ˜λŠ” MissionUtils.Console.print ν•¨μˆ˜μ˜ ν˜ΈμΆœμ„ μΆ”μ ν•˜κ³ , κ·Έ κ²°κ³Όλ₯Ό ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•  수 있게 ν•œλ‹€.  (spyOn에 λŒ€ν•œ μ„€λͺ…은 Jest 곡식 λ¬Έμ„œμ˜ 예제λ₯Ό μ°Έκ³ ν•˜λ©΄ 이해가 μ‰¬μšΈ 것이닀) μ•„λ¬΄νŠΌ μ—¬κΈ°μ„œλŠ” MissionUtils.Console 객체의 print λ©”μ„œλ“œκ°€ 호좜될 λ•Œλ§ˆλ‹€ 이λ₯Ό μΆ”μ ν•˜λ„λ‘ μ„€μ •ν–ˆλ‹€.

 

λ”λΆˆμ–΄, logSpy.mockClear()λ₯Ό ν˜ΈμΆœν•˜μ—¬ logSpy의 기둝을 μ΄ˆκΈ°ν™”ν•΄ μ€€λ‹€. μ΄λŠ” ν…ŒμŠ€νŠΈ μ‹œμž‘ 전에 이전 ν…ŒμŠ€νŠΈμ—μ„œμ˜ 호좜 기둝을 μ œκ±°ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λ©°, 각 ν…ŒμŠ€νŠΈ 전에 μžλ™μœΌλ‘œ λͺ¨μ˜ ν•­λͺ©λ“€μ„ μ§€μšΈ 수 μžˆλ‹€κ³  ν•œλ‹€. (μ°Έκ³ : Jest κ³΅μ‹λ¬Έμ„œ mockClear)

 

κ·Έ λ‹€μŒμ—λŠ” μ΄ˆκΈ°ν™”λœ logSpy 객체λ₯Ό λ°˜ν™˜ν•œλ‹€. 이 객체λ₯Ό μ‚¬μš©ν•˜λ©΄ ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œ MissionUtils.Console.print ν•¨μˆ˜μ˜ 호좜 μ—¬λΆ€, 호좜 횟수, 호좜 μ‹œ μ‚¬μš©λœ 인자 등을 검사할 수 μžˆμ„ 것이닀.

 

μ •λ¦¬ν•˜μžλ©΄, 이 μ½”λ“œλŠ” ν…ŒμŠ€νŠΈ 쀑에 좜λ ₯ ν•¨μˆ˜κ°€ μ–΄λ–»κ²Œ μ‚¬μš©λ˜λŠ”μ§€ μΆ”μ ν•˜κ³ , 둜그 좜λ ₯이 μ˜ˆμƒλŒ€λ‘œ λ™μž‘ν•˜λŠ”μ§€ κ²€μ¦ν•˜λŠ” 데 μ‚¬μš©λœλ‹€. 예λ₯Ό λ“€μ–΄ νŠΉμ • μƒν™©μ—μ„œ νŠΉμ • λ©”μ‹œμ§€κ°€ 잘 좜λ ₯λ˜λŠ”μ§€, ν˜Ήμ€ 좜λ ₯ ν•¨μˆ˜κ°€ 정해진 횟수만큼 잘 ν˜ΈμΆœλ˜λŠ”μ§€ 등을 검증할 수 μžˆλ‹€λŠ” μ˜λ―Έλ‹€.

 

 

5️⃣ κ²Œμž„ μ’…λ£Œ ν›„ μž¬μ‹œμž‘ 둜직 κ²€μ¦ν•˜κΈ°

μ½”λ“œ 뢄석에 μ•žμ„œ, μš°μ„  ν…ŒμŠ€νŠΈ μ½”λ“œμ˜ ꡬ쑰λ₯Ό μ•Œμ•„λ³΄μž. ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” ν•˜λ‚˜μ˜ 큰 ν…ŒμŠ€νŠΈ μŠ€μœ„νŠΈ(Test Suite) μ•ˆμ— μ—¬λŸ¬ 개의 κ΄€λ ¨λœ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€(Test Case)λ“€λ‘œ 이루어져 μžˆλ‹€.

 

Test Plan @parasoft.com

 

ν…ŒμŠ€νŠΈ μ½”λ“œ κ΅¬μ‘°λŠ” κ°„λ‹¨ν•˜κ²Œ μ•Œμ•„λ³΄μ•˜μœΌλ‹ˆ, μ•„λž˜μ˜ 첫 번째 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μͺΌκ°œκ°€λ©° κΉŠμˆ™ν•˜κ²Œ νŒŒκ³ λ“€μ–΄ κ°€ 보자.

 

// '숫자 야ꡬ κ²Œμž„'에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μŠ€μœ„νŠΈλ₯Ό μ •μ˜
describe('숫자 야ꡬ κ²Œμž„', () => { 
  // 'κ²Œμž„ μ’…λ£Œ ν›„ μž¬μ‹œμž‘'에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μ •μ˜
  test('κ²Œμž„ μ’…λ£Œ ν›„ μž¬μ‹œμž‘', async () => {

 

β†ͺ️ μš°μ„ , describe ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ '숫자 야ꡬ κ²Œμž„'에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μŠ€μœ„νŠΈλ₯Ό μ •μ˜ν•œλ‹€. μœ„μ—μ„œ μ„€λͺ…ν–ˆλ“― 이 블둝 μ•ˆμ— ν¬ν•¨λœ test ν•¨μˆ˜λ“€μ€ λͺ¨λ‘ 이 '숫자 야ꡬ κ²Œμž„'κ³Ό κ΄€λ ¨λœ ν…ŒμŠ€νŠΈμΌ 것이닀.

 

그리고 'κ²Œμž„ μ’…λ£Œ ν›„ μž¬μ‹œμž‘'μ΄λΌλŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μ •μ˜ν•˜λ©°, 이 ν…ŒμŠ€νŠΈλŠ” λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰(async)λœλ‹€.

 

// given
// ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ μ΄ˆκΈ°κ°’ μ„€μ •
const randoms = [1, 3, 5, 5, 8, 9]; 
const answers = ['246', '135', '1', '597', '589', '2'];
const logSpy = getLogSpy();
const messages = ['λ‚«μ‹±', '3슀트라이크', '1λ³Ό 1슀트라이크', '3슀트라이크', 'κ²Œμž„ μ’…λ£Œ'];

 

β†ͺ️ λ‹€μŒμœΌλ‘œλŠ” ν…ŒμŠ€νŠΈ μ΄ˆκΈ°κ°’μ„ μ„€μ •ν•œλ‹€. μ½”λ“œμ—μ„œ κ·ΈλŒ€λ‘œ μ½νžˆλ“― randoms λ°°μ—΄μ—λŠ” κ²Œμž„μ— μ‚¬μš©λ  랜덀 μˆ«μžλ“€μ„ 직접 μ •μ˜, answers λ°°μ—΄μ—λŠ” μ‚¬μš©μžμ˜ μž…λ ₯을 λͺ¨μ˜ν•˜λŠ” 데에 μ‚¬μš©λ  μ˜ˆμƒ 닡변듀을 μ •μ˜, logSpyλŠ” 좜λ ₯을 κ°μ‹œν•˜λŠ” ν•¨μˆ˜μ˜ λ¦¬ν„΄κ°’μœΌλ‘œ 좜λ ₯ ν•¨μˆ˜ ν˜ΈμΆœμ„ μΆ”μ ν•œλ‹€. 그리고 messages 배열은 μ˜ˆμƒλ˜λŠ” 둜그 λ©”μ‹œμ§€λ“€μ„ ν¬ν•¨ν•œλ‹€.

 

mockRandoms(randoms);
mockQuestions(answers);

 

β†ͺ️ λͺ¨μ˜ ν•¨μˆ˜λ“€μ„ ν˜ΈμΆœν•΄ ν™œμ„±ν™”ν•΄ μ€€λ‹€. 두 mock ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•  λ•ŒλŠ” μœ„μ—μ„œ μ„ μ–Έν•œ 배열을 인자둜 λ„£μ–΄μ£Όλ©΄μ„œ κ²Œμž„ λ‘œμ§μ—μ„œ μ‚¬μš©λ  랜덀 μˆ«μžμ™€ μ‚¬μš©μž μž…λ ₯을 λͺ¨μ˜ν•œλ‹€.

 

// when
// App μΈμŠ€ν„΄μŠ€ 생성 및 play ν•¨μˆ˜ μ‹€ν–‰
const app = new App();
await expect(app.play()).resolves.not.toThrow();

 

β†ͺ️ new둜 App 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , play( )λ₯Ό ν˜ΈμΆœν•œλ‹€. 그리고 이 play( )κ°€ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€μ§€ μ•Šκ³ (not.toThrow) μ •μƒμ μœΌλ‘œ μˆ˜ν–‰λ˜λŠ”μ§€ 검증(expect) ν•œλ‹€.

 

// then
// 둜그 좜λ ₯이 μ˜ˆμƒλŒ€λ‘œ μ΄λ£¨μ–΄μ‘ŒλŠ”μ§€ 검증
messages.forEach((output) => { 
  expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output));
});

 

β†ͺ️ 그러고 λ‚˜μ„œλŠ” messages 배열에 ν¬ν•¨λœ 각각의 μ˜ˆμƒ λ©”μ‹œμ§€λ“€μ— λŒ€ν•΄, μ•„κΉŒ 좜λ ₯ ν•¨μˆ˜λ₯Ό κ°μ‹œν•œλ‹€λ˜ logSpyκ°€ λ‚˜μ„ λ‹€. 이 logSpyλŠ” ν•΄λ‹Ή λ©”μ‹œμ§€λ₯Ό ν¬ν•¨ν•˜μ—¬ ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€ κ²€μ¦ν•˜λŠ” 역할을 ν•œλ‹€. μ‰½κ²Œ 말해, κ²Œμž„μ΄ μ§„ν–‰λ˜λ©° 각 상황에 λ§žλŠ” 둜그 λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•˜κ³  μžˆλŠ”μ§€ μ•„λ‹Œμ§€ ν™•μΈν•˜λŠ” 것이닀.

 

참고둜, μ—¬κΈ°μ„œ toHaveBeenCalledWith( )λŠ” λͺ¨μ˜ ν•¨μˆ˜κ°€ νŠΉμ • 인수둜 ν˜ΈμΆœλ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” 역할을 ν•˜λ©°, expect.stringContainingλŠ” 상황에 맞게 μ •ν™•ν•œ μ˜ˆμƒ λ¬Έμžμ—΄μ„ ν¬ν•¨ν•˜λŠ” λ¬Έμžμ—΄μΈ 경우λ₯Ό ν™•μΈν•˜λŠ” 역할을 ν•œλ‹€.

 

 

6️⃣ μ˜ˆμ™Έ 상황  처리 μ—¬λΆ€ ν…ŒμŠ€νŠΈν•˜κΈ°

// 'μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ'에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μ •μ˜
test('μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ', async () => {

 

β†ͺ️ μš°μ„ , 'μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ'λΌλŠ” μ΄λ¦„μ˜ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μ •μ˜ν•˜λ©° 이 ν…ŒμŠ€νŠΈ λ˜ν•œ 비동기 ν•¨μˆ˜λ₯Ό ν…ŒμŠ€νŠΈν•˜κΈ° μœ„ν•΄ asyncλ₯Ό μ‚¬μš©ν–ˆλ‹€.

 

// given
// ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ μ΄ˆκΈ°κ°’ μ„€μ •
const randoms = [1, 3, 5]; 
const answers = ['1234'];

 

β†ͺ️ 첫 번째 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ™€ μœ μ‚¬ν•˜κ²Œ ν…ŒμŠ€νŠΈ μ΄ˆκΈ°κ°’μ„ μ„€μ •ν•œλ‹€. randoms 배열은 ν…ŒμŠ€νŠΈμ— μ‚¬μš©λ  랜덀 μˆ«μžλ“€, answers 배열은 ν…ŒμŠ€νŠΈμ— μ‚¬μš©λ  μ‚¬μš©μž μž…λ ₯을 λͺ¨μ˜ν•˜μ—¬ μ˜ˆμƒ 닡변을 μ •μ˜ν•œλ‹€.

 

참고둜 이 ν…ŒμŠ€νŠΈλŠ” μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€λŠ”μ§€ μ•Œμ•„λ³΄λŠ” ν…ŒμŠ€νŠΈμ΄κΈ° λ•Œλ¬Έμ— μ—λŸ¬λ₯Ό λ°œμƒμ‹œμΌœμ•Ό ν•œλ‹€. λ•Œλ¬Έμ— answersμ—λŠ” μ˜ˆμ™Έ 처리 λŒ€μƒμΈ λ‹΅λ³€(μ„Έ μžλ¦¬κ°€ μ•„λ‹Œ λ„€ 자리 λ¬Έμžμ—΄)을 λ„£μ–΄μ•Ό ν•œλ‹€.

 

mockRandoms(randoms);
mockQuestions(answers);

 

β†ͺ️ λ§ˆμ°¬κ°€μ§€λ‘œ μœ„μ—μ„œ μ„ μ–Έν•œ 배열듀을 인자둜 λ„£μ–΄ mock ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œλ‹€.

 

// when & then
// App μΈμŠ€ν„΄μŠ€ 생성 및 play ν•¨μˆ˜ μ‹€ν–‰, μ˜ˆμ™Έ λ°œμƒ 검증
const app = new App();
await expect(app.play()).rejects.toThrow('[ERROR]');

 

β†ͺ️ new둜 App 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , play( )λ₯Ό ν˜ΈμΆœν•œλ‹€. 그리고 이 play( )κ°€ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€κ³ (toThrow), μ˜ˆμ™Έ λ©”μ‹œμ§€λ‘œ '[ERROR]'κ°€ 좜λ ₯λ˜λŠ”μ§€ ν™•μΈν•œλ‹€.

 

 

πŸ‘©πŸ»‍πŸ’» ν…ŒμŠ€νŠΈ μ½”λ“œ 뢄석 μ†Œκ°

ν™•μ‹€νžˆ 전보닀 ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ 잘 μ΄ν•΄λœλ‹€. 처음 봀을 λ•ŒλŠ” λ‹Ήμ—°ν•˜κ²Œλ„ Jest의 각 λ©”μ„œλ“œκ°€ μ–΄λ–€ 역할을 ν•˜λŠ”μ§€ λͺ¨λ₯΄κΈ°μ— κΉŒλ§‰λˆˆμ΄ 된 κΈ°λΆ„μ΄μ—ˆμ§€λ§Œ, ν•˜λ‚˜ν•˜λ‚˜ 찾아보고 λΆ„μ„ν•΄λ³΄λ‹ˆ 잘 μ½νžŒλ‹€. 마치 μ˜μ–΄λ₯Ό ν•˜λ‚˜λ„ λͺ¨λ₯΄λŠ”데 μ˜μ–΄μ‚¬μ „μ—μ„œ 단어 ν•˜λ‚˜ν•˜λ‚˜ μ°Ύμ•„κ°€λ©΄μ„œ 문단을 읽어낸 λŠλ‚Œ?

 

μ–΄μ¨Œλ“  ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ„Έμ„Έν•˜κ²Œ νŒŒλ³΄λ‹ˆ λ¬Έμ œμ—μ„œ μš”κ΅¬ν•˜λŠ” λ°”λ₯Ό μ •ν™•ν•˜κ²Œ μ•Œ 수 μžˆμ—ˆλ‹€. λ‹€μŒ λ―Έμ…˜λ“€λ„ μ΄λ ‡κ²Œ νŒŒλ΄μ•Όμ§€.

 

 

 

λŒ“κΈ€