From 4be91f834dac8fde236d7836b34725aec42231a7 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 09:03:24 +0900 Subject: [PATCH 01/13] =?UTF-8?q?docs:=20=EA=B5=AC=ED=98=84=ED=95=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=85=EC=84=B8=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index e078fd41..e353ef54 100644 --- a/README.md +++ b/README.md @@ -1 +1,42 @@ # javascript-racingcar-precourse + +**에러 처리 기능** + +- try ~ catch 문으로 에러를 catch했다면 [ERROR]로 시작하는 에러 메세지를 반환 + +**경주할 자동차 입력을 받고 이름을 검증하는 기능** + +- 입력: 입력한 자동차를 join으로 분리해 배열에 저장 + - `Console.readLineAsync()` +- 출력: “경주할 자동차 이름을 입력하세요.” + - `Console.readLineAsync()` +- 검증: 길이 5이하의 문자열인지 확인, 아니라면 에러 throw + +**시도할 횟수를 입력받고 검증하는 기능** + +- 입력: 시도할 횟수를 입력받아 저장 + - `Console.readLineAsync()` +- 출력: “시도할 횟수는 몇 회인가요?” + - `Console.readLineAsync()` +- 검증: 숫자인지 확인, 아니라면 에러 throw + +**시도할 횟수만큼 반복문으로 돌며 자동차마다 랜덤값을 생성하는 기능** + +- `MissionUtils.Random.pickNumberInRange(0, 9);` + +**자동차를 전진하는 기능** + +- 랜덤값이 4 이상일 시 1 전진 +- 전진한 자동차를 배열에 push + +**실행 결과를 출력하는 기능** + +- 출력: 처음에는 “실행 결과”를 출력, 이후 각 자동차 이름과 전진 횟수를 -로 표시 + - `Console.print()` + +**우승 결과를 출력하는 기능** + +- 출력: 최종 우승자 : + - 가장 많은 전진 횟수를 가진 자동차를 출력 + - 동일한 전진 횟수를 가진 자동차가 여러대라면 쉼표로 구분해 출력 + - `Console.print()` From ba583b3d7d2b7a5210bbc7eb2e497860eeae18fc Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 09:51:42 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80(try=20~=20c?= =?UTF-8?q?atch=20=EB=AC=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 091aa0a5..cab0b711 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ class App { - async run() {} + async run() { + try { + } catch (error) { + throw new Error(`[ERROR] ${error.message}`); + } + } } export default App; From 80f54ee75b7c7a21d83f8e718a6cecde0bdbee94 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 16:24:40 +0900 Subject: [PATCH 03/13] =?UTF-8?q?docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(=EC=9E=85=EB=A0=A5=ED=95=9C=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=EC=B0=A8=20=EB=B6=84=EB=A6=AC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20join=20->=20split)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e353ef54..02da8f67 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **경주할 자동차 입력을 받고 이름을 검증하는 기능** -- 입력: 입력한 자동차를 join으로 분리해 배열에 저장 +- 입력: 입력한 자동차를 split으로 분리해 배열에 저장 - `Console.readLineAsync()` - 출력: “경주할 자동차 이름을 입력하세요.” - `Console.readLineAsync()` From f23f279b4ed4239b5d086b5ea813009257f56009 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 16:25:09 +0900 Subject: [PATCH 04/13] =?UTF-8?q?feat:=20=EA=B2=BD=EC=A3=BC=ED=95=A0=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=B0=A8=20=EC=9E=85=EB=A0=A5=EC=9D=84=20?= =?UTF-8?q?=EB=B0=9B=EA=B3=A0=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/App.js b/src/App.js index cab0b711..a6a05c0c 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,31 @@ +import { Console } from "@woowacourse/mission-utils"; + +const readCarNames = async () => { + const cars = await Console.readLineAsync( + "경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분) \n" + ); + + const carNames = cars.split(","); + carNames.map((car) => { + const trimmedCar = car.trim(); + validateCarName(trimmedCar); + return trimmedCar; + }); + + return carNames; +}; + +const validateCarName = (carName) => { + if (carName.length > 5) { + throw new Error("자동차 이름은 5글자 이하로 입력해주세요."); + } +}; + class App { async run() { try { + const carsNames = await readCarNames(); + Console.print(carsNames); } catch (error) { throw new Error(`[ERROR] ${error.message}`); } From 40c17dbb10c478cd497ff8b716198686f5866022 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 17:36:11 +0900 Subject: [PATCH 05/13] =?UTF-8?q?feat:=20=EC=8B=9C=EB=8F=84=ED=95=A0=20?= =?UTF-8?q?=ED=9A=9F=EC=88=98=EB=A5=BC=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EA=B3=A0=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/App.js b/src/App.js index a6a05c0c..af302204 100644 --- a/src/App.js +++ b/src/App.js @@ -1,31 +1,56 @@ import { Console } from "@woowacourse/mission-utils"; +const validateCarName = (carName) => { + const trimmed = String(carName).trim(); + if (!trimmed) { + throw new Error("자동차 이름을 입력해주세요."); + } + if (trimmed.length > 5) { + throw new Error("자동차 이름은 5글자 이하로 입력해주세요."); + } + return trimmed; +}; + const readCarNames = async () => { - const cars = await Console.readLineAsync( + const input = await Console.readLineAsync( "경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분) \n" ); - - const carNames = cars.split(","); - carNames.map((car) => { - const trimmedCar = car.trim(); - validateCarName(trimmedCar); - return trimmedCar; - }); - + const carNames = input.split(",").map((name) => validateCarName(name)); return carNames; }; -const validateCarName = (carName) => { - if (carName.length > 5) { - throw new Error("자동차 이름은 5글자 이하로 입력해주세요."); +const validateAttemptCount = (count) => { + if (!count) { + throw new Error("시도할 횟수를 입력해주세요."); + } + + const numCount = Number(count); + + if (Number.isNaN(numCount)) { + throw new Error("시도할 횟수는 숫자로 입력해주세요."); + } else if (!Number.isInteger(numCount)) { + throw new Error("시도할 횟수는 정수로 입력해주세요."); + } else if (numCount <= 0) { + throw new Error("시도할 횟수는 0 이상의 양의 정수로 입력해주세요."); + } else { + return numCount; } }; +const readAttemptCount = async () => { + const input = await Console.readLineAsync("시도할 횟수는 몇 회인가요? \n"); + const value = validateAttemptCount(input); + return value; +}; + class App { async run() { try { const carsNames = await readCarNames(); + const attemptCount = await readAttemptCount(); + Console.print(carsNames); + Console.print(attemptCount); } catch (error) { throw new Error(`[ERROR] ${error.message}`); } From 79e7e036ccef7e0c6cbb7503971aff0501d528c8 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 20:51:28 +0900 Subject: [PATCH 06/13] =?UTF-8?q?feat:=20=EC=8B=9C=EB=8F=84=20=ED=9A=9F?= =?UTF-8?q?=EC=88=98=EB=A7=8C=ED=81=BC=20=EB=B0=98=EB=B3=B5=EB=AC=B8?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9E=90=EB=8F=99=EC=B0=A8=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=EB=9E=9C=EB=8D=A4=EA=B0=92=EC=9D=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index af302204..20889465 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,8 @@ -import { Console } from "@woowacourse/mission-utils"; +import { Console, MissionUtils } from "@woowacourse/mission-utils"; +const PROGRESS_BAR = "-"; + +// 중복되는 자동차 이름 검사하기 const validateCarName = (carName) => { const trimmed = String(carName).trim(); if (!trimmed) { @@ -43,14 +46,35 @@ const readAttemptCount = async () => { return value; }; +const randomMove = () => { + const randomValue = MissionUtils.Random.pickNumberInRange(0, 9); + if (randomValue >= 4) { + return true; + } else { + return false; + } +}; + +const startRound = (carNames, movedCars) => { + carNames.forEach((car) => { + const canMove = randomMove(); + Console.print(canMove); + }); + Console.print("\n"); +}; + class App { async run() { try { - const carsNames = await readCarNames(); + const movedCars = []; + const carNames = await readCarNames(); const attemptCount = await readAttemptCount(); - Console.print(carsNames); - Console.print(attemptCount); + Console.print("\n실행 결과\n"); + + for (let i = 1; i <= attemptCount; i++) { + startRound(carNames, movedCars); + } } catch (error) { throw new Error(`[ERROR] ${error.message}`); } From 20977a44bbede0ca1dea8bb64ff741e34ca99c1d Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 20:52:21 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=EA=B0=92=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=B0=A8=EB=A5=BC=20=EC=A0=84=EC=A7=84=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 20889465..0a42f144 100644 --- a/src/App.js +++ b/src/App.js @@ -58,7 +58,9 @@ const randomMove = () => { const startRound = (carNames, movedCars) => { carNames.forEach((car) => { const canMove = randomMove(); - Console.print(canMove); + if (canMove) { + movedCars.push(car); + } }); Console.print("\n"); }; From 193aa6197e62498f186c7a124b0e07273769671c Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 20:53:28 +0900 Subject: [PATCH 08/13] =?UTF-8?q?feat:=20=EA=B0=81=20=EB=9D=BC=EC=9A=B4?= =?UTF-8?q?=EB=93=9C=EC=9D=98=20=EC=8B=A4=ED=96=89=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/App.js b/src/App.js index 0a42f144..e550aa62 100644 --- a/src/App.js +++ b/src/App.js @@ -55,12 +55,19 @@ const randomMove = () => { } }; +const printProgress = (currentCar, movedCars) => { + const currentMove = movedCars.filter((item) => item === currentCar).length; + const currentProgress = PROGRESS_BAR.repeat(currentMove); + Console.print(`${currentCar} : ${currentProgress}`); +}; + const startRound = (carNames, movedCars) => { carNames.forEach((car) => { const canMove = randomMove(); if (canMove) { movedCars.push(car); } + printProgress(car, movedCars); }); Console.print("\n"); }; From ae698c26d5070f236d813fa21136c7a31b061ea4 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 21:30:38 +0900 Subject: [PATCH 09/13] =?UTF-8?q?refactor:=20=EC=A0=84=EC=A7=84=ED=95=9C?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=EC=B0=A8=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=B0=B0=EC=97=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 74 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/src/App.js b/src/App.js index e550aa62..b18b9d03 100644 --- a/src/App.js +++ b/src/App.js @@ -2,7 +2,6 @@ import { Console, MissionUtils } from "@woowacourse/mission-utils"; const PROGRESS_BAR = "-"; -// 중복되는 자동차 이름 검사하기 const validateCarName = (carName) => { const trimmed = String(carName).trim(); if (!trimmed) { @@ -14,16 +13,30 @@ const validateCarName = (carName) => { return trimmed; }; +const checkNameDuplicate = (carNames) => { + const nameSet = new Set(); + for (const name of carNames) { + if (nameSet.has(name)) { + throw new Error(`중복된 자동차 이름이 있습니다. 이름: ${name}`); + } + nameSet.add(name); + } +}; + const readCarNames = async () => { const input = await Console.readLineAsync( "경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분) \n" ); - const carNames = input.split(",").map((name) => validateCarName(name)); + const rawNames = input.split(","); + const carNames = rawNames.map((name) => validateCarName(name)); + checkNameDuplicate(carNames); + return carNames; }; +// 시도 횟수 유효성 검사 const validateAttemptCount = (count) => { - if (!count) { + if (count === undefined || count === null || String(count).trim() === "") { throw new Error("시도할 횟수를 입력해주세요."); } @@ -34,7 +47,7 @@ const validateAttemptCount = (count) => { } else if (!Number.isInteger(numCount)) { throw new Error("시도할 횟수는 정수로 입력해주세요."); } else if (numCount <= 0) { - throw new Error("시도할 횟수는 0 이상의 양의 정수로 입력해주세요."); + throw new Error("시도할 횟수는 1 이상의 정수로 입력해주세요."); } else { return numCount; } @@ -48,45 +61,48 @@ const readAttemptCount = async () => { const randomMove = () => { const randomValue = MissionUtils.Random.pickNumberInRange(0, 9); - if (randomValue >= 4) { - return true; - } else { - return false; - } + return randomValue >= 4; }; -const printProgress = (currentCar, movedCars) => { - const currentMove = movedCars.filter((item) => item === currentCar).length; +const printProgress = (currentCar, movedCount) => { + const currentMove = movedCount[currentCar] || 0; const currentProgress = PROGRESS_BAR.repeat(currentMove); Console.print(`${currentCar} : ${currentProgress}`); }; -const startRound = (carNames, movedCars) => { +const startRound = (carNames, movedCount) => { carNames.forEach((car) => { - const canMove = randomMove(); - if (canMove) { - movedCars.push(car); + if (randomMove()) { + movedCount[car] = (movedCount[car] || 0) + 1; } - printProgress(car, movedCars); + printProgress(car, movedCount); }); Console.print("\n"); }; +const runCarRace = async () => { + try { + const movedCount = {}; + const carNames = await readCarNames(); + const attemptCount = await readAttemptCount(); + + carNames.forEach((name) => { + movedCount[name] = 0; + }); + + Console.print("\n실행 결과\n"); + + for (let i = 1; i <= attemptCount; i++) { + startRound(carNames, movedCount); + } + } catch (error) { + throw new Error(`[ERROR] ${error.message}`); + } +}; + class App { async run() { - try { - const movedCars = []; - const carNames = await readCarNames(); - const attemptCount = await readAttemptCount(); - - Console.print("\n실행 결과\n"); - - for (let i = 1; i <= attemptCount; i++) { - startRound(carNames, movedCars); - } - } catch (error) { - throw new Error(`[ERROR] ${error.message}`); - } + await runCarRace(); } } From 16aecf17b65beff2c9dbe95631738a764168401d Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 21:31:20 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat:=20=EC=9A=B0=EC=8A=B9=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EB=A5=BC=20=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/App.js b/src/App.js index b18b9d03..e401d477 100644 --- a/src/App.js +++ b/src/App.js @@ -80,6 +80,14 @@ const startRound = (carNames, movedCount) => { Console.print("\n"); }; +const printWinner = (movedCount) => { + const maxMove = Math.max(...Object.values(movedCount)); + const winners = Object.keys(movedCount).filter( + (key) => movedCount[key] === maxMove + ); + Console.print(`최종 우승자: ${winners.join(", ")}`); +}; + const runCarRace = async () => { try { const movedCount = {}; @@ -95,6 +103,8 @@ const runCarRace = async () => { for (let i = 1; i <= attemptCount; i++) { startRound(carNames, movedCount); } + + printWinner(movedCount); } catch (error) { throw new Error(`[ERROR] ${error.message}`); } From daf57569d806e79d48ca8938919c1f10cc03c166 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 21:39:37 +0900 Subject: [PATCH 11/13] =?UTF-8?q?style:=20indent=20depth=EA=B0=80=202?= =?UTF-8?q?=EC=9D=B4=ED=95=98=EA=B0=80=20=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/App.js b/src/App.js index e401d477..156e4247 100644 --- a/src/App.js +++ b/src/App.js @@ -85,34 +85,34 @@ const printWinner = (movedCount) => { const winners = Object.keys(movedCount).filter( (key) => movedCount[key] === maxMove ); - Console.print(`최종 우승자: ${winners.join(", ")}`); + Console.print(`최종 우승자 : ${winners.join(", ")}`); }; const runCarRace = async () => { - try { - const movedCount = {}; - const carNames = await readCarNames(); - const attemptCount = await readAttemptCount(); + const movedCount = {}; + const carNames = await readCarNames(); + const attemptCount = await readAttemptCount(); - carNames.forEach((name) => { - movedCount[name] = 0; - }); - - Console.print("\n실행 결과\n"); + carNames.forEach((name) => { + movedCount[name] = 0; + }); - for (let i = 1; i <= attemptCount; i++) { - startRound(carNames, movedCount); - } + Console.print("\n실행 결과\n"); - printWinner(movedCount); - } catch (error) { - throw new Error(`[ERROR] ${error.message}`); + for (let i = 1; i <= attemptCount; i++) { + startRound(carNames, movedCount); } + + printWinner(movedCount); }; class App { async run() { - await runCarRace(); + try { + await runCarRace(); + } catch (error) { + throw new Error(`[ERROR] ${error.message}`); + } } } From 61b7a3f644394964e0e8cd529bcd84eec1d99b09 Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 22:30:45 +0900 Subject: [PATCH 12/13] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02da8f67..9c1c76c0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - `Console.readLineAsync()` - 출력: “경주할 자동차 이름을 입력하세요.” - `Console.readLineAsync()` -- 검증: 길이 5이하의 문자열인지 확인, 아니라면 에러 throw +- 검증: 길이 5이하의 문자열인지 확인, 아니라면 에러 throw / 중복된 이름이 있는지 확인, 있다면 에러 throw **시도할 횟수를 입력받고 검증하는 기능** @@ -27,7 +27,7 @@ **자동차를 전진하는 기능** - 랜덤값이 4 이상일 시 1 전진 -- 전진한 자동차를 배열에 push +- 전진한 자동차 정보를 저장 **실행 결과를 출력하는 기능** From 8858d53c72a00c7ea9144b106e1dce525026d25b Mon Sep 17 00:00:00 2001 From: bbbbmo Date: Mon, 27 Oct 2025 22:31:22 +0900 Subject: [PATCH 13/13] =?UTF-8?q?test:=20=EA=B8=B0=EB=8A=A5=20=EB=B3=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 22 ++++----- src/App.test.js | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 src/App.test.js diff --git a/src/App.js b/src/App.js index 156e4247..8ec9cc7f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,8 +1,8 @@ import { Console, MissionUtils } from "@woowacourse/mission-utils"; -const PROGRESS_BAR = "-"; +export const PROGRESS_BAR = "-"; -const validateCarName = (carName) => { +export const validateCarName = (carName) => { const trimmed = String(carName).trim(); if (!trimmed) { throw new Error("자동차 이름을 입력해주세요."); @@ -13,7 +13,7 @@ const validateCarName = (carName) => { return trimmed; }; -const checkNameDuplicate = (carNames) => { +export const checkNameDuplicate = (carNames) => { const nameSet = new Set(); for (const name of carNames) { if (nameSet.has(name)) { @@ -23,7 +23,7 @@ const checkNameDuplicate = (carNames) => { } }; -const readCarNames = async () => { +export const readCarNames = async () => { const input = await Console.readLineAsync( "경주할 자동차 이름을 입력하세요. (이름은 쉼표(,) 기준으로 구분) \n" ); @@ -35,7 +35,7 @@ const readCarNames = async () => { }; // 시도 횟수 유효성 검사 -const validateAttemptCount = (count) => { +export const validateAttemptCount = (count) => { if (count === undefined || count === null || String(count).trim() === "") { throw new Error("시도할 횟수를 입력해주세요."); } @@ -53,24 +53,24 @@ const validateAttemptCount = (count) => { } }; -const readAttemptCount = async () => { +export const readAttemptCount = async () => { const input = await Console.readLineAsync("시도할 횟수는 몇 회인가요? \n"); const value = validateAttemptCount(input); return value; }; -const randomMove = () => { +export const randomMove = () => { const randomValue = MissionUtils.Random.pickNumberInRange(0, 9); return randomValue >= 4; }; -const printProgress = (currentCar, movedCount) => { +export const printProgress = (currentCar, movedCount) => { const currentMove = movedCount[currentCar] || 0; const currentProgress = PROGRESS_BAR.repeat(currentMove); Console.print(`${currentCar} : ${currentProgress}`); }; -const startRound = (carNames, movedCount) => { +export const startRound = (carNames, movedCount) => { carNames.forEach((car) => { if (randomMove()) { movedCount[car] = (movedCount[car] || 0) + 1; @@ -80,7 +80,7 @@ const startRound = (carNames, movedCount) => { Console.print("\n"); }; -const printWinner = (movedCount) => { +export const printWinner = (movedCount) => { const maxMove = Math.max(...Object.values(movedCount)); const winners = Object.keys(movedCount).filter( (key) => movedCount[key] === maxMove @@ -88,7 +88,7 @@ const printWinner = (movedCount) => { Console.print(`최종 우승자 : ${winners.join(", ")}`); }; -const runCarRace = async () => { +export const runCarRace = async () => { const movedCount = {}; const carNames = await readCarNames(); const attemptCount = await readAttemptCount(); diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 00000000..83e38f12 --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,120 @@ +import { + checkNameDuplicate, + randomMove, + startRound, + validateAttemptCount, + validateCarName, +} from "./App"; + +describe("경주할 자동차 입력을 검증하는 기능 테스트", () => { + test("정상적인 자동차 이름", () => { + expect(validateCarName("pobi")).toBe("pobi"); + }); + + test("빈 문자열 예외", () => { + expect(() => validateCarName("")).toThrow("자동차 이름을 입력해주세요."); + }); + + test("5글자 초과 예외", () => { + expect(() => validateCarName("abcdef")).toThrow( + "자동차 이름은 5글자 이하로 입력해주세요." + ); + }); + + test("공백 제거", () => { + expect(validateCarName(" pobi ")).toBe("pobi"); + }); +}); +describe("경주할 자동차 이름 중복 검사 기능 테스트", () => { + test("중복 없는 경우", () => { + expect(() => checkNameDuplicate(["pobi", "woni", "jun"])).not.toThrow(); + }); + + test("중복 있는 경우", () => { + expect(() => checkNameDuplicate(["pobi", "woni", "pobi"])).toThrow( + "중복된 자동차 이름이 있습니다. 이름: pobi" + ); + }); +}); + +describe("시도 횟수 검증 기능 테스트", () => { + test("정상적인 시도 횟수", () => { + expect(validateAttemptCount("5")).toBe(5); + expect(validateAttemptCount("1")).toBe(1); + expect(validateAttemptCount("100")).toBe(100); + }); + + test("빈 값 예외", () => { + expect(() => validateAttemptCount("")).toThrow( + "시도할 횟수를 입력해주세요." + ); + expect(() => validateAttemptCount(undefined)).toThrow( + "시도할 횟수를 입력해주세요." + ); + expect(() => validateAttemptCount(null)).toThrow( + "시도할 횟수를 입력해주세요." + ); + }); + + test("숫자가 아닌 값 예외", () => { + expect(() => validateAttemptCount("abc")).toThrow( + "시도할 횟수는 숫자로 입력해주세요." + ); + expect(() => validateAttemptCount("5.5")).toThrow( + "시도할 횟수는 정수로 입력해주세요." + ); + }); + + test("0 이하 값 예외", () => { + expect(() => validateAttemptCount("0")).toThrow( + "시도할 횟수는 1 이상의 정수로 입력해주세요." + ); + expect(() => validateAttemptCount("-1")).toThrow( + "시도할 횟수는 1 이상의 정수로 입력해주세요." + ); + }); +}); + +describe("시도한 횟수만큼 반복문으로 랜덤값을 생성하고 전진하는 기능 테스트", () => { + test("랜덤값이 4 이상일 시 전진하는 기능 테스트", () => { + const movedCount = { pobi: 0, woni: 0, jun: 0 }; + movedCount["pobi"] = (movedCount["pobi"] || 0) + 1; + expect(movedCount["pobi"]).toBe(1); + }); + + test("실행 결과를 출력하는 기능 테스트", () => { + const currentCar = "pobi"; + const movedCount = { pobi: 3, woni: 2, jun: 1 }; + const PROGRESS_BAR = "-"; + + const currentMove = movedCount[currentCar] || 0; + const currentProgress = PROGRESS_BAR.repeat(currentMove); + + expect(currentMove).toBe(3); + expect(currentProgress).toBe("---"); + }); +}); + +describe("우승자 출력 기능 테스트", () => { + test("단일 우승자 찾기", () => { + const movedCount = { pobi: 3, woni: 2, jun: 1 }; + const maxMove = Math.max(...Object.values(movedCount)); + const winners = Object.keys(movedCount).filter( + (key) => movedCount[key] === maxMove + ); + + expect(maxMove).toBe(3); + expect(winners).toEqual(["pobi"]); + }); + + test("공동 우승자 찾기", () => { + const movedCount = { pobi: 2, woni: 2, jun: 1 }; + const maxMove = Math.max(...Object.values(movedCount)); + const winners = Object.keys(movedCount).filter( + (key) => movedCount[key] === maxMove + ); + + expect(maxMove).toBe(2); + expect(winners).toEqual(["pobi", "woni"]); + }); +});