-
Notifications
You must be signed in to change notification settings - Fork 172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1단계 - 자동차 경주] 해리(최현웅) 미션 제출합니다. #260
Merged
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
510b652
chore: .gitignore 설정 추가
Yoonkyoungme fad42fb
chore: 커밋 공동 작업자 추가
Yoonkyoungme 4185421
docs: 기능 목록 작성
Yoonkyoungme 221af86
feat: 랜덤 숫자 생성 함수 구현
Yoonkyoungme a0c25b5
feat: 이동 여부를 결정하는 함수 구현
Yoonkyoungme 4eae2d9
feat: 자동차 A가 자동차 B와 위치가 같은지 판단하는 함수 구현
Yoonkyoungme 44a3957
feat: 자동차 A가 자동차 B와 위치보다 앞서가는지 결정하는 함수 구현
Yoonkyoungme da81312
feat: 각 라운드의 결과 만드는 함수 구현
Yoonkyoungme 03f00d6
feat: 우승자 판단하는 함수 구현
Yoonkyoungme 71bbcac
feat: Console 관련 모듈 (입력, 출력) 구현
Yoonkyoungme bac4164
feat: 경주할 자동차 이름을 입력받기
Yoonkyoungme 9b39963
feat: 시도할 횟수는 입력 받기
Yoonkyoungme 0e915c4
feat: 각 라운드 결과 출력
Yoonkyoungme da035c9
feat: 우승자 이름 출력
Yoonkyoungme 12d4390
fix: makeRoundResult 함수에서 move 함수 호출 누락한 부분 수정
Yoonkyoungme 649a7f6
feat: RaceController 구현 및 호출
Yoonkyoungme 69e746f
feat: 자동차 이름 유효성 검사 로직 구현
Yoonkyoungme d17385b
feat: 시도할 횟수 유효성 검사 로직 구현
Yoonkyoungme 50469b2
fix: retrun 되지 않았던 문제 수정
Yoonkyoungme 32b2a98
modify: 메시지 상수화 및 사용
Yoonkyoungme 77e3d58
rename: 파일명 변경
Yoonkyoungme 5928ad6
test: 자동차 도메인 테스트
Yoonkyoungme 81fabbe
refactor: findMaxPosition 메서드를 private으로 설정
Yoonkyoungme 316b9ac
test: 자동차 경주 도메인 테스트
Yoonkyoungme 51a4e6d
test: 자동차 경주 애플리케이션 테스트
Yoonkyoungme bc1f7e3
modify: 매직 넘버, 메시지 상수화 정의 및 사용
Yoonkyoungme e0a5761
modify: 객체 불변성 유지 유틸 함수 구현
Yoonkyoungme 45ec6a9
remove: 사용 안 하는 파일 삭제
Yoonkyoungme 4b03951
test: 랜덤 함수 모킹 방법 변경 & 코드 중복 제거
hwinkr 504c368
chore(constants): 자동차 이름 유효 길이 상수, 경주 시도 횟수 에러 메시지 상수 추가
hwinkr bff98f2
feat(process input): 입력을 처리하는 유틸 함수 구현
hwinkr b8a3999
refactor(use util function): 입력을 처리하는 유틸 함수를 사용하도록 변경
hwinkr 6657b5f
refactor(CarRace): 가장 멀리 간 자동차를 찾을 때 reduce를 사용해서 선언적으로 구현
hwinkr 5453a0a
chore(use constants): 자동차 유효 길이 상수 사용하도록 변경
hwinkr 07f8514
chore(split error message): 에러 메시지 세분화
hwinkr ec6ffdb
chore(change function name): makes -> make 함수 이름 수정
hwinkr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules/ | ||
dist/ | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Co-authored-by: hwinkr <dnddl8280@naver.com> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import App from "../src/App"; | ||
import pickNumberInRange from "../src/utils/pickNumberInRange"; | ||
import Console from "../src/utils/Console"; | ||
import { ERROR_MESSAGES } from "../src/constants/car-race"; | ||
|
||
jest.mock("../src/utils/pickNumberInRange", () => { | ||
return jest.fn(); | ||
}); | ||
|
||
const mockRandoms = (numbers) => { | ||
numbers.forEach((number) => { | ||
pickNumberInRange.mockReturnValueOnce(number); | ||
}); | ||
}; | ||
|
||
const mockQuestions = (inputs) => { | ||
Console.readLineAsync = jest.fn(); | ||
|
||
Console.readLineAsync.mockImplementation(() => { | ||
const input = inputs.shift(); | ||
return Promise.resolve(input); | ||
}); | ||
}; | ||
|
||
const getLogSpy = () => { | ||
const logSpy = jest.spyOn(Console, "print"); | ||
logSpy.mockClear(); | ||
|
||
return logSpy; | ||
}; | ||
|
||
describe("자동차 경주 애플리케이션 테스트", () => { | ||
it("정상적인 전진-정지의 경우 올바르게 출력 되는지 테스트", async () => { | ||
// given | ||
const MOVING_FORWARD = 4; | ||
const STOP = 3; | ||
const randoms = [MOVING_FORWARD, STOP]; | ||
|
||
const inputs = ["harry,bong", "1"]; | ||
const harryOutput = ["harry : -"]; | ||
const bongOutput = ["bong :"]; | ||
|
||
const logSpy = getLogSpy(); | ||
// when | ||
mockQuestions(inputs); | ||
mockRandoms(randoms); | ||
|
||
const app = new App(); | ||
await app.run(); | ||
|
||
// then | ||
[...harryOutput, ...bongOutput].forEach((output) => { | ||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output)); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("자동차 경주 입력에 대한 예외 테스트", () => { | ||
const TEST_CASES = [ | ||
{ | ||
inputs: ["harry,harry", "harry,bong", "5"], | ||
expectedErrorMessage: ERROR_MESSAGES.carNameUniqueness, | ||
}, | ||
{ | ||
inputs: ["harrrrrry,harry", "harry,bong", "5"], | ||
expectedErrorMessage: ERROR_MESSAGES.carNameLength, | ||
}, | ||
{ | ||
inputs: ["harry,bong", "a", "10"], | ||
expectedErrorMessage: ERROR_MESSAGES.invalidNumberType, | ||
}, | ||
{ | ||
inputs: ["harry,bong", "-1", "10"], | ||
expectedErrorMessage: ERROR_MESSAGES.negativeTryCount, | ||
}, | ||
]; | ||
|
||
let logSpy; | ||
|
||
beforeEach(() => { | ||
logSpy = getLogSpy(); | ||
}); | ||
|
||
test.each(TEST_CASES)( | ||
"각 예외 상황에 맞는 에러 메시지를 콘솔에 출력하는지 테스트", | ||
async ({ inputs, expectedErrorMessage }) => { | ||
console.log(inputs, expectedErrorMessage); | ||
mockQuestions(inputs); | ||
|
||
const app = new App(); | ||
await app.run(); | ||
|
||
expect(logSpy).toHaveBeenCalledWith( | ||
expect.stringContaining(expectedErrorMessage) | ||
); | ||
} | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import CarRace from '../src/domains/CarRace'; | ||
import pickNumberInRange from '../src/utils/pickNumberInRange'; | ||
|
||
jest.mock('../src/utils/pickNumberInRange', () => { | ||
return jest.fn(); | ||
}); | ||
|
||
const mockRandoms = (numbers) => { | ||
numbers.reduce((acc, number) => { | ||
return acc.mockReturnValueOnce(number); | ||
}, pickNumberInRange); | ||
}; | ||
|
||
describe('자동차 경주 도메인 테스트', () => { | ||
let carRace; | ||
|
||
beforeEach(() => { | ||
carRace = new CarRace('harry,bong,pobi'); | ||
}); | ||
|
||
it('각 라운드의 결과를 올바르게 표시한다.', () => { | ||
// given | ||
mockRandoms([1, 4, 9]); | ||
const expectedResult = { | ||
harry: 0, | ||
bong: 1, | ||
pobi: 1, | ||
}; | ||
|
||
// when | ||
const roundResult = carRace.makesRoundResult(); | ||
|
||
// then | ||
expect(roundResult).toEqual(expectedResult); | ||
}); | ||
|
||
it('우승자를 올바르게 판단한다.', () => { | ||
// given | ||
mockRandoms([1, 4, 9, 7, 1, 3, 2, 8, 5]); | ||
const TRY_COUNT = 3; | ||
const expectedWinner = ['bong', 'pobi']; | ||
|
||
// when | ||
Array.from({ length: TRY_COUNT }, () => { | ||
carRace.makesRoundResult(); | ||
}); | ||
const winners = carRace.judgeWinners(); | ||
|
||
// then | ||
expect(winners).toEqual(expectedWinner); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import Car from '../src/domains/Car'; | ||
import pickNumberInRange from '../src/utils/pickNumberInRange'; | ||
|
||
jest.mock('../src/utils/pickNumberInRange', () => { | ||
return jest.fn(); | ||
}); | ||
|
||
const mockRandoms = (numbers) => { | ||
numbers.reduce((acc, number) => { | ||
return acc.mockReturnValueOnce(number); | ||
}, pickNumberInRange); | ||
}; | ||
|
||
describe('자동차 도메인 테스트', () => { | ||
let harryCar; | ||
|
||
beforeEach(() => { | ||
harryCar = new Car('harry'); | ||
}); | ||
|
||
it('숫자가 4이상인 경우에 자동차는 전진해야한다.', () => { | ||
// given | ||
mockRandoms([4, 6]); | ||
const expectedPosition = 2; | ||
|
||
// when | ||
harryCar.move(); | ||
harryCar.move(); | ||
|
||
// then | ||
expect(harryCar.getPosition()).toBe(expectedPosition); | ||
}); | ||
|
||
it('숫자가 4미만인 경우 자동차는 전진할 수 없다.', () => { | ||
// given | ||
mockRandoms([1, 3]); | ||
const expectedPosition = 0; | ||
|
||
// when | ||
harryCar.move(); | ||
harryCar.move(); | ||
|
||
// then | ||
expect(harryCar.getPosition()).toBe(expectedPosition); | ||
}); | ||
|
||
it('두 자동차의 위치가 같은 경우 true를 반환해야 한다.', () => { | ||
// given | ||
const bongCar = new Car('bong'); | ||
mockRandoms([4, 9]); | ||
|
||
// when | ||
harryCar.move(); | ||
bongCar.move(); | ||
|
||
// then | ||
const isSame = harryCar.isSamePosition(bongCar); | ||
expect(isSame).toBe(true); | ||
}); | ||
|
||
it('두 자동차의 위치가 다른 경우 false 반환해야 한다.', () => { | ||
// given | ||
const bongCar = new Car('bong'); | ||
mockRandoms([3, 9]); | ||
|
||
// when | ||
harryCar.move(); | ||
bongCar.move(); | ||
|
||
// then | ||
const isSame = harryCar.isSamePosition(bongCar); | ||
expect(isSame).toBe(false); | ||
}); | ||
|
||
it('자동차 도메인은 다른 자동차 도메인과 위치 비교를 할 수 있어야 한다.', () => { | ||
// given | ||
const bongCar = new Car('bong'); | ||
mockRandoms([1, 7]); | ||
|
||
// when | ||
harryCar.move(); | ||
bongCar.move(); | ||
|
||
// then | ||
const isAheadOf = harryCar.isAheadOf(bongCar); | ||
expect(isAheadOf).toBe(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
## 기능 목록 | ||
|
||
### 입력 | ||
|
||
- [x] 경주할 자동차 이름을 입력받기 | ||
- [x] 유효성 검사 1: 중복 | ||
- [x] 유효성 검사 2: 자동차 이름 5자 이하 | ||
- [x] 시도할 횟수는 입력받기 | ||
- [x] 유효성 검사 1: 정수 | ||
|
||
### 기능 | ||
|
||
#### Car | ||
|
||
- [x] 이동 여부를 결정하는 함수 구현 | ||
- [x] 자동차 A가 자동차 B와 위치가 같은지 판단하는 함수 구현 | ||
- [x] 자동차 A가 자동차 B와 위치보다 앞서가는지 결정하는 함수 구현 | ||
|
||
#### Car Race | ||
|
||
- [x] 각 라운드의 결과 만드는 함수 구현 | ||
- [x] 우승자 판단하는 함수 구현 | ||
|
||
#### 기타 | ||
|
||
- [x] 랜덤 숫자 생성 함수 구현 | ||
|
||
### 출력 | ||
|
||
- [x] 각 라운드 결과 출력 | ||
- [x] 우승자 이름 출력 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import RaceController from './controller/RaceController'; | ||
|
||
class App { | ||
#raceController; | ||
|
||
constructor() { | ||
this.#raceController = new RaceController(); | ||
} | ||
|
||
async run() { | ||
await this.#raceController.run(); | ||
} | ||
} | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export const INPUT_QUERY = { | ||
carNames: | ||
"경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).\n", | ||
tryCount: "시도할 횟수는 몇 회인가요?\n", | ||
}; | ||
|
||
export const MESSAGES = { | ||
result: "실행 결과", | ||
winner: "최종 우승자 : ", | ||
positionMark: "-", | ||
}; | ||
|
||
export const ERROR_MESSAGES = { | ||
prefix: "[ERROR]", | ||
carNameLength: "경주할 자동차 이름은 1자 이상 5자 이하만 가능합니다.", | ||
carNameUniqueness: "자동차 이름은 중복될 수 없습니다.", | ||
negativeTryCount: "시도할 횟수는 1이상의 숫자만 가능합니다.", | ||
invalidNumberRange: "최솟값은 최대값보다 작거나 같아야 합니다.", | ||
invalidNumberType: "숫자만 입력 가능합니다.", | ||
}; | ||
|
||
export const RULES = { | ||
minRandomNumber: 1, | ||
maxRandomNumber: 9, | ||
minCarNameLength: 1, | ||
maxCarNameLength: 5, | ||
moveStandard: 4, | ||
initialPosition: 0, | ||
movingUnit: 1, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import CarRace from "../domains/CarRace.js"; | ||
|
||
import InputView from "../views/InputView.js"; | ||
import OutputView from "../views/OutputView.js"; | ||
|
||
import carNamesValidator from "../validators/carNamesValidator"; | ||
import tryCountValidator from "../validators/tryCountValidator"; | ||
import { MESSAGES } from "../constants/car-race.js"; | ||
import processInputWithRetry from "../utils/processInputWithRetry.js"; | ||
|
||
class RaceController { | ||
#carRace; | ||
|
||
constructor() { | ||
this.#carRace = null; | ||
} | ||
|
||
async #processCarNames() { | ||
const carNames = processInputWithRetry( | ||
InputView.readCarNames.bind(InputView), | ||
carNamesValidator.validate.bind(carNamesValidator), | ||
OutputView.printMessage.bind(OutputView) | ||
); | ||
|
||
return carNames; | ||
} | ||
|
||
async #processTryCount() { | ||
const carNames = processInputWithRetry( | ||
InputView.readTryCount.bind(InputView), | ||
tryCountValidator.validate.bind(tryCountValidator), | ||
OutputView.printMessage.bind(OutputView) | ||
); | ||
|
||
return carNames; | ||
} | ||
|
||
async #initCarRace() { | ||
const carNames = await this.#processCarNames(); | ||
this.#carRace = new CarRace(carNames); | ||
|
||
const tryCount = await this.#processTryCount(); | ||
return tryCount; | ||
} | ||
|
||
async #playCarRace(tryCount) { | ||
OutputView.printMessage(MESSAGES.result); | ||
|
||
Array.from({ length: tryCount }, () => { | ||
const roundResult = this.#carRace.makesRoundResult(); | ||
OutputView.printRoundResult(roundResult); | ||
OutputView.printBlankLine(); | ||
}); | ||
} | ||
|
||
#announceWinners() { | ||
const winners = this.#carRace.judgeWinners(); | ||
OutputView.printWinners(winners); | ||
} | ||
|
||
async run() { | ||
const tryCount = await this.#initCarRace(); | ||
await this.#playCarRace(tryCount); | ||
this.#announceWinners(); | ||
} | ||
} | ||
|
||
export default RaceController; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit) (nit 은 꼭 반영하지 않아도 되고, 참고차 말씀드리는내용입니다)
이렇게 단순 반복에는 for 을 사용해도 좋을거 같아요
Array.from을 사용하게 되면, 불필요한 배열 생성이 되기 때문입니다.