From ae072b314af70c799eadfc8bafe417e11c182ce7 Mon Sep 17 00:00:00 2001 From: Terry Date: Tue, 11 Feb 2025 23:02:05 +0900 Subject: [PATCH 01/14] =?UTF-8?q?Refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=93=A4=20=EC=A0=84=EC=97=AD=20=EB=B3=80=EC=88=98=ED=99=94=20?= =?UTF-8?q?&=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/Car.test.js | 43 +++++++++++++++++++++++------------------- src/utils/constants.js | 5 +++++ 2 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 src/utils/constants.js diff --git a/__tests__/Car.test.js b/__tests__/Car.test.js index 76043f0c2..fc02d7245 100644 --- a/__tests__/Car.test.js +++ b/__tests__/Car.test.js @@ -1,14 +1,24 @@ +import { ERROR_MESSAGES } from "../src/utils/constants.js"; import Car from "../src/domain/Car.js"; +const carNames = { + N: "N", + NEXT: "NEXT", + NEXTSTEP: "NEXTSTEP", + MYCAR: "MYCAR", + G70: "G70", + GV80: "GV80", +}; + describe("자동차 세팅 테스트 ", () => { describe("자동차는", () => { let car; - beforeAll(() => { - car = new Car("NEXT"); + beforeEach(() => { + car = new Car(carNames.NEXT); }); it("이름을 가진다", () => { - expect(car.getName()).toEqual("NEXT"); + expect(car.getName()).toEqual(carNames.NEXT); }); it("위치 값을 가지며, 초기 값은 0이다.", () => { @@ -19,7 +29,7 @@ describe("자동차 세팅 테스트 ", () => { describe("자동차 동작 테스트", () => { it(" 자동차는 전진할 수 있으며 한 번에 1만큼 전진한다.", () => { - const car = new Car("NEXT"); + const car = new Car(carNames.NEXT); car.moveForward(); @@ -29,46 +39,41 @@ describe("자동차 동작 테스트", () => { describe("자동차에 이름을 부여할 수 있다.", () => { it("자동차는 생성 시 부여한 이름을 가진다.", () => { - const name = "GV80"; - const car = new Car(name); - expect(car.getName()).toBe(name); + const car = new Car(carNames.GV80); + expect(car.getName()).toBe(carNames.GV80); }); it("자동차는 여러 대가 생성될 수 있다.", () => { - const carNames = ["GV80", "G70"]; - const cars = carNames.map((car) => new Car(car)); - expect(cars.map((car) => car.getName())).toEqual(carNames); + const carArray = [carNames.GV80, carNames.G70]; + const cars = carArray.map((car) => new Car(car)); + expect(cars.map((car) => car.getName())).toEqual(carArray); }); }); describe("자동차 이름 유효성 테스트", () => { - const INVALID_CAR_NAME = "잘못된 자동차 이름입니다."; - it("이름이 1자 미만이면 에러를 던진다.", () => { const car = () => new Car(""); expect(car).toThrow(Error); - expect(car).toThrow(INVALID_CAR_NAME); + expect(car).toThrow(ERROR_MESSAGES.INVALID_CAR_NAME); }); it("이름이 1자면 유효성 검사에 통과한다.", () => { - const name = "N"; - const car = () => new Car(name); + const car = () => new Car(carNames.N); expect(car).not.toThrow(); }); it("이름이 5자이면 유효성 검사에 통과한다.", () => { - const name = "MYCAR"; - const car = () => new Car(name); + const car = () => new Car(carNames.MYCAR); expect(car).not.toThrow(); }); it("이름이 6자 이상이면 에러를 던진다.", () => { - const car = () => new Car("NEXTSTEP"); + const car = () => new Car(carNames.NEXTSTEP); expect(car).toThrow(Error); - expect(car).toThrow(INVALID_CAR_NAME); + expect(car).toThrow(ERROR_MESSAGES.INVALID_CAR_NAME); }); }); diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 000000000..6a7b1b754 --- /dev/null +++ b/src/utils/constants.js @@ -0,0 +1,5 @@ +export const ERROR_MESSAGES = { + INVALID_CAR_NAME: "자동차 이름은 1자 이상 5자 이하이어야 합니다.", + NOT_SPACE_IN_NAME: "이름 중간에 공백이 들어갈 수 없습니다.", + }; + \ No newline at end of file From 5711c6a13de01113ac722cd715edd14e032f8953 Mon Sep 17 00:00:00 2001 From: Terry Date: Tue, 11 Feb 2025 23:42:16 +0900 Subject: [PATCH 02/14] =?UTF-8?q?Refactor:=20readline=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=20=EB=B0=8F=20=EB=A7=A4=EC=A7=81=EB=84=98=EB=B2=84=20=EC=83=81?= =?UTF-8?q?=EC=88=98=ED=99=94=20&=20view=20=EB=A1=9C=EC=A7=81=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 input 사용자 입력 에러 핸들 & split 함수 분리 output 주석 제거 및 함수명 통일 --- src/Input.js | 34 ---------------------------------- src/main.js | 13 ++++++++----- src/utils/readline.js | 27 +++++++++++++++++++++++++++ src/view/Input.js | 25 +++++++++++++++++++++++++ src/{ => view}/Output.js | 6 +++--- 5 files changed, 63 insertions(+), 42 deletions(-) delete mode 100644 src/Input.js create mode 100644 src/utils/readline.js create mode 100644 src/view/Input.js rename src/{ => view}/Output.js (68%) diff --git a/src/Input.js b/src/Input.js deleted file mode 100644 index 06fb83ddf..000000000 --- a/src/Input.js +++ /dev/null @@ -1,34 +0,0 @@ -import readline from "readline"; -import Car from "./domain/Car.js"; - -class Input { - async askCarNames() { - const input = await this.readLineAsync("자동차 이름을 입력하세요. \n"); - const carNames = input.split(",").map((name) => new Car(name.trim())); - return carNames; - } - - readLineAsync(query) { - return new Promise((resolve, reject) => { - if (arguments.length !== 1) { - reject(new Error("arguments must be 1")); - } - - if (typeof query !== "string") { - reject(new Error("query must be string")); - } - - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - rl.question(query, (input) => { - rl.close(); - resolve(input); - }); - }); - } -} - -export default Input; diff --git a/src/main.js b/src/main.js index 7d2167496..5498e943e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,14 +1,17 @@ -import Input from "./Input.js"; +import Input from "../view/Input.js"; import Race from "./domain/Race.js"; -import Output from "./Output.js"; +import Output from "../view/Output.js"; +import Car from "./domain/Car.js"; async function main() { const input = new Input(); const output = new Output(); - - const cars = await input.askCarNames(); + + const carNames = await input.askCarNames(); + const cars = carNames.map((name) => new Car(name)); + const race = new Race(cars); - + race.start(); output.printRaceResult(race.result); } diff --git a/src/utils/readline.js b/src/utils/readline.js new file mode 100644 index 000000000..b9d5f50f7 --- /dev/null +++ b/src/utils/readline.js @@ -0,0 +1,27 @@ +import readline from "readline"; + +const MIN_LENGTH = 1; + +function readLineAsync(query) { + return new Promise((resolve, reject) => { + if (arguments.length !== MIN_LENGTH) { + reject(new Error("arguments must be 1")); + } + + if (typeof query !== "string") { + reject(new Error("query must be string")); + } + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + rl.question(query, (input) => { + rl.close(); + resolve(input); + }); + }); +} + +export default readLineAsync; diff --git a/src/view/Input.js b/src/view/Input.js new file mode 100644 index 000000000..d5b10782b --- /dev/null +++ b/src/view/Input.js @@ -0,0 +1,25 @@ +import { ERROR_MESSAGES } from "../src/utils/constants.js"; +import readLineAsync from "../src/utils/readline.js"; + +class Input { + async askCarNames() { + const input = await readLineAsync("자동차 이름을 입력하세요. \n"); + const carNames = this.splitBy(input, ","); + + if (!this.validateNotSpace(carNames)) { + throw new Error(ERROR_MESSAGES.NOT_SPACE_IN_NAME); + } + + return carNames; + } + + splitBy(string, separator) { + return string.split(separator).map((name) => name.trim()); + } + + validateNotSpace(names) { + return names.every((name) => !name.includes(" ")); + } +} + +export default Input; diff --git a/src/Output.js b/src/view/Output.js similarity index 68% rename from src/Output.js rename to src/view/Output.js index 4ab657c0c..10ad50ab5 100644 --- a/src/Output.js +++ b/src/view/Output.js @@ -2,17 +2,17 @@ class Output { printRaceResult(result) { console.log("\n실행결과"); - // 라운드 마다 차 위치를 프린트한다. const roundWithCars = result.map((round) => round.cars); roundWithCars.forEach((round) => { - round.forEach((car) => this.printRoundResult(car.name, car.location)); + round.forEach((car) => this.printCarLocation(car.name, car.location)); + // 라운드 구분 console.log(""); }); } - printCarStatus(name, location) { + printCarLocation(name, location) { console.log(`${name} : ${"-".repeat(location)}`); } } From fbfce558c301127bbf1904e4ac85f7649678ece7 Mon Sep 17 00:00:00 2001 From: Terry Date: Tue, 11 Feb 2025 23:44:15 +0900 Subject: [PATCH 03/14] =?UTF-8?q?Refactor:=20view=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main 파일에서 try-catch 에러처리 \n-output 에러 메세지 출력 함수 추가 --- src/domain/Car.js | 5 +++-- src/main.js | 18 +++++++++++------- src/view/Input.js | 5 ++--- src/view/Output.js | 4 ++++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/domain/Car.js b/src/domain/Car.js index 7b6e5517f..782ce2905 100644 --- a/src/domain/Car.js +++ b/src/domain/Car.js @@ -1,11 +1,12 @@ +import { ERROR_MESSAGES } from '../utils/constants.js'; + class Car { - static INVALID_CAR_NAME = "잘못된 자동차 이름입니다."; #name; #location = 0; constructor(name) { if (!this.isValidName(name)) { - throw new Error(Car.INVALID_CAR_NAME); + throw new Error(ERROR_MESSAGES.INVALID_CAR_NAME); } this.#name = name; } diff --git a/src/main.js b/src/main.js index 5498e943e..4da1c1400 100644 --- a/src/main.js +++ b/src/main.js @@ -1,19 +1,23 @@ -import Input from "../view/Input.js"; +import Input from "./view/Input.js"; import Race from "./domain/Race.js"; -import Output from "../view/Output.js"; +import Output from "./view/Output.js"; import Car from "./domain/Car.js"; async function main() { const input = new Input(); const output = new Output(); - const carNames = await input.askCarNames(); - const cars = carNames.map((name) => new Car(name)); + try { + const carNames = await input.askCarNames(); + const cars = carNames.map((name) => new Car(name)); - const race = new Race(cars); + const race = new Race(cars); - race.start(); - output.printRaceResult(race.result); + race.start(); + output.printRaceResult(race.result); + } catch (error) { + output.printErrorMessage(error); + } } main(); diff --git a/src/view/Input.js b/src/view/Input.js index d5b10782b..5382285c0 100644 --- a/src/view/Input.js +++ b/src/view/Input.js @@ -1,5 +1,5 @@ -import { ERROR_MESSAGES } from "../src/utils/constants.js"; -import readLineAsync from "../src/utils/readline.js"; +import { ERROR_MESSAGES } from "../utils/constants.js"; +import readLineAsync from "../utils/readline.js"; class Input { async askCarNames() { @@ -9,7 +9,6 @@ class Input { if (!this.validateNotSpace(carNames)) { throw new Error(ERROR_MESSAGES.NOT_SPACE_IN_NAME); } - return carNames; } diff --git a/src/view/Output.js b/src/view/Output.js index 10ad50ab5..d8cd30d16 100644 --- a/src/view/Output.js +++ b/src/view/Output.js @@ -15,6 +15,10 @@ class Output { printCarLocation(name, location) { console.log(`${name} : ${"-".repeat(location)}`); } + + printErrorMessage(error) { + console.log(error.message); + } } export default Output; From 7306715856f737fa7f17f7fb5a8123aafdabaf3b Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 00:40:29 +0900 Subject: [PATCH 04/14] =?UTF-8?q?Refactor:=20race=20start=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B4=80=EC=8B=AC=EC=82=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/Race.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/domain/Race.js b/src/domain/Race.js index 4b7839797..19f18e2fd 100644 --- a/src/domain/Race.js +++ b/src/domain/Race.js @@ -17,16 +17,19 @@ class Race { start() { for (let round = 1; round <= this.rounds; round++) { this.moveCars(this.cars); - - this.result.push({ - round: round, - cars: this.cars.map((car) => ({ - name: car.getName(), - location: car.getLocation(), - })), - }); + this.recordRoundResult(round, this.cars); } } + + recordRoundResult(round, cars) { + this.result.push({ + round: round, + cars: cars.map((car) => ({ + name: car.getName(), + location: car.getLocation(), + })), + }); + } } export default Race; From a3f90fa5b7e302af76882b4b123a4d7c5eb1fbc1 Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 00:41:15 +0900 Subject: [PATCH 05/14] =?UTF-8?q?Feat:=20Input=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EC=83=81=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/Car.test.js | 10 +--------- __tests__/Input.test.js | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/utils/constants.js | 18 ++++++++++++++---- 4 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 __tests__/Input.test.js diff --git a/__tests__/Car.test.js b/__tests__/Car.test.js index fc02d7245..3a55bb805 100644 --- a/__tests__/Car.test.js +++ b/__tests__/Car.test.js @@ -1,14 +1,6 @@ import { ERROR_MESSAGES } from "../src/utils/constants.js"; import Car from "../src/domain/Car.js"; - -const carNames = { - N: "N", - NEXT: "NEXT", - NEXTSTEP: "NEXTSTEP", - MYCAR: "MYCAR", - G70: "G70", - GV80: "GV80", -}; +import { carNames } from "../src/utils/constants.js"; describe("자동차 세팅 테스트 ", () => { describe("자동차는", () => { diff --git a/__tests__/Input.test.js b/__tests__/Input.test.js new file mode 100644 index 000000000..806ac87f3 --- /dev/null +++ b/__tests__/Input.test.js @@ -0,0 +1,38 @@ +import Input from "../src/view/Input"; +import { carNames } from "../src/utils/constants"; + +describe("Input 유효성 메서드 테스트", () => { + let input; + beforeEach(() => { + input = new Input(); + }); + + describe("이름 안에 공백이 포함된 경우", () => { + it("이름에 공백이 있으면 false를 반환한다.", () => { + const validate = input.validateNotSpace([carNames.NAME_SPACE]); + expect(validate).toBeFalsy(); + }); + + it("이름에 공백이 없으면 true를 반환한다.", () => { + const validate = input.validateNotSpace([carNames.NAME]); + expect(validate).toBeTruthy(); + }); + }); + + describe("SplitBy 메서드", () => { + it("쉼표로 구분된 문자열을 배열로 변환한다.", () => { + const result = input.splitBy("G70,GV80,NEXTSTEP", ","); + expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); + }); + + it("-로 구분된 문자열을 배열로 변환한다.", () => { + const result = input.splitBy("G70-GV80-NEXTSTEP", "-"); + expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); + }); + + it("이름의 앞 뒤 공백을 제거하고 배열로 변환한다.", () => { + const result = input.splitBy(" G70,GV80 , NEXTSTEP ", ","); + expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); + }); + }); +}); diff --git a/package.json b/package.json index aa7569bde..35050b5ac 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "vite": "^6.1.0" }, "jest": { + "verbose": true, "transform": { "^.+\\.js$": "babel-jest" } diff --git a/src/utils/constants.js b/src/utils/constants.js index 6a7b1b754..886f4c61d 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,5 +1,15 @@ export const ERROR_MESSAGES = { - INVALID_CAR_NAME: "자동차 이름은 1자 이상 5자 이하이어야 합니다.", - NOT_SPACE_IN_NAME: "이름 중간에 공백이 들어갈 수 없습니다.", - }; - \ No newline at end of file + INVALID_CAR_NAME: "자동차 이름은 1자 이상 5자 이하이어야 합니다.", + NOT_SPACE_IN_NAME: "이름 중간에 공백이 들어갈 수 없습니다.", +}; + +export const carNames = { + NAME: "NAME", + NAME_SPACE: "N AME", + N: "N", + NEXT: "NEXT", + NEXTSTEP: "NEXTSTEP", + MYCAR: "MYCAR", + G70: "G70", + GV80: "GV80", +}; From 558694311f2cb71138bd698010a25c5901e2bbe6 Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 00:41:33 +0900 Subject: [PATCH 06/14] =?UTF-8?q?Docs:=20Step4=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/REQUIREMENTS.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index a858571e9..c6fe91121 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -1,8 +1,10 @@ ## 요구사항 -- [ ] 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. -- [ ] 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. -- [ ] 자동차 경주는 5회로 고정하여 진행한다. -- [ ] 자동차는 1회당 1칸씩 전진한다 -- [ ] 회차를 거듭할 때마다 자동차가 지나간 궤적을 출력한다(실행 예시 참고). -- [ ] 사용자가 잘못된 입력 값을 작성한 경우 프로그램을 종료한다. +- [X] 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +- [X] 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- [ ] 자동차 경주 횟수는 사용자에게 입력 받는다. +- [ ] 자동차 경주는 사용자 입력이 없다면 5회로 고정하여 진행한다. +- [ ] 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- [X] 자동차는 1회당 1칸씩 전진한다 +- [X] 회차를 거듭할 때마다 자동차가 지나간 궤적을 출력한다(실행 예시 참고). +- [X] 사용자가 잘못된 입력 값을 작성한 경우 프로그램을 종료한다. From 4e199a93e2dc63aeae018423d355e429e08dad4d Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 15:44:38 +0900 Subject: [PATCH 07/14] =?UTF-8?q?Env:=20eslint=20no-unused-vars=20warn=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index abd17f708..4760876cc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -13,7 +13,7 @@ export default [ }, rules: { eqeqeq: ["error", "always"], - "no-unused-vars": "error", + "no-unused-vars": "warn", "no-var": "error", "no-else-return": "error", }, From 7020410f7c7b259e0950c5ea1a8f653adcb33a64 Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 15:46:33 +0900 Subject: [PATCH 08/14] =?UTF-8?q?Feat:=20Input=20=EB=B9=84=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=20=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=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/utils/constants.js | 5 +++-- src/view/Input.js | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/utils/constants.js b/src/utils/constants.js index 886f4c61d..f0c5565ed 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,6 +1,7 @@ export const ERROR_MESSAGES = { - INVALID_CAR_NAME: "자동차 이름은 1자 이상 5자 이하이어야 합니다.", - NOT_SPACE_IN_NAME: "이름 중간에 공백이 들어갈 수 없습니다.", + INVALID_CAR_NAME: "[ERROR] 자동차 이름은 1자 이상 5자 이하이어야 합니다.", + NOT_SPACE_IN_NAME: "[ERROR] 이름 중간에 공백이 들어갈 수 없습니다.", + INPUT_ASYNC_ERROR: "[ERROR] 인풋 비동기화 처리중 에러 발생.", }; export const carNames = { diff --git a/src/view/Input.js b/src/view/Input.js index 5382285c0..0b9ef096b 100644 --- a/src/view/Input.js +++ b/src/view/Input.js @@ -3,13 +3,18 @@ import readLineAsync from "../utils/readline.js"; class Input { async askCarNames() { - const input = await readLineAsync("자동차 이름을 입력하세요. \n"); - const carNames = this.splitBy(input, ","); + try { + const input = await readLineAsync("자동차 이름을 입력하세요. \n"); + const carNames = this.splitBy(input, ","); - if (!this.validateNotSpace(carNames)) { - throw new Error(ERROR_MESSAGES.NOT_SPACE_IN_NAME); + if (!this.validateNotSpace(carNames)) { + throw new Error(ERROR_MESSAGES.NOT_SPACE_IN_NAME); + } + + return carNames; + } catch (error) { + throw new Error(ERROR_MESSAGES.INPUT_ASYNC_ERROR); } - return carNames; } splitBy(string, separator) { From e86c2de876d1c6f17d79e224ccb50b0699d6f506 Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 16:40:12 +0900 Subject: [PATCH 09/14] =?UTF-8?q?Feat:=20=EC=9E=90=EB=8F=99=EC=B0=A8=20?= =?UTF-8?q?=EA=B2=BD=EC=A3=BC=20=ED=9A=9F=EC=88=98=EB=8A=94=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EC=97=90=EA=B2=8C=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B0=9B=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Input 함수들 역할에 알맞은 이름 변경 --- __tests__/Input.test.js | 33 +++++++++++++++++++++++++++++++-- src/main.js | 3 ++- src/utils/constants.js | 1 + src/view/Input.js | 25 +++++++++++++++++++++---- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/__tests__/Input.test.js b/__tests__/Input.test.js index 806ac87f3..a64199430 100644 --- a/__tests__/Input.test.js +++ b/__tests__/Input.test.js @@ -9,12 +9,12 @@ describe("Input 유효성 메서드 테스트", () => { describe("이름 안에 공백이 포함된 경우", () => { it("이름에 공백이 있으면 false를 반환한다.", () => { - const validate = input.validateNotSpace([carNames.NAME_SPACE]); + const validate = input.isValidNotSpace([carNames.NAME_SPACE]); expect(validate).toBeFalsy(); }); it("이름에 공백이 없으면 true를 반환한다.", () => { - const validate = input.validateNotSpace([carNames.NAME]); + const validate = input.isValidNotSpace([carNames.NAME]); expect(validate).toBeTruthy(); }); }); @@ -35,4 +35,33 @@ describe("Input 유효성 메서드 테스트", () => { expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); }); }); + + describe("경주 횟수 입력 유효성 검사", () => { + it("사용자가 양수를 입력하면 true를 반환한다.", () => { + const result = input.isValidInteger(1); + expect(result).toBeTruthy(); + }); + + describe("사용자가 양수가 아닌", () => { + it("0 입력 시 false를 반환한다.", () => { + const result = input.isValidInteger(0); + expect(result).toBeFalsy(); + }); + + it("음수 입력 시 false를 반환한다.", () => { + const result = input.isValidInteger(-1); + expect(result).toBeFalsy(); + }); + + it("유리수 입력 시 false를 반환한다.", () => { + const result = input.isValidInteger(1.11); + expect(result).toBeFalsy(); + }); + + it("String 입력 시 false를 반환한다.", () => { + const result = input.isValidInteger("일"); + expect(result).toBeFalsy(); + }); + }); + }); }); diff --git a/src/main.js b/src/main.js index 4da1c1400..dbbf942f7 100644 --- a/src/main.js +++ b/src/main.js @@ -8,8 +8,9 @@ async function main() { const output = new Output(); try { - const carNames = await input.askCarNames(); + const carNames = await input.getCarNames(); const cars = carNames.map((name) => new Car(name)); + const rounds = await input.getRoundCount(); const race = new Race(cars); diff --git a/src/utils/constants.js b/src/utils/constants.js index f0c5565ed..5c411848d 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,6 +1,7 @@ export const ERROR_MESSAGES = { INVALID_CAR_NAME: "[ERROR] 자동차 이름은 1자 이상 5자 이하이어야 합니다.", NOT_SPACE_IN_NAME: "[ERROR] 이름 중간에 공백이 들어갈 수 없습니다.", + INVALID_RACE_COUNT: "[ERROR] 경기 횟수는 양수만 입력할 수 있습니다.", INPUT_ASYNC_ERROR: "[ERROR] 인풋 비동기화 처리중 에러 발생.", }; diff --git a/src/view/Input.js b/src/view/Input.js index 0b9ef096b..affc72a2b 100644 --- a/src/view/Input.js +++ b/src/view/Input.js @@ -2,12 +2,14 @@ import { ERROR_MESSAGES } from "../utils/constants.js"; import readLineAsync from "../utils/readline.js"; class Input { - async askCarNames() { + async getCarNames() { try { - const input = await readLineAsync("자동차 이름을 입력하세요. \n"); + const input = await readLineAsync( + "경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분). \n" + ); const carNames = this.splitBy(input, ","); - if (!this.validateNotSpace(carNames)) { + if (!this.isValidNotSpace(carNames)) { throw new Error(ERROR_MESSAGES.NOT_SPACE_IN_NAME); } @@ -17,13 +19,28 @@ class Input { } } + async getRoundCount() { + const input = await readLineAsync("시도할 회수는 몇회인가요? \n"); + + if (!this.isValidInteger(input)) { + throw new Error(ERROR_MESSAGES.INVALID_RACE_COUNT); + } + + return input; + } + splitBy(string, separator) { return string.split(separator).map((name) => name.trim()); } - validateNotSpace(names) { + isValidNotSpace(names) { return names.every((name) => !name.includes(" ")); } + + isValidInteger(value) { + const number = parseFloat(value); + return !isNaN(number) && number > 0 && Number.isInteger(number); + } } export default Input; From 39687df2e6da72c2a8cf4bb88978b390e9ef7d8d Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 17:21:07 +0900 Subject: [PATCH 10/14] =?UTF-8?q?Fix:=20=EC=A0=81=ED=95=A9=ED=95=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/Input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/Input.js b/src/view/Input.js index affc72a2b..2f498cade 100644 --- a/src/view/Input.js +++ b/src/view/Input.js @@ -15,7 +15,7 @@ class Input { return carNames; } catch (error) { - throw new Error(ERROR_MESSAGES.INPUT_ASYNC_ERROR); + throw new Error(error.message); } } From 30e971679e001c2056fb3b36e106bf5adcca780d Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 19:30:56 +0900 Subject: [PATCH 11/14] =?UTF-8?q?Feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B4=EB=93=9C=20=EC=9E=85=EB=A0=A5=20trim=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=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 - 자동차 경주 횟수는 입력이 없다면 5회로 고정하여 진행한다. --- __tests__/Car.test.js | 20 ++++++++++---------- __tests__/Input.test.js | 12 ++++++------ __tests__/Race.test.js | 29 +++++++++++++++++------------ docs/REQUIREMENTS.md | 4 ++-- src/domain/Race.js | 4 ++-- src/main.js | 4 ++-- src/utils/constants.js | 8 +++++++- src/view/Input.js | 8 ++++++++ 8 files changed, 54 insertions(+), 35 deletions(-) diff --git a/__tests__/Car.test.js b/__tests__/Car.test.js index 3a55bb805..eb2612681 100644 --- a/__tests__/Car.test.js +++ b/__tests__/Car.test.js @@ -1,16 +1,16 @@ import { ERROR_MESSAGES } from "../src/utils/constants.js"; import Car from "../src/domain/Car.js"; -import { carNames } from "../src/utils/constants.js"; +import { CARNAMES } from "../src/utils/constants.js"; describe("자동차 세팅 테스트 ", () => { describe("자동차는", () => { let car; beforeEach(() => { - car = new Car(carNames.NEXT); + car = new Car(CARNAMES.NEXT); }); it("이름을 가진다", () => { - expect(car.getName()).toEqual(carNames.NEXT); + expect(car.getName()).toEqual(CARNAMES.NEXT); }); it("위치 값을 가지며, 초기 값은 0이다.", () => { @@ -21,7 +21,7 @@ describe("자동차 세팅 테스트 ", () => { describe("자동차 동작 테스트", () => { it(" 자동차는 전진할 수 있으며 한 번에 1만큼 전진한다.", () => { - const car = new Car(carNames.NEXT); + const car = new Car(CARNAMES.NEXT); car.moveForward(); @@ -31,12 +31,12 @@ describe("자동차 동작 테스트", () => { describe("자동차에 이름을 부여할 수 있다.", () => { it("자동차는 생성 시 부여한 이름을 가진다.", () => { - const car = new Car(carNames.GV80); - expect(car.getName()).toBe(carNames.GV80); + const car = new Car(CARNAMES.GV80); + expect(car.getName()).toBe(CARNAMES.GV80); }); it("자동차는 여러 대가 생성될 수 있다.", () => { - const carArray = [carNames.GV80, carNames.G70]; + const carArray = [CARNAMES.GV80, CARNAMES.G70]; const cars = carArray.map((car) => new Car(car)); expect(cars.map((car) => car.getName())).toEqual(carArray); }); @@ -51,19 +51,19 @@ describe("자동차 이름 유효성 테스트", () => { }); it("이름이 1자면 유효성 검사에 통과한다.", () => { - const car = () => new Car(carNames.N); + const car = () => new Car(CARNAMES.N); expect(car).not.toThrow(); }); it("이름이 5자이면 유효성 검사에 통과한다.", () => { - const car = () => new Car(carNames.MYCAR); + const car = () => new Car(CARNAMES.MYCAR); expect(car).not.toThrow(); }); it("이름이 6자 이상이면 에러를 던진다.", () => { - const car = () => new Car(carNames.NEXTSTEP); + const car = () => new Car(CARNAMES.NEXTSTEP); expect(car).toThrow(Error); expect(car).toThrow(ERROR_MESSAGES.INVALID_CAR_NAME); diff --git a/__tests__/Input.test.js b/__tests__/Input.test.js index a64199430..030ab381a 100644 --- a/__tests__/Input.test.js +++ b/__tests__/Input.test.js @@ -1,5 +1,5 @@ import Input from "../src/view/Input"; -import { carNames } from "../src/utils/constants"; +import { CARNAMES } from "../src/utils/constants"; describe("Input 유효성 메서드 테스트", () => { let input; @@ -9,12 +9,12 @@ describe("Input 유효성 메서드 테스트", () => { describe("이름 안에 공백이 포함된 경우", () => { it("이름에 공백이 있으면 false를 반환한다.", () => { - const validate = input.isValidNotSpace([carNames.NAME_SPACE]); + const validate = input.isValidNotSpace([CARNAMES.NAME_SPACE]); expect(validate).toBeFalsy(); }); it("이름에 공백이 없으면 true를 반환한다.", () => { - const validate = input.isValidNotSpace([carNames.NAME]); + const validate = input.isValidNotSpace([CARNAMES.NAME]); expect(validate).toBeTruthy(); }); }); @@ -22,17 +22,17 @@ describe("Input 유효성 메서드 테스트", () => { describe("SplitBy 메서드", () => { it("쉼표로 구분된 문자열을 배열로 변환한다.", () => { const result = input.splitBy("G70,GV80,NEXTSTEP", ","); - expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); + expect(result).toEqual([CARNAMES.G70, CARNAMES.GV80, CARNAMES.NEXTSTEP]); }); it("-로 구분된 문자열을 배열로 변환한다.", () => { const result = input.splitBy("G70-GV80-NEXTSTEP", "-"); - expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); + expect(result).toEqual([CARNAMES.G70, CARNAMES.GV80, CARNAMES.NEXTSTEP]); }); it("이름의 앞 뒤 공백을 제거하고 배열로 변환한다.", () => { const result = input.splitBy(" G70,GV80 , NEXTSTEP ", ","); - expect(result).toEqual([carNames.G70, carNames.GV80, carNames.NEXTSTEP]); + expect(result).toEqual([CARNAMES.G70, CARNAMES.GV80, CARNAMES.NEXTSTEP]); }); }); diff --git a/__tests__/Race.test.js b/__tests__/Race.test.js index 2ecc2012d..c3b70da28 100644 --- a/__tests__/Race.test.js +++ b/__tests__/Race.test.js @@ -1,7 +1,8 @@ import Car from "../src/domain/Car"; import Race from "../src/domain/Race"; +import { CARNAMES, ROUNDS } from "../src/utils/constants"; -describe("경주 테스트", () => { +describe("기본 경주 테스트", () => { let cars; let race; @@ -15,9 +16,8 @@ describe("경주 테스트", () => { expect(race.cars).toEqual(cars); }); - it("경주는 5회로 고정하여 진행한다.", () => { - const DEFAULT_ROUNDS = 5; - expect(race.rounds).toBe(DEFAULT_ROUNDS); + it("경주는 기본 5회로 진행한다.", () => { + expect(race.rounds).toBe(Race.DEFAULT_ROUNDS); }); }); @@ -31,29 +31,34 @@ describe("경주 테스트", () => { }); it("1회에는 한 칸 이동한다.", () => { - const FIRST_ROUND = 1; - const firstRound = result.find( - (round) => round.round === FIRST_ROUND * MOVE_DISTANCE + (round) => round.round === ROUNDS.ONE * MOVE_DISTANCE ); const allCarsFirstMovedLocation = firstRound.cars.every( - (car) => car.location === FIRST_ROUND + (car) => car.location === ROUNDS.ONE ); expect(allCarsFirstMovedLocation).toBeTruthy(); }); it("5회에는 다섯 칸 이동한다.", () => { - const TOTAL_ROUND = 5; - const finalRound = result.find( - (round) => round.round === TOTAL_ROUND * MOVE_DISTANCE + (round) => round.round === ROUNDS.FIVE * MOVE_DISTANCE ); const allCarsFinalLocation = finalRound.cars.every( - (car) => car.location === TOTAL_ROUND * MOVE_DISTANCE + (car) => car.location === ROUNDS.FIVE * MOVE_DISTANCE ); expect(allCarsFinalLocation).toBeTruthy(); }); }); }); + +describe("사용자 입력 경주 테스트", () => { + it("사용자가 10을 입력하면 경주를 10회 진행한다.", () => { + const cars = [new Car(CARNAMES.G70), new Car(CARNAMES.GV80)]; + const race = new Race(cars, ROUNDS.TEN); + + expect(race.rounds).toBe(ROUNDS.TEN); + }); +}); diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index c6fe91121..f98259f20 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -2,8 +2,8 @@ - [X] 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. - [X] 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. -- [ ] 자동차 경주 횟수는 사용자에게 입력 받는다. -- [ ] 자동차 경주는 사용자 입력이 없다면 5회로 고정하여 진행한다. +- [X] 자동차 경주 횟수는 사용자에게 입력 받는다. +- [X] 자동차 경주는 사용자 입력이 없다면 5회로 고정하여 진행한다. - [ ] 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. - [X] 자동차는 1회당 1칸씩 전진한다 - [X] 회차를 거듭할 때마다 자동차가 지나간 궤적을 출력한다(실행 예시 참고). diff --git a/src/domain/Race.js b/src/domain/Race.js index 19f18e2fd..79519fc46 100644 --- a/src/domain/Race.js +++ b/src/domain/Race.js @@ -4,9 +4,9 @@ class Race { rounds; result = []; - constructor(cars) { + constructor(cars, rounds = Race.DEFAULT_ROUNDS) { this.cars = cars; - this.rounds = Race.DEFAULT_ROUNDS; + this.rounds = rounds; } moveCars(cars) { diff --git a/src/main.js b/src/main.js index dbbf942f7..d3b76723b 100644 --- a/src/main.js +++ b/src/main.js @@ -12,8 +12,8 @@ async function main() { const cars = carNames.map((name) => new Car(name)); const rounds = await input.getRoundCount(); - const race = new Race(cars); - + const race = new Race(cars, rounds); + race.start(); output.printRaceResult(race.result); } catch (error) { diff --git a/src/utils/constants.js b/src/utils/constants.js index 5c411848d..3364dbfb1 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -5,7 +5,7 @@ export const ERROR_MESSAGES = { INPUT_ASYNC_ERROR: "[ERROR] 인풋 비동기화 처리중 에러 발생.", }; -export const carNames = { +export const CARNAMES = { NAME: "NAME", NAME_SPACE: "N AME", N: "N", @@ -15,3 +15,9 @@ export const carNames = { G70: "G70", GV80: "GV80", }; + +export const ROUNDS = { + ONE : 1, + FIVE : 5, + TEN : 10, +} \ No newline at end of file diff --git a/src/view/Input.js b/src/view/Input.js index 2f498cade..907f70d62 100644 --- a/src/view/Input.js +++ b/src/view/Input.js @@ -22,6 +22,10 @@ class Input { async getRoundCount() { const input = await readLineAsync("시도할 회수는 몇회인가요? \n"); + if (this.isBlank(input)) { + return; + } + if (!this.isValidInteger(input)) { throw new Error(ERROR_MESSAGES.INVALID_RACE_COUNT); } @@ -41,6 +45,10 @@ class Input { const number = parseFloat(value); return !isNaN(number) && number > 0 && Number.isInteger(number); } + + isBlank(value) { + return value.trim() === ""; + } } export default Input; From ecab91f75e86663e27feec88b2a643af0b0ba54d Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 19:33:28 +0900 Subject: [PATCH 12/14] =?UTF-8?q?Style:=20=EC=83=81=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/Car.test.js | 20 ++++++++++---------- __tests__/Input.test.js | 18 +++++++++--------- __tests__/Race.test.js | 4 ++-- src/utils/constants.js | 14 +++++++------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/__tests__/Car.test.js b/__tests__/Car.test.js index eb2612681..40b1d7b7d 100644 --- a/__tests__/Car.test.js +++ b/__tests__/Car.test.js @@ -1,16 +1,16 @@ import { ERROR_MESSAGES } from "../src/utils/constants.js"; import Car from "../src/domain/Car.js"; -import { CARNAMES } from "../src/utils/constants.js"; +import { CAR_NAMES } from "../src/utils/constants.js"; describe("자동차 세팅 테스트 ", () => { describe("자동차는", () => { let car; beforeEach(() => { - car = new Car(CARNAMES.NEXT); + car = new Car(CAR_NAMES.NEXT); }); it("이름을 가진다", () => { - expect(car.getName()).toEqual(CARNAMES.NEXT); + expect(car.getName()).toEqual(CAR_NAMES.NEXT); }); it("위치 값을 가지며, 초기 값은 0이다.", () => { @@ -21,7 +21,7 @@ describe("자동차 세팅 테스트 ", () => { describe("자동차 동작 테스트", () => { it(" 자동차는 전진할 수 있으며 한 번에 1만큼 전진한다.", () => { - const car = new Car(CARNAMES.NEXT); + const car = new Car(CAR_NAMES.NEXT); car.moveForward(); @@ -31,12 +31,12 @@ describe("자동차 동작 테스트", () => { describe("자동차에 이름을 부여할 수 있다.", () => { it("자동차는 생성 시 부여한 이름을 가진다.", () => { - const car = new Car(CARNAMES.GV80); - expect(car.getName()).toBe(CARNAMES.GV80); + const car = new Car(CAR_NAMES.GV80); + expect(car.getName()).toBe(CAR_NAMES.GV80); }); it("자동차는 여러 대가 생성될 수 있다.", () => { - const carArray = [CARNAMES.GV80, CARNAMES.G70]; + const carArray = [CAR_NAMES.GV80, CAR_NAMES.G70]; const cars = carArray.map((car) => new Car(car)); expect(cars.map((car) => car.getName())).toEqual(carArray); }); @@ -51,19 +51,19 @@ describe("자동차 이름 유효성 테스트", () => { }); it("이름이 1자면 유효성 검사에 통과한다.", () => { - const car = () => new Car(CARNAMES.N); + const car = () => new Car(CAR_NAMES.N); expect(car).not.toThrow(); }); it("이름이 5자이면 유효성 검사에 통과한다.", () => { - const car = () => new Car(CARNAMES.MYCAR); + const car = () => new Car(CAR_NAMES.M_CAR); expect(car).not.toThrow(); }); it("이름이 6자 이상이면 에러를 던진다.", () => { - const car = () => new Car(CARNAMES.NEXTSTEP); + const car = () => new Car(CAR_NAMES.NEXT_STEP); expect(car).toThrow(Error); expect(car).toThrow(ERROR_MESSAGES.INVALID_CAR_NAME); diff --git a/__tests__/Input.test.js b/__tests__/Input.test.js index 030ab381a..723c44257 100644 --- a/__tests__/Input.test.js +++ b/__tests__/Input.test.js @@ -1,5 +1,5 @@ import Input from "../src/view/Input"; -import { CARNAMES } from "../src/utils/constants"; +import { CAR_NAMES } from "../src/utils/constants"; describe("Input 유효성 메서드 테스트", () => { let input; @@ -9,30 +9,30 @@ describe("Input 유효성 메서드 테스트", () => { describe("이름 안에 공백이 포함된 경우", () => { it("이름에 공백이 있으면 false를 반환한다.", () => { - const validate = input.isValidNotSpace([CARNAMES.NAME_SPACE]); + const validate = input.isValidNotSpace([CAR_NAMES.NAME_SPACE]); expect(validate).toBeFalsy(); }); it("이름에 공백이 없으면 true를 반환한다.", () => { - const validate = input.isValidNotSpace([CARNAMES.NAME]); + const validate = input.isValidNotSpace([CAR_NAMES.NAME]); expect(validate).toBeTruthy(); }); }); describe("SplitBy 메서드", () => { it("쉼표로 구분된 문자열을 배열로 변환한다.", () => { - const result = input.splitBy("G70,GV80,NEXTSTEP", ","); - expect(result).toEqual([CARNAMES.G70, CARNAMES.GV80, CARNAMES.NEXTSTEP]); + const result = input.splitBy("G70,GV80,NEXT_STEP", ","); + expect(result).toEqual([CAR_NAMES.G70, CAR_NAMES.GV80, CAR_NAMES.NEXT_STEP]); }); it("-로 구분된 문자열을 배열로 변환한다.", () => { - const result = input.splitBy("G70-GV80-NEXTSTEP", "-"); - expect(result).toEqual([CARNAMES.G70, CARNAMES.GV80, CARNAMES.NEXTSTEP]); + const result = input.splitBy("G70-GV80-NEXT_STEP", "-"); + expect(result).toEqual([CAR_NAMES.G70, CAR_NAMES.GV80, CAR_NAMES.NEXT_STEP]); }); it("이름의 앞 뒤 공백을 제거하고 배열로 변환한다.", () => { - const result = input.splitBy(" G70,GV80 , NEXTSTEP ", ","); - expect(result).toEqual([CARNAMES.G70, CARNAMES.GV80, CARNAMES.NEXTSTEP]); + const result = input.splitBy(" G70,GV80 , NEXT_STEP ", ","); + expect(result).toEqual([CAR_NAMES.G70, CAR_NAMES.GV80, CAR_NAMES.NEXT_STEP]); }); }); diff --git a/__tests__/Race.test.js b/__tests__/Race.test.js index c3b70da28..da3e39f93 100644 --- a/__tests__/Race.test.js +++ b/__tests__/Race.test.js @@ -1,6 +1,6 @@ import Car from "../src/domain/Car"; import Race from "../src/domain/Race"; -import { CARNAMES, ROUNDS } from "../src/utils/constants"; +import { CAR_NAMES, ROUNDS } from "../src/utils/constants"; describe("기본 경주 테스트", () => { let cars; @@ -56,7 +56,7 @@ describe("기본 경주 테스트", () => { describe("사용자 입력 경주 테스트", () => { it("사용자가 10을 입력하면 경주를 10회 진행한다.", () => { - const cars = [new Car(CARNAMES.G70), new Car(CARNAMES.GV80)]; + const cars = [new Car(CAR_NAMES.G70), new Car(CAR_NAMES.GV80)]; const race = new Race(cars, ROUNDS.TEN); expect(race.rounds).toBe(ROUNDS.TEN); diff --git a/src/utils/constants.js b/src/utils/constants.js index 3364dbfb1..29b9e7194 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -5,19 +5,19 @@ export const ERROR_MESSAGES = { INPUT_ASYNC_ERROR: "[ERROR] 인풋 비동기화 처리중 에러 발생.", }; -export const CARNAMES = { +export const CAR_NAMES = { NAME: "NAME", NAME_SPACE: "N AME", N: "N", NEXT: "NEXT", - NEXTSTEP: "NEXTSTEP", - MYCAR: "MYCAR", + NEXT_STEP: "NEXT_STEP", + M_CAR: "M_CAR", G70: "G70", GV80: "GV80", }; export const ROUNDS = { - ONE : 1, - FIVE : 5, - TEN : 10, -} \ No newline at end of file + ONE: 1, + FIVE: 5, + TEN: 10, +}; From b62b20b726e7cd7d1d0aaac8a080e97ca54246ec Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 21:28:29 +0900 Subject: [PATCH 13/14] =?UTF-8?q?Feat:=20=EC=9E=90=EB=8F=99=EC=B0=A8=20?= =?UTF-8?q?=EC=A0=84=EC=A7=84=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/Race.test.js | 32 ++++++-------------------------- docs/REQUIREMENTS.md | 2 +- src/domain/Car.js | 10 +++++++++- src/domain/Race.js | 19 ++++++++++++++++++- src/main.js | 9 ++++++--- src/utils/constants.js | 2 ++ src/view/Output.js | 5 +++++ 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/__tests__/Race.test.js b/__tests__/Race.test.js index da3e39f93..6b949ab84 100644 --- a/__tests__/Race.test.js +++ b/__tests__/Race.test.js @@ -21,35 +21,15 @@ describe("기본 경주 테스트", () => { }); }); - describe("자동차는 1회당 한 칸씩 전진한다.", () => { - const MOVE_DISTANCE = 1; - let result; - - beforeEach(() => { - race.start(); - result = race.result; - }); - - it("1회에는 한 칸 이동한다.", () => { - const firstRound = result.find( - (round) => round.round === ROUNDS.ONE * MOVE_DISTANCE - ); - const allCarsFirstMovedLocation = firstRound.cars.every( - (car) => car.location === ROUNDS.ONE - ); - - expect(allCarsFirstMovedLocation).toBeTruthy(); + describe("경주 결과 테스트", () => { + it("경주가 끝나면 결과를 반환한다.", () => { + expect(race.start()).toEqual(race.result); }); - it("5회에는 다섯 칸 이동한다.", () => { - const finalRound = result.find( - (round) => round.round === ROUNDS.FIVE * MOVE_DISTANCE - ); - const allCarsFinalLocation = finalRound.cars.every( - (car) => car.location === ROUNDS.FIVE * MOVE_DISTANCE - ); + it("우승자 조회시 우승자 목록을 반환한다.", () => { + race.start(); - expect(allCarsFinalLocation).toBeTruthy(); + expect(race.getWinners().length).toBeGreaterThan(0); }); }); }); diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index f98259f20..ce7484ef7 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -4,7 +4,7 @@ - [X] 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. - [X] 자동차 경주 횟수는 사용자에게 입력 받는다. - [X] 자동차 경주는 사용자 입력이 없다면 5회로 고정하여 진행한다. -- [ ] 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- [X] 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. - [X] 자동차는 1회당 1칸씩 전진한다 - [X] 회차를 거듭할 때마다 자동차가 지나간 궤적을 출력한다(실행 예시 참고). - [X] 사용자가 잘못된 입력 값을 작성한 경우 프로그램을 종료한다. diff --git a/src/domain/Car.js b/src/domain/Car.js index 782ce2905..6264c475f 100644 --- a/src/domain/Car.js +++ b/src/domain/Car.js @@ -1,4 +1,4 @@ -import { ERROR_MESSAGES } from '../utils/constants.js'; +import { ERROR_MESSAGES, NUMBERS } from "../utils/constants.js"; class Car { #name; @@ -23,9 +23,17 @@ class Car { this.#location += 1; } + movingCondition() { + return this.getRandomNumber() >= NUMBERS.THRESHOLD; + } + isValidName(name) { return name.length >= 1 && name.length <= 5; } + + getRandomNumber() { + return Math.floor(Math.random() * NUMBERS.MAX_RANGE); + } } export default Car; diff --git a/src/domain/Race.js b/src/domain/Race.js index 79519fc46..a7c573f59 100644 --- a/src/domain/Race.js +++ b/src/domain/Race.js @@ -10,7 +10,11 @@ class Race { } moveCars(cars) { - cars.map((car) => car.moveForward()); + cars.map((car) => { + if (car.movingCondition()) { + car.moveForward(); + } + }); } // 자동차는 1회에 1칸씩 이동 @@ -19,17 +23,30 @@ class Race { this.moveCars(this.cars); this.recordRoundResult(round, this.cars); } + return this.result; } recordRoundResult(round, cars) { this.result.push({ round: round, + cars: cars.map((car) => ({ name: car.getName(), location: car.getLocation(), })), }); } + + getWinners() { + const locations = this.cars.map((car) => car.getLocation()); + const maxLocation = Math.max(...locations); + + const winnersCar = this.cars.filter( + (car) => car.getLocation() === maxLocation + ); + + return winnersCar.map((car) => car.getName()); + } } export default Race; diff --git a/src/main.js b/src/main.js index d3b76723b..44ab68d7c 100644 --- a/src/main.js +++ b/src/main.js @@ -13,9 +13,12 @@ async function main() { const rounds = await input.getRoundCount(); const race = new Race(cars, rounds); - - race.start(); - output.printRaceResult(race.result); + + const raceResult = race.start(); + const raceWinners = race.getWinners(); + + output.printRaceResult(raceResult); + output.printWinners(raceWinners); } catch (error) { output.printErrorMessage(error); } diff --git a/src/utils/constants.js b/src/utils/constants.js index 29b9e7194..21785fa07 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -21,3 +21,5 @@ export const ROUNDS = { FIVE: 5, TEN: 10, }; + +export const NUMBERS = { THRESHOLD: 4, MAX_RANGE: 10 }; diff --git a/src/view/Output.js b/src/view/Output.js index d8cd30d16..92b290ccb 100644 --- a/src/view/Output.js +++ b/src/view/Output.js @@ -12,6 +12,11 @@ class Output { }); } + printWinners(winnerNames) { + const winner = winnerNames.join(", "); + console.log(`${winner}가 최종 우승했습니다.`); + } + printCarLocation(name, location) { console.log(`${name} : ${"-".repeat(location)}`); } From 54cc93a1acb49b5ae7c75d9e778dd19282eca894 Mon Sep 17 00:00:00 2001 From: Terry Date: Wed, 12 Feb 2025 21:31:50 +0900 Subject: [PATCH 14/14] =?UTF-8?q?fix:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/Race.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/domain/Race.js b/src/domain/Race.js index a7c573f59..2731b16b4 100644 --- a/src/domain/Race.js +++ b/src/domain/Race.js @@ -17,7 +17,6 @@ class Race { }); } - // 자동차는 1회에 1칸씩 이동 start() { for (let round = 1; round <= this.rounds; round++) { this.moveCars(this.cars);