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

[2단계 - 자동차 경주 리팩터링] - 윤생(이윤성) 미션 제출합니다. #204

Merged
merged 61 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
0864c02
chore: npm 모듈 설치
2yunseong Feb 7, 2023
e4448fb
docs: 요구사항 문서 작성
2yunseong Feb 7, 2023
80e324d
docs: 프리코스 피드백 및 페어프로그래밍 규칙 작성
2yunseong Feb 7, 2023
3ee13de
chore: 불필요한 npm 모듈 삭제
2yunseong Feb 8, 2023
97cf66d
feat: 입출력 담당 모듈 구현
2yunseong Feb 8, 2023
d8da869
feat: n대의 자동차를 입력 받는 기능 구현
2yunseong Feb 8, 2023
fbfb1cb
feat: 각 자동차 이름이 1자 이상 5자 이하의 글자인지 판별하는 기능 구현
2yunseong Feb 8, 2023
35663a4
test: 각 자동차 이름이 1자 이상 5자 이하의 글자인지 판별하는 기능 테스트
2yunseong Feb 8, 2023
84db7c1
feat: 자동차 생성 기능 구현
2yunseong Feb 8, 2023
ead5f2b
feat: 시도 횟수를 입력 받는 기능 구현
2yunseong Feb 8, 2023
658fd36
feat: 시도 횟수를 입력이 자연수인지 판별하는 기능 구현
2yunseong Feb 8, 2023
e4806a1
test: 시도 횟수를 입력이 자연수인지 판별하는 기능 테스트
2yunseong Feb 8, 2023
a6df78d
feat: 입력받은 시도 횟수 만큼 모든 자동차의 이동을 시도하는 기능 구현
2yunseong Feb 8, 2023
079ed07
feat: 0부터 9까지 랜덤으로 숫자를 발생시키는 기능 구현
2yunseong Feb 8, 2023
a1457ee
feat: 차가 전진하는 기능 구현
2yunseong Feb 8, 2023
06f1107
feat: 자동차의 현재 이동 상태를 출력하는 기능 구현
2yunseong Feb 8, 2023
f12c5f9
feat: 최종 우승자를 판별하고 출력하는 기능 구현
2yunseong Feb 8, 2023
93d4d82
docs: 기능목록 최신화
2yunseong Feb 8, 2023
6e0c83c
feat: 잘못된 이름 입력이 들어왔을 경우 에러 처리 구현
2yunseong Feb 9, 2023
16a0534
feat: 잘못된 횟수 입력이 들어왔을 경우 에러 처리 구현
2yunseong Feb 9, 2023
90bd66a
feat: 게임 종료 후 cleanup 기능 구현
2yunseong Feb 9, 2023
f0134b8
test: 자동차 이름 유효성 검사 테스트 케이스 추가
2yunseong Feb 9, 2023
e21ed1a
test: 시도 횟수 검사 테스트 케이스 추가
2yunseong Feb 9, 2023
a990424
refactor: 콜백 패턴의 입력 로직을 async/await로 변경
2yunseong Feb 9, 2023
926a75b
refactor: 형 변환 책임 분리
2yunseong Feb 9, 2023
ebf28d5
refactor: 게임 동작/제어에 관한 책임 분리
2yunseong Feb 9, 2023
0f1e56e
refactor: UI 출력을 담당하는 로직을 분리
2yunseong Feb 9, 2023
c083e0d
refactor: 콘솔을 담당하는 책임 분리
2yunseong Feb 9, 2023
c802c7c
style: 비구조화 할당 문법으로 변경
2yunseong Feb 9, 2023
29ed5f6
refactor: 문자열 리터럴 상수화
2yunseong Feb 9, 2023
c0e56f3
refactor: 불필요한 필드 삭제
2yunseong Feb 9, 2023
71e97bf
refactor: 사용하지 않는 import 문 삭제
2yunseong Feb 9, 2023
57a4612
refactor: 자동차 생성 로직 분리
2yunseong Feb 9, 2023
5c72db0
refactor: 메서드에서 필드로 사용하던 변수를 매개변수로 변경
2yunseong Feb 9, 2023
454395d
refactor: 불필요한 import 문 제거
2yunseong Feb 9, 2023
43913b2
test: Car 클래스 테스트 작성
2yunseong Feb 9, 2023
78dd042
test: 테스트를 위한 형변환 로직 추가
2yunseong Feb 9, 2023
c7d7fe3
chore: eslint 설정 변경
2yunseong Feb 9, 2023
f975546
test: 랜덤 기능 mock 함수 생성
2yunseong Feb 9, 2023
3ea8cd9
test: 전진할지 안할지 판단하는 기능 테스트
2yunseong Feb 9, 2023
5b95d00
fix: mocking 이 안되는 이슈 해결
2yunseong Feb 9, 2023
096d43d
test: 승자들을 판단하는 기능 테스트
2yunseong Feb 9, 2023
bfe05fc
chore: lint 설정 변경
2yunseong Feb 9, 2023
1aa7e7a
docs: 요구사항 문서 작성
2yunseong Feb 9, 2023
4af06ce
refactor: 미사용 주석 제거
2yunseong Feb 10, 2023
db293a1
refactor: CommonJS 모듈 import/export ES6 방식으로 변경
2yunseong Feb 10, 2023
98dabaf
refactor: 매직 넘버 상수화
2yunseong Feb 10, 2023
1d268c1
refactor: move 책임 분리
2yunseong Feb 10, 2023
2d741ec
refactor: 도메인 로직에서 UI 로직 분리
2yunseong Feb 11, 2023
abfbbc8
refactor: 디렉토리 구조 변경
2yunseong Feb 11, 2023
373abf2
fix: 디렉토리 구조 변경에 따른 import 문 개선
2yunseong Feb 11, 2023
fc18b5a
fix: import 가 되지않는 오류 해결
2yunseong Feb 11, 2023
5a363d7
refactor: 언어에서 제공하는 getter 를 사용
2yunseong Feb 11, 2023
76d29e4
refactor: mocking 함수 테스트 제거
2yunseong Feb 11, 2023
bd80c5f
test: 차 이름 입력이 유효하지 않으면 에러를 던지는 지 테스트
2yunseong Feb 11, 2023
991d7fe
test: 시도 횟수 입력이 유효하지 않으면 에러를 던지는 지 테스트
2yunseong Feb 11, 2023
8ce3d9f
refactor: 메서드 책임 분리
2yunseong Feb 11, 2023
e58bb91
fix: 서로게이트 쌍 처리를 위한 유효성 검증 로직 수정
2yunseong Feb 12, 2023
7b2745c
test: 서로게이트 쌍 처리를 위한 유효성 검증 로직 테스트
2yunseong Feb 12, 2023
293ac9b
Merge Confilct 해결
2yunseong Feb 12, 2023
6287e24
refactor: 로직이 의미를 나타낼 수 있도록 메서드로 분리
2yunseong Feb 14, 2023
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
4 changes: 2 additions & 2 deletions __tests__/Car.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-lines-per-function */
/* eslint-disable no-undef */
import Car from '../src/Car';
import Car from '../src/domain/Car';

describe('Car Test', () => {
test.each([
Expand All @@ -11,6 +11,6 @@ describe('Car Test', () => {
for (let i = 0; i < go; i++) {
car.move(true);
}
expect(car.getPosition() === position).toBe(expected);
expect(car.position === position).toBe(expected);
});
});
58 changes: 12 additions & 46 deletions __tests__/GameManager.test.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,21 @@
/* eslint-disable max-lines-per-function */
/* eslint-disable no-undef */
// const Car = require('../src/Car');
import GameManager from '../src/GameManager';
import RandomGenerator from '../src/utils/RandomGenerator';

const mockRandom = (number) => {
RandomGenerator.pickRandomNumber = jest.fn();
RandomGenerator.pickRandomNumber.mockReturnValueOnce(number);
};

const mockRandoms = (numbers) => {
RandomGenerator.pickRandomNumber = jest.fn();
numbers.reduce((acc, number) => {
return acc.mockReturnValueOnce(number);
}, RandomGenerator.pickRandomNumber);
};
import GameManager from '../src/domain/GameManager';

describe('GameManager Test', () => {
test('Random mock Test', () => {
mockRandom(10);
expect(RandomGenerator.pickRandomNumber()).toBe(10);
});
test('차 이름 입력이 유효하지 않으면 에러를 던지는 지 테스트', () => {
const gameMananger = new GameManager();
const badNamesInput = 'abcdef,bad guy,nononono';

test.each([
[9, true],
[4, true],
[3, false],
[0, false],
])('isFoward Test Random value : %i', (number, expected) => {
mockRandom(number);
const gameManager = new GameManager();
expect(gameManager.isForward()).toEqual(expected);
expect(() => gameMananger.checkCarNames(badNamesInput)).toThrow();
});

test.each([
[['yun', 'park', 'kim'], [9, 0, 1, 8, 1, 2, 7, 5, 6], ['yun']],
[
['choi', 'ann', 'lee', 'gabi'],
[0, 0, 5, 5, 0, 0, 9, 9],
['lee', 'gabi'],
],
[
['aa', 'bb'],
[0, 0, 5, 5, 0, 0, 9, 9],
['aa', 'bb'],
],
])('judgeWinners Test (%#)', (carNames, moves, winners) => {
const gameManager = new GameManager();
const cars = gameManager.generateCars(carNames);
mockRandoms(moves);
gameManager.moveCars(cars);
expect(gameManager.judgeWinners([...cars])).toEqual(winners);
});
test.each([0, -1, NaN])(
'시도 횟수 입력이 유효하지 않으면 에러를 던지는 지 테스트',
(input) => {
const gameMananger = new GameManager();
expect(() => gameMananger.checkTryCount(input)).toThrow();
}
);
});
20 changes: 14 additions & 6 deletions __tests__/Validation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import Validation from '../src/utils/Validation.js';

describe('Validation Test', () => {
test.each([
[['yunseong', 'gabriel'], false],
[['aa', 'bb', 'cc'], true],
[['aa', 'bb', ''], false],
// [['aa', 'bb', '윤생이😁😁'], true], => 고민할 부분(서로게이트 쌍)
])('이름 유효성 검사(%s: %s)', (names, expected) => {
expect(Validation.isValidCarNames(names)).toBe(expected);
'ys,pobi,crong',
'gabi,hoho',
'jason,poco',
'윤생😁😁😁,𩷶𩷶𩷶𩷶',
])('올바른 차이름 입력 시 true를 반환한다.', (carNames) => {
expect(Validation.isValidCarNames(carNames.split(','))).toBeTruthy();
});

test.each([
'gabriel,yunseong',
'이윤성입니다,윤생',
'우아한 배달이,치킨과피자',
])('잘못된 차이름 입력 시 false를 반환한다.', (carNames) => {
expect(Validation.isValidCarNames(carNames.split(','))).toBeFalsy();
});

test.each([
Expand Down
12 changes: 12 additions & 0 deletions __tests__/calcWordCount.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import calcWordCount from '../src/utils/calcWordCount';

/* eslint-disable no-undef */
describe('calcWordCount', () => {
test.each([
['test', 4],
['yunsang', 7],
['윤생😁', 3],
])('문자열이 들어오면 그 문자열의 글자 수를 반환한다.', (text, expected) => {
expect(calcWordCount(text)).toBe(expected);
});
});
10 changes: 2 additions & 8 deletions src/Car.js → src/domain/Car.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import OutputView from './OutputView.js';

class Car {
#name;
#position = 0;
Expand All @@ -8,11 +6,11 @@ class Car {
this.#name = name;
}

getName() {
get name() {
return this.#name;
}

getPosition() {
get position() {
return this.#position;
}

Expand All @@ -25,10 +23,6 @@ class Car {
this.move();
}
}

print() {
OutputView.printCar(this.#name, this.#position);
}
}

export default Car;
24 changes: 12 additions & 12 deletions src/GameManager.js → src/domain/GameManager.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Console from './utils/Console.js';
import Validation from './utils/Validation.js';
import Console from '../utils/Console.js';
import Validation from '../utils/Validation.js';
import Car from './Car.js';
import RandomGenerator from './utils/RandomGenerator.js';
import OutputView from './OutputView.js';
import InputView from './InputView.js';
import constants from './utils/constants.js';
import RandomGenerator from '../utils/RandomGenerator.js';
import OutputView from '../view/OutputView.js';
import InputView from '../view/InputView.js';
import constants from '../utils/constants.js';

class GameManager {
#cars = [];
Expand All @@ -21,25 +21,24 @@ class GameManager {

printCars(cars) {
cars.forEach((car) => {
car.print();
OutputView.printCar(car.name, car.position);

Choose a reason for hiding this comment

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

car가 직접 스스로에 대한 정보를 print하는 것보다 이렇게 OutputView의 메소드인 printCar가 출력하는 구조가 훨씬 깔끔하고 좋은 것 같습니다. 역할과 책임이 좀 더 명확하게 구분된 것 같네요. 👍

});
OutputView.printEmptyLine();
}

tryMoveCars(tryCount, cars) {
OutputView.printResult();
for (let i = 0; i < tryCount; i++) {
this.moveCars(cars);
this.printCars(cars);
}
}

judgeWinners(cars) {
cars.sort((a, b) => b.getPosition() - a.getPosition());
const max = cars[0].getPosition();
cars.sort((a, b) => b.position - a.position);
const max = cars[0].position;
const winners = cars
.filter((car) => car.getPosition() === max)
.map((car) => car.getName());
.filter((car) => car.position === max)
.map((car) => car.name);
return winners;
}

Expand Down Expand Up @@ -84,6 +83,7 @@ class GameManager {
async play() {
await this.handleCarNames();
const tryCount = await this.handleTryCount();
OutputView.printResult();

Choose a reason for hiding this comment

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

적절한 위치에 OutputView.printResult() 코드가 삽입된 것 같습니다. 잘 리팩토링하신 것 같네요. 👍

this.tryMoveCars(tryCount, this.#cars);
const winners = this.judgeWinners([...this.#cars]);
OutputView.printWinners(winners);
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import GameManager from './GameManager.js';
import GameManager from './domain/GameManager.js';

const gameManger = new GameManager();
gameManger.play();
3 changes: 2 additions & 1 deletion src/utils/RandomGenerator.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import constants from './constants';
import constants from './constants.js';

const RandomGenerator = {
pickRandomNumber() {
return Math.floor(Math.random() * constants.RANDOM_RANGE);
Expand Down
16 changes: 11 additions & 5 deletions src/utils/Validation.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import constants from './constants';
import calcWordCount from './calcWordCount.js';
import constants from './constants.js';

const Validation = {
isValidCarNames(carNames) {
return carNames.every(
(name) =>
name.length >= constants.MIN_CAR_NAME_LENGTH &&
name.length <= constants.MAX_CAR_NAME_LENGTH
return carNames.every((carName) =>
this.isValidCarNameLength(calcWordCount(carName))
);
},

isValidTryCount(tryCount) {
return Number.isInteger(tryCount) && tryCount >= constants.MIN_TRY_COUNT;
},

isValidCarNameLength(carNameLength) {
return (
carNameLength >= constants.MIN_CAR_NAME_LENGTH &&
carNameLength <= constants.MAX_CAR_NAME_LENGTH
);
},
};
export default Validation;
8 changes: 8 additions & 0 deletions src/utils/calcWordCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function calcWordCount(text) {
let count = 0;
// eslint-disable-next-line no-unused-vars
for (const char of text) {
count += 1;
}
return count;
}
Comment on lines +1 to +8

Choose a reason for hiding this comment

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

적절한 네이밍과 함께 함수를 분리하셨네요! 좋습니다 👍
개인적으로는 이모지와 같은 예외 상황에 대응하기 위해 for ... of문을 사용하였다는 Note 주석을 남겨두는 것도 괜찮은 방법일 것 같아요.

2 changes: 1 addition & 1 deletion src/InputView.js → src/view/InputView.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Console from './utils/Console.js';
import Console from '../utils/Console.js';

const InputView = {
async readCarNames() {
Expand Down
4 changes: 2 additions & 2 deletions src/OutputView.js → src/view/OutputView.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Console from './utils/Console.js';
import constants from './utils/constants.js';
import Console from '../utils/Console.js';
import constants from '../utils/constants.js';

const OutputView = {
printEmptyLine() {
Expand Down