우테코 프리코스를 통해 테스트 코드란 무엇인지 알아보고 왜 사용하는지 어덯게 사용하는지 알아보장
애플리케이션의 특정 기능을 테스트하는 코드이다.
예를 들어 특정 함수의 테스트를 하려면 그 함수에 대한 입력값과 기대되는 출력을 정의하고 실제 출력과 비교하는 식으로 작성된다.
테스트 코드는 일반적으로 코드와 함께 저장되어 계속해서 수정되고 실행해본다.
여러 테스트 라이브러리가 있지만 jest를 사용해보겠습니다.
jest 설치는  npm install --save-dev jest 로 설치한후 package.json에
{
   "scripts":{
      "test": "jest"
   }
}를 추가해준다.
코드가 추가 되거나 수정될 때 발생할 수 있는 버그들은 사전에 방지하고 기능 변경 시 기존 코드가 정상적으로 동작하는지 확인이 가능함.
리팩토링 혹은 코드 변경 시 안정적이며 개발 속도를 높일 수 있다. 하지만 초기 작성 시간과 기능변경 시 테스트 코드도 변경을 해야하기 때문에 번거로울 수 있다.
주로 예외처리 혹은 정상동작 시 입력에 따른 값이 나오는지 체크한다.
모의 함수를 사용하여 함수의 실제 구현을 대체하고 함수 호출과 그 호출에 사용된 인자들을 추적함.
const mockQuestions = (inputs) => {
  MissionUtils.Console.readLineAsync = jest.fn();
모의로 사용자가 입력값을 넣었을 경우를 만들어 주는 코드이다.  MissionUtils.Console.readLineAsync를 가짜 함수로 대체함. jest.fn()은 모의 함수를 생성하는 함수이다.
mockImplementation는 모의 함수의 구현을 정의하고 로직을 대체할 수 있음. 위의 코드를 사용하면 사용자의 값을 받아오는 MissionUtils.Console.readLineAsync 대신 가짜 함수를 사용해 테스트 시 설정한 input값으로 테스트가 가능하다. 
MissionUtils.Console.readLineAsync.mockImplementation(() => {
    const input = inputs.shift();
    return Promise.resolve(input);
  });
};이 부분은 받아온 inputs배열에서 맨 첫번째로 받아온 input요소를 제거 후 그 값을 promise로 감싸 반환한다. 이러한 이유는 readLineAsync는 비동기 함수(입력받는)이기 떄문에 promise로 감싸 반환시킴.
const mockRandoms = (numbers) => {
  MissionUtils.Random.pickNumberInRange = jest.fn();
  numbers.reduce((acc, number) => {
    return acc.mockReturnValueOnce(number);
  }, MissionUtils.Random.pickNumberInRange);
};이 코드에서는 pickNumberInRange라는 함수가 쓰인다. 이것은 랜덤 숫자를 생성해주는 함수이다. 이 테스트 코드에서는 이 함수를 mock한다.
우선 numbers  라는 배열을 순회하면서 reduce 함수를 사용한다. 이 과정에 pickNumberInRange 함수의 반환값을 numbers 배열의 각 요소로 순차적으로 설정한다.
그리고 mockReturnValueOnce 메서드는 함수가 호출될 때마다 지정된 순서대로 한 번씩 값을 반환하도록 설정한다. 예를 들어, 첫 번째 호출에서는 numbers 배열의 첫 번째 요소를 반환하고, 두 번째 호출에서는 두 번째 요소를 반환하는 식이다.
const getLogSpy = () => {
  const logSpy = jest.spyOn(MissionUtils.Console, "print");
  logSpy.mockClear();
  return logSpy;
};MissionUtils.Console.print를 추적해 그 결과를 테스트에서 사용 가능하게 함. 또한
logSpy.mockClear()를 호출하여 logSpy의 기록을 초기화 해준다. 이는 테스트 시작 전에 이전 테스트에서의 호출 기록을 제거하기 위해 사용됨.
describe("자동차 경주", () => {
  test("기능 테스트", async () => {
    // given
    const MOVING_FORWARD = 4;
    const STOP = 3;
    const inputs = ["pobi,woni", "1"];
    const logs = ["pobi : -", "woni : ", "최종 우승자 : pobi"];
    const logSpy = getLogSpy();
    mockQuestions(inputs);
    mockRandoms([MOVING_FORWARD, STOP]);
    // when
    const app = new App();
    await app.run();
    // then
    logs.forEach((log) => {
      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
    });
  });
  test("예외 테스트", async () => {
    // given
    const inputs = ["pobi,javaji"];
    mockQuestions(inputs);
    // when
    const app = new App();
    // then
    await expect(app.run()).rejects.toThrow("[ERROR]");
  });
});descript는 큰 단위이다. test들을 한번에 넣는 group이라고 생각하면 될 것 같다.
먼저 기능 테스트부터 보면 
 	const MOVING_FORWARD = 4;
    const STOP = 3;
    const inputs = ["pobi,woni", "1"];
    const logs = ["pobi : -", "woni : ", "최종 우승자 : pobi"];
    const logSpy = getLogSpy();MOVING_FORWARD는 4이상시 전진하는 것을 뜻하는 변수이다.
또한 STOP은 4이하기 때문에 전진하지 않는다.
 mockQuestions(inputs);
    mockRandoms([MOVING_FORWARD, STOP]);
    // when
    const app = new App();
    await app.run();
    // then
    logs.forEach((log) => {
      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
    });inputs배열은 mockQuestions에 넣어 mock으로 사용되게끔 된다. 이후 new App으로 app을 불러와 run을 해주면 주어진 log들이 출력 된다. 그러고 나서는 logs 배열에 포함된 각각의 예상 메시지들에 대해, 아까 출력 함수를 감시한다던 logSpy가 나선다. 이 logSpy는 해당 메시지를 포함하여 호출되었는지 검증하는 역할을 한다.게임이 진행되며 각 상황에 맞는 로그 메시지를 출력하고 있는지 아닌지 확인하는 것이다.
여기서 toHaveBeenCalledWith()는 모의 함수가 특정 인수로 호출되었는지 확인하는 역할을 하며, expect.stringContaining는 상황에 맞게 정확한 예상 문자열을 포함하는 문자열인 경우를 확인하는 역할을 한다.
 test("예외 테스트", async () => {
    // given
    const inputs = ["pobi,javaji"];
    mockQuestions(inputs);
    // when
    const app = new App();
    // then
    await expect(app.run()).rejects.toThrow("[ERROR]");
  });예외 테스트는 inputs배열을 통해 app 클래스의 run을 실행하였을때 오류가 나는지 확인한다.
이와 비슷하게 또는 다른 방법으로 테스트코드를 만들어 테스트를 진행하며 개발을 할 수 있다.
나중에 프리코스를 진행하며 해봐야겟다..