Skip to content
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단계 - 자동차 경주] 지니(손진영) 미션 제출합니다. #275

Merged
merged 46 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ab4fe61
docs (REQUIREMENTS.md): 기능 구현 목록 및 프로그래밍 요구사항 작성
chosim-dvlpr Feb 14, 2024
f8d43c4
chore (ESLint & Prettier) : eslint & prettier 설정
chosim-dvlpr Feb 14, 2024
898bb38
feat(phase1): 자동차 입력 기능 구현
jinyoung234 Feb 14, 2024
8ec0d80
test(CommonValidator): 공통 유효성 검사 관련 테스트
jinyoung234 Feb 14, 2024
9ce6d4e
test(CarNameValidator): 자동차 이름 유효성 검사 관련 테스트
jinyoung234 Feb 14, 2024
b2ab97d
test(AppError): 공통 에러 객체 관련 테스트
jinyoung234 Feb 14, 2024
389320b
test(ErrorHandler): 재입력 테스트
jinyoung234 Feb 14, 2024
b14db1c
docs(REQUIREMENTS.md): phase 1 기능 구현 목록 반영
jinyoung234 Feb 14, 2024
27b4f4f
feat(phase 2) : 자동차 시도 횟수 입력 기능 구현
chosim-dvlpr Feb 14, 2024
6ff6a4c
test(TryCountValidator) : 자동차 게임 시도 횟수 유효성 검사 테스트
chosim-dvlpr Feb 14, 2024
8960e06
docs(REQUIREMENTS.md) : phase 2 기능 구현 목록 반영
chosim-dvlpr Feb 14, 2024
51f756f
feat(phase3): 자동차 이동 기능 구현
jinyoung234 Feb 15, 2024
67063f0
test(CarEngine): 자동차 이동 테스트
jinyoung234 Feb 15, 2024
410ac93
test(Cars): 랜덤 값에 따른 자동차 이동 테스트
jinyoung234 Feb 15, 2024
9c45c53
refactor(RandomMoveCountMaker): randomMoveCounts를 RacingGame 외부에서 주입 …
jinyoung234 Feb 15, 2024
21fcd5f
test(RacingGame): 시도 횟수에 따른 자동차 경주 결과 테스트
jinyoung234 Feb 15, 2024
2355080
test(RandomMoveCountMaker): 시도 횟수와 총 자동차 대수가 주어졌을 때 알맞은 크기의 랜덤 이동 횟수 …
jinyoung234 Feb 15, 2024
31fd77c
docs(REQUIREMENTS.md): phase 3 기능 구현 목록 반영
jinyoung234 Feb 15, 2024
57b59e1
feat(phase 4): 우승자 확인 기능 구현
chosim-dvlpr Feb 15, 2024
bff467f
test(RacingWinnerRecorder): 레이싱 게임 우승자 생성 테스트
chosim-dvlpr Feb 15, 2024
6bff77f
docs(REQUIREMENTS.md): phase 4 기능 구현 목록 반영
chosim-dvlpr Feb 15, 2024
4cc1f97
feat(phase5): 자동차 경주 결과 출력 기능 구현
jinyoung234 Feb 15, 2024
2c20203
test(FORMAT_MESSAGE): 자동차 경주 문자열 변환 테스트
jinyoung234 Feb 15, 2024
bb02fbd
docs(REQUIREMENTS.md): 자동차 경주 결과 출력 기능 구현 목록 반영
jinyoung234 Feb 15, 2024
2dca485
fix(CarNameValidator): 중복된 자동차 이름이 유효성 검사에 걸리지 않는 문제 해결
jinyoung234 Feb 15, 2024
e433863
test(CarNameValidator): 중복된 자동차 이름에 대한 유효성 검증 테스트 추가
jinyoung234 Feb 15, 2024
482d0fc
refactor: 하드 코딩 된 값 상수로 변경
jinyoung234 Feb 15, 2024
23f786c
chore(ESLint & Prettier): ESLint 및 Prettier 설정 변경
jinyoung234 Feb 15, 2024
7529bc4
fix: merge 충돌 해결
chosim-dvlpr Feb 15, 2024
623fceb
Merge branch 'step1' of https://github.com/jinyoung234/javascript-rac…
chosim-dvlpr Feb 15, 2024
ce3c376
refactor(ErrorHandler): RetryHandler로 네이밍 변경
jinyoung234 Feb 16, 2024
082d4ba
refactor: 모듈 파일 네이밍 변경
jinyoung234 Feb 16, 2024
001a7d6
refactor(Cars): Cars에서 Car로 변경
jinyoung234 Feb 16, 2024
af88ae3
test(Car): 자동차 이동 기능 테스트 추가
jinyoung234 Feb 16, 2024
318e70a
refactor(test): 테스트 코드 들의 import path 변경
jinyoung234 Feb 16, 2024
e4528f4
refactor(domain): 디렉터리 네이밍 변경
jinyoung234 Feb 16, 2024
264b647
refactor(random): shuffle 내 내부 로직 변경
jinyoung234 Feb 16, 2024
a7debd2
refactor(RacingGame): index 네이밍 변경
jinyoung234 Feb 16, 2024
a6b77bd
refactor(CarNameValidator): MIN_CAR_LENGTH 네이밍 변경
jinyoung234 Feb 16, 2024
835405d
refactor(Car): constant.js에 있는 상수 값을 class 내부로 이동
jinyoung234 Feb 16, 2024
b02d738
refactor(RacingGame): 메서드 순서 변경
jinyoung234 Feb 16, 2024
ca2e771
refactor(RacingGame): randomMoveCounts의 row index와 column index 네이밍 변경
jinyoung234 Feb 16, 2024
5c1d2b9
refactor(RacingGame): updateRacingResult 메서드 내 결과 값 반환 형태 변경
jinyoung234 Feb 16, 2024
c1efc23
refactor(RacingWinnerRecorder): 최대 이동 횟수의 자동차 이름을 추출하기 위한 함수 네이밍 변경
jinyoung234 Feb 16, 2024
95585b1
refactor(messages): racingResultToString 내 상수 및 함수 분리
jinyoung234 Feb 16, 2024
2f18fd5
refactor(Random): pickUniqueNumbersInRange 내 내부 로직 변경
jinyoung234 Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
{
"rules": {},
"env": {
"es6": true,
"node": true
"es2021": true,
"node": true,
"jest": true
},
"extends": ["airbnb-base", "prettier", "plugin:jsdoc/recommended", "plugin:jest/recommended"],
"overrides": [
{
"files": ["*.test.js", "console.js"],
"rules": {
"max-lines-per-function": ["off"]
}
}
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
]
}
"rules": {
"no-use-before-define": ["off"],
"import/prefer-default-export": ["off"],
"import/extensions": ["off"],
"class-methods-use-this" : ["off"],
"max-depth": ["error", 2],
"max-lines-per-function": ["error", { "max": 10, "ignoreComments": true }],
"jsdoc/require-returns" : ["off"],
"jsdoc/require-jsdoc" : ["off"],
"no-param-reassign" : ["off"]
}
}
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 120,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "auto"
}

11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
// Set the default
"editor.formatOnSave": false,
// Enable per-language
"[javascript]": {
"editor.formatOnSave": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
51 changes: 51 additions & 0 deletions docs/REQUIREMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 🎯 기능 구현 목록
## 공통
- [x] 공백이 포함되었을 경우, '입력한 값에 공백이 존재합니다.'와 함께 재입력 해야 한다.
- [x] 입력 값이 빈 값일 경우 '아무것도 입력하지 않았으므로 다시 입력해주세요.'와 함께 재입력 해야 한다.

## 예외 처리
- [x] 사용자가 잘못된 입력 값을 작성한 경우 에러 메시지를 보여주고, 다시 입력할 수 있게 한다.
- [x] 에러가 발생 시 prefix로 `[ERROR]`를 추가한다.

## 자동차 이름 입력 기능 (phase 1)
- [x] 자동차 이름은 쉼표(,)를 기준으로 구분한다.
- ,로 구분하지 않았을 경우 '자동차 이름은 ,로만 구분 가능합니다.' 라는 에러 메시지와 함께 재입력 해야 한다.
- [x] 자동차 이름은 1 ~ 5자만 가능하다.
- 자동차 이름이 범위에서 벗어날 경우 '자동차 이름은 1 ~ 5자의 범위만 가능합니다.'라는 에러메시지와 함께 재입력 해야 한다.

## 자동차 시도 횟수 입력 기능 (phase 2)
- [x] 사용자는 정수만 입력 가능하다.
- 정수를 입력하지 않았을 경우 '정수만 입력 가능합니다.' 라는 에러 메시지와 함께 재입력 해야 한다.
- [x] 사용자는 1 ~ 10의 범위만 입력 가능하다.
- 1 ~ 10의 범위가 아닐 경우 '시도 횟수는 1 ~ 10만 입력 가능합니다.'라는 에러 메시지와 함께 재입력 해야 한다.

## 자동차 이동 기능 (phase 3)
- [x] 자동차에 이름을 부여할 수 있다.
- [x] 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.

## 우승자 확인 기능 (phase 4)
- [x] 우승자는 한 명 이상일 수 있다.

## 자동차 경주 결과 출력 기능 (phase 5)
- [x] 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- [x] 최종 우승자를 출력한다.

# ✅ 프로그래밍 요구사항
- 프리코스 때 사용한 @woowacourse/mission-utils 라이브러리 사용을 금지한다.
- 입출력(readline)과 관련된 테스트는 작성하지 않는다.
- 입력 또는 출력이 잘 되었는지를 확인하는 테스트
- 입력 후 출력 값을 기반으로 결괏값이 잘 도출되었는지를 확인하는 테스트
- 코드 스타일 가이드에 따라 컨벤션을 준수하며 개발한다.
- 변수 선언시 var를 사용하지 않는다. let, const를 사용한다.
- 전역 변수를 만들지 않는다.
- 축약하지 않는다.
- 하드 코딩된 값 대신에 의미 있는 상수를 활용한다.
- 동등 연산자는 === 로만 사용한다.
- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 하도록 만든다.
- 함수(또는 메서드)의 들여쓰기 depth는 2단계까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 depth는 2단계 이다.
- 힌트) 함수(또는 메서드) 분리는 들여쓰기 depth를 줄이는 좋은 방법이다.
- 도메인 로직과 UI 로직을 분리한다.
- 모든 도메인 로직에 단위 테스트를 구현한다. (UI 로직은 제외)

9 changes: 9 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import RacingGameController from './controller/RacingGameController.js';

const App = Object.freeze({
async start() {
await RacingGameController.run();
},
});
Comment on lines +3 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppObject.freeze()를 사용하신 특별한 이유가 있으실까요?

한 군데(index.js)서만 사용되고, 딱히 외부에서 변경될 가능성이 없는 객체라면 굳이 모든 것을 Object.freeze()해줄 필요는 없을 것 같습니다. App 객체 안에 중요한 정보들이 있는 것도 아닌 것 같아서요

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 곳들(ex. RacingGameController)도 마찬가지입니다

Copy link
Author

@jinyoung234 jinyoung234 Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppObject.freeze()를 사용하신 특별한 이유가 있으실까요?

한 군데(index.js)서만 사용되고, 딱히 외부에서 변경될 가능성이 없는 객체라면 굳이 모든 것을 Object.freeze()해줄 필요는 없을 것 같습니다. App 객체 안에 중요한 정보들이 있는 것도 아닌 것 같아서요

다른 곳에서 수정할 수 있는 여지를 막으려는 목적으로 read-only 성격의 Object.freeze()를 사용하게 되었습니다.

프로덕션(cli 기반 애플리케이션) 코드라고 가정 했을 때 App 객체 내 start 메서드가 변경 된다면 앱 전체가 멈춰버릴 수 있다고 생각했던거 같습니다. 🥲

지그가 언급해주신 중요한 정보라고 한다면 비즈니스 로직들이 있는 model layer 라고 생각하면 되는지 궁금합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 '중요한 정보'라고 언급한 건 특별한 의미는 아니었구요, 외부에서 의도적으로 변경할 만한 프로퍼티가 아니라고 생각했습니다.

비즈니스 로직들이 있는 model layer 라고 생각하면 되는지 궁금합니다!

말씀주신 대로 Car의 moveCount 등이 외부에서 변경될 가능성이 있는 요소들이겠죠?

Copy link
Author

@jinyoung234 jinyoung234 Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아! 좋은 인사이트 감사합니다 !! 👍


export default App;
26 changes: 26 additions & 0 deletions src/constants/messages/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SYMBOLS } from '../symbols.js';

export const INPUT_MESSAGE = Object.freeze({
racingCar: '경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)\n',
tryCount: '시도할 횟수는 몇 회인가요?\n',
});

export const OUTPUT_MESSAGE = Object.freeze({
executeResult: '\n실행 결과',
movementIndicator: '-',
});

export const FORMAT_MESSAGE = Object.freeze({
racingResultToString(racingResult) {
const extractCarNameToString = ({ carName, moveCount }) =>
`${carName} : ${OUTPUT_MESSAGE.movementIndicator.repeat(moveCount)}`;

const generatePartialRacingResultToString = (racingTurn) => racingTurn.map(extractCarNameToString).join('\n');

return racingResult.map(generatePartialRacingResultToString).join('\n\n');
},

racingWinnersToString(racingWinners) {
return `\n최종 우승자: ${racingWinners.join(`${SYMBOLS.comma} `)}`;
},
});
51 changes: 51 additions & 0 deletions src/constants/messages/messages.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FORMAT_MESSAGE } from './messages';

describe('자동차 경주 문자열 변환 테스트', () => {
describe('racingResultToString 메서드를 통한 자동차 경주 결과 문자열 변환 테스트', () => {
test.each([
{
description:
"racingResult가 [[{ carName: 'CarA', moveCount: 1 }, { carName: 'CarB', moveCount: 1 }]] 일 때, \n 문자열 변환 결과는 'CarA : - CarB : -'이다.",
racingResult: [
[
{ carName: 'CarA', moveCount: 1 },
{ carName: 'CarB', moveCount: 1 },
],
],
expected: 'CarA : -\nCarB : -',
},
{
description:
"racingResult가 [[{ carName: 'CarA', moveCount: 2 }, { carName: 'CarB', moveCount: 0 }], [{ carName: 'CarA', moveCount: 3 },{ carName: 'CarB', moveCount: 1 }]] 일 때, \n 문자열 변환 결과는 'CarA : -- CarB : CarA : --- CarB : -'이다.",
racingResult: [
[
{ carName: 'CarA', moveCount: 2 },
{ carName: 'CarB', moveCount: 0 },
],
[
{ carName: 'CarA', moveCount: 3 },
{ carName: 'CarB', moveCount: 1 },
],
],
expected: 'CarA : --\nCarB : \n\nCarA : ---\nCarB : -',
},
])('$description', ({ racingResult, expected }) => {
expect(FORMAT_MESSAGE.racingResultToString(racingResult)).toMatch(expected);
});
});

describe('racingWinnersToString 메서드를 통한 우승자 문자열 변환 테스트', () => {
test.each([
{
racingWinners: ['CarA'],
expected: '\n최종 우승자: CarA',
},
{
racingWinners: ['CarA', 'CarB'],
expected: '\n최종 우승자: CarA, CarB',
},
])('racingWinners가 $racingWinners일 때, 문자열 변환 결과는 "$expected다."', ({ racingWinners, expected }) => {
expect(FORMAT_MESSAGE.racingWinnersToString(racingWinners)).toMatch(expected);
});
});
});
10 changes: 10 additions & 0 deletions src/constants/symbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* 현재 single export로 인해 import/prefer-default-export 관련 airbnb 컨벤션을 위배하고 있지만
* constants 모듈이 default export 셩격에 맞지 않다는 점과 random과 관련된 상수가 추가될 수 있다고 판단되어
* export 방식을 유지하기로 결정
*/
export const SYMBOLS = Object.freeze({
emptyString: '',
space: ' ',
comma: ',',
});
35 changes: 35 additions & 0 deletions src/controller/RacingGameController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import InputView from '../views/InputView.js';
import OutputView from '../views/OutputView.js';
import RacingGame from '../models/RacingGame/RacingGame.js';
import RandomMoveCountMaker from '../models/RandomMoveCountMaker/RandomMoveCountMaker.js';
import RacingWinnerRecorder from '../models/RacingWinnerRecorder/RacingWinnerRecorder.js';
import RetryHandler from '../errors/RetryHandler/RetryHandler.js';

const RacingGameController = Object.freeze({
async run() {
const { racingCarNames, tryCount } = await processUserInput();
processRacingGame({ racingCarNames, tryCount });
},
});

export default RacingGameController;

async function processUserInput() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processUserInput 이 함수는 딱히 이 파일 외부에서 쓰이지도 않고, 공용 함수나 유틸성 함수도 아니라 RacingGameController 안에 있어도 상관없을 것 같은데 따로 빼신 이유가 있나요~?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

객체 내부에 존재하게 되면 public 하게 외부 모듈에서 접근이 가능하게 되기 때문입니다.

controller 내부 로직을 굳이 외부에 알리지 않고 캡슐화 하고 싶은 목적에 외부에 작성하게 되었습니다.

Copy link

@zigsong zigsong Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

controller 내부 로직을 굳이 외부에 알리지 않고 캡슐화 하고 싶은 목적에 외부에 작성하게 되었습니다.

  • controller 내부로직을
  • 외부에 작성했다

라는 것은, 이것이 controller 내부 로직이라고 생각하신 건가요? 외부 로직이라고 생각하신 건가요?

외부에 작성했다면 더 이상 controller 내부 로직이 아닙니다.

그런 의도였다면 processUserInput()은 util 등 별도의 파일에 있는 것이 나을 것 같습니다

Copy link
Author

@jinyoung234 jinyoung234 Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다시 생각해보니 지그 의견 처럼 내부 동작에 관련된 로직을 객체 외부로 두게 될 경우 그 모듈의 내부 로직이 아닌 그 모듈과 관련된 함수 정도로 인식할 수도 있겠네요..

제가 생각이 짧았던거 같습니다 👏

const racingCarNames = await RetryHandler.errorWithLogging(() => InputView.readRacingCarNames());
const tryCount = await RetryHandler.errorWithLogging(() => InputView.readTryCount());

return { racingCarNames, tryCount };
}

function processRacingGame({ racingCarNames, tryCount }) {
const racingGame = new RacingGame({ racingCarNames, tryCount });

const randomMoveCounts = RandomMoveCountMaker.execute(tryCount, racingCarNames.length);
const racingResult = racingGame.startRace(randomMoveCounts);

const finalRacingResult = racingResult.at(-1);
const racingWinners = RacingWinnerRecorder.createRacingWinners(finalRacingResult);

OutputView.printRacingResult(racingResult);
OutputView.printRacingWinners(racingWinners);
}
22 changes: 22 additions & 0 deletions src/errors/AppError/AppError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @module AppError
* '일관된 에러 메시지 제공'의 역할을 수행
*/
class AppError extends Error {
/**
* @static
* @public
* @constant
* @type {string}
*/
static PREFIX = '[ERROR]';

/**
* @param {string} message - 에러 메시지
*/
constructor(message) {
super(`\n${AppError.PREFIX} ${message}\n`);
}
}

export default AppError;
11 changes: 11 additions & 0 deletions src/errors/AppError/AppError.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import AppError from './AppError';

describe('AppError 테스트', () => {
const throwAppError = () => {
throw new AppError('test');
};
test(`발생 된 에러 메시지는 ${AppError.PREFIX}으로 시작 한다.`, () => {
// given - when - then
expect(throwAppError).toThrow(AppError.PREFIX);
});
});
24 changes: 24 additions & 0 deletions src/errors/RetryHandler/RetryHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Console from '../../utils/console.js';

/**
* @module RetryHandler
* 시스템 작업 중 발생하는 예외 처리 및 재 실행을 위한 모듈
*/
const RetryHandler = {
/**
* 제공된 비동기 함수를 실행 후 오류가 발생하지 않을 때 까지 오류 메시지를 출력하고 함수를 재 실행
* @template T
* @param {Function} executeFunction - 실행할 비동기 함수
* @returns {Promise<T>} 비동기 함수가 성공적으로 완료되면 그 결과를 반환
*/
async errorWithLogging(executeFunction) {
try {
return await executeFunction();
} catch (error) {
Console.print(error.message);
return this.errorWithLogging(executeFunction);
}
},
};

export default RetryHandler;
37 changes: 37 additions & 0 deletions src/errors/RetryHandler/RetryHandler.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Console from '../../utils/console.js';
import RetryHandler from './RetryHandler.js';

jest.mock('../../utils/console.js', () => ({
print: jest.fn(),
}));

describe('입력 관련 예외 처리 테스트', () => {
let executeTest;

beforeEach(() => {
jest.clearAllMocks();
// given
executeTest = jest.fn().mockRejectedValueOnce(new Error('Test Error')).mockResolvedValueOnce('Success');
});

test('함수가 두 번 호출된다.', async () => {
// when
await RetryHandler.errorWithLogging(executeTest);
// then
expect(executeTest).toHaveBeenCalledTimes(2);
});

test('첫 번째 호출은 실패 후 에러 로깅이 발생한다.', async () => {
// when
await RetryHandler.errorWithLogging(executeTest);
// then
expect(Console.print).toHaveBeenCalledWith('Test Error');
});

test('두 번째 호출은 성공적인 결과를 반환한다.', async () => {
// when
const result = await RetryHandler.errorWithLogging(executeTest);
// then
expect(result).toMatch('Success');
});
});
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import App from './App.js';

App.start();
32 changes: 32 additions & 0 deletions src/models/Car/Car.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Car {
static #MIN_MOVABLE_VALUE = 4;

static #CAR_MOVE_COUNT = 1;

#carDetails;

constructor(carName) {
this.#carDetails = {
carName,
moveCount: 0,
};
}

move(randomMoveCount) {
this.#updateMoveCount(randomMoveCount);

return { ...this.#carDetails };
}

#updateMoveCount(randomMoveCount) {
if (this.#isMovable(randomMoveCount)) {
this.#carDetails.moveCount += Car.#CAR_MOVE_COUNT;
}
}

#isMovable(randomMoveCount) {
return randomMoveCount >= Car.#MIN_MOVABLE_VALUE;
}
}

export default Car;
Loading