Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ec977e0
feat: Car 클래스 생성
mintcoke123 Sep 9, 2025
3f0445f
feat: RandomDigitGenerator 클래스 생성
mintcoke123 Sep 9, 2025
7df9482
test: Race 테스트코드 작성
mintcoke123 Sep 9, 2025
3ebb68d
refactor: 요구사항에 부합하게끔 (경주가 가능하게끔) Car.java 클래스 변경
mintcoke123 Sep 9, 2025
c671883
feat: 경주를 진행하는 Race클래스 생성
mintcoke123 Sep 9, 2025
36dbf6e
feat: 경주 관련 class 구현
mintcoke123 Sep 10, 2025
36984b7
refactor(임시저장): 파일구조 분리/클래스 캡슐화
mintcoke123 Sep 10, 2025
e2ba6f6
refactor: 유틸함수 분리, test까지 가능하도록 리팩터링
mintcoke123 Sep 11, 2025
cc07bf6
refactor: 테스트코드 추가, 단위테스트 세분화
mintcoke123 Sep 11, 2025
5c61abb
docs: README.md 파일 추가
mintcoke123 Sep 11, 2025
bf48666
refacor: stream 연산을 활용하여 코드 depth가 2이상이 되지 않도록 수정
mintcoke123 Sep 11, 2025
49ac5ef
chore: eol 규칙 준수
mintcoke123 Sep 13, 2025
ce2975d
chore: 코드 개행규칙 준수
mintcoke123 Sep 13, 2025
f122d22
chore: 코드 순서 정렬
mintcoke123 Sep 13, 2025
ecb605c
refactor: car 클래스 캡슐화 적용
mintcoke123 Sep 13, 2025
cc93719
refactor: carListGenearator가 이름을 입력받도록 변경
mintcoke123 Sep 13, 2025
7c4ac68
refactor: 객체의 책임에 따라 domain 과 utils 분리
mintcoke123 Sep 13, 2025
cfb1a00
refactor: 객체들의 캡슐화 진행, 테스트코드 리팩토링
mintcoke123 Sep 13, 2025
2eab03d
refactor: 경주 시작전에 모든 참가자들이 출발점에 있는지 확인
mintcoke123 Sep 16, 2025
a0e9167
refactor: 차 이름을 외부에서 가져온다고 가정, CarListGenerator 리팩토링
mintcoke123 Sep 16, 2025
4a63777
refactor: Car에서 정적팩토리메서드로 Car 선언
mintcoke123 Sep 16, 2025
e2609ba
refactor: 매직넘버로 상수관리
mintcoke123 Sep 16, 2025
cd29fb1
refactor: 필요없는 `stop` 매서드 제거
mintcoke123 Sep 16, 2025
600ac3b
chore:실행용 엔트리포인트 추가
mintcoke123 Sep 18, 2025
76b571e
refactor: 사용하지 않는 매서드 제거
mintcoke123 Sep 18, 2025
2b430b2
refactor: 게임 초기의 view 작성
mintcoke123 Sep 18, 2025
ae311f1
refactor: view 작성 완료
mintcoke123 Sep 18, 2025
f0c3fec
refactor: Main 컴포넌트 작동하게끔 수정
mintcoke123 Sep 18, 2025
0c09409
refactor: 요구사항에 맞게끔 메세지 줄바꿈
mintcoke123 Sep 18, 2025
e1d356b
refactor: controller와 view의 구체적 분리
mintcoke123 Sep 18, 2025
b49925d
refactor: 리네이밍, 요구사항 부합하게 변경
mintcoke123 Sep 18, 2025
af47e04
refactor: 전략 패턴으로 리팩토링
mintcoke123 Sep 18, 2025
19b1ab7
refactor: carTest 테스트코드 추가
mintcoke123 Sep 18, 2025
04b93c1
refactor: Race 전략패턴 방식으로 변경
mintcoke123 Sep 18, 2025
5133c61
refactor: 모든 model 로직 단위 테스트
mintcoke123 Sep 18, 2025
ad5232a
refactor: 전략 패턴 반영
mintcoke123 Sep 18, 2025
029ea6d
refactor: 입력 조건문 요구사항 반영
mintcoke123 Sep 18, 2025
ef3714a
docs: README.md 작성
mintcoke123 Sep 18, 2025
c8adcfb
refactor: mvc 패턴 세분화
mintcoke123 Sep 19, 2025
626f0ee
refactor: do-while 문 적용하여 RaceController 리팩토링
mintcoke123 Sep 19, 2025
0313518
chore: 코드 컨벤션 적용
mintcoke123 Sep 21, 2025
0a839cb
chore: editorconfig 파일 추가, 개행문자 적용
mintcoke123 Sep 21, 2025
96a19f7
refactor: getValidCarNames, getValidTurn의 관심사 분리
mintcoke123 Sep 21, 2025
1490281
refactor: 매직넘버 네이밍 컨벤션 수정
mintcoke123 Sep 21, 2025
38fd877
refactor: 이름 입력 검증로직 수정
mintcoke123 Sep 21, 2025
2aed197
refactor: 턴수 입력 검증로직 수정
mintcoke123 Sep 21, 2025
945ac06
test: carTest 테스트코드 리팩토링
mintcoke123 Sep 21, 2025
f18197e
test: 컨드롤러 테스트코드 추가
mintcoke123 Sep 21, 2025
d1f8b9b
Merge branch 'mintcoke123' into step_4_TDD_refactoring
mintcoke123 Sep 22, 2025
279cba2
refactor: 변수명 리네이밍
mintcoke123 Sep 22, 2025
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
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
251 changes: 242 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# 🚀 자동차 경주 미션

## 1단계 - 움직이는 자동차

---

## 📝 기능 요구사항

- 자동차는 이름을 가지고 있다.
- 자동차는 움직일 수 있다.
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.

- 자동차는 이름을 가지고 있다.
- 자동차는 움직일 수 있다.
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.



Expand All @@ -25,14 +27,18 @@ car 객체는 랜덤값을 받고, 값에 따라 위치를 이동하게 됩니

현재 코드에서 랜덤값을 사용하는 것은 자동차 뿐이므로, 재사용성이 낮은 김에 자동차 객체 내부에 랜덤값 생성 함수를 넣을까 생각했습니다.

그러나 경주를 진행할 때 여러 대의 자동차들이 각자의 객체 내부에서 랜덤값을 구하고, 그 랜덤값을 다시 객체 내부에서 활용하는 것보다는 외부 `RandomNumberGenerator` 에서 랜덤값을 받아와 자동차 객체에 사용하는 것이 적절하다고 생각했습니다.
그러나 경주를 진행할 때 여러 대의 자동차들이 각자의 객체 내부에서 랜덤값을 구하고, 그 랜덤값을 다시 객체 내부에서 활용하는 것보다는 외부 `RandomNumberGenerator` 에서 랜덤값을 받아와 자동차
객체에 사용하는 것이 적절하다고 생각했습니다.

### Car의 유틸함수 분리는 필요한가?

재사용성을 위해 `car` 객체에서 이동 관련 로직 `move`의 유틸함수의 분리해볼까 했습니다만, 다음 두 이유로 그만두었습니다.

1. 현재의 `car` 객체에서 유틸 함수를 분리하면, Car객체는 carName이라는 데이터를 저장하는 저장소 역할만을 수행해 객체로써 존재해야 할 이유가 희석된다고 판단했습니다.
2. 설령 `car` 객체에 다른 요소들이 추가된다고 한들, `move`는 `car` 객체 자체의 움직임을 책임진다고 생각하기 때문에, `move`메서드를 사용하는 다른 객체가 나오지 않는다면 메서드의 분리는 불필요하다고 생각했습니다.

2. 설령 `car` 객체에 다른 요소들이 추가된다고 한들, `move`는 `car` 객체 자체의 움직임을 책임진다고 생각하기 때문에, `move`메서드를 사용하는 다른 객체가 나오지 않는다면 메서드의 분리는
불필요하다고 생각했습니다.


---

Expand All @@ -42,7 +48,7 @@ car 객체는 랜덤값을 받고, 값에 따라 위치를 이동하게 됩니

## 📝 요구사항

- 자동차가 참여할 수 있다.
- 자동차가 참여할 수 있다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 구할 수 있다. 우승자는 한 명 이상일 수 있다.
Expand All @@ -65,19 +71,246 @@ car 객체는 랜덤값을 받고, 값에 따라 위치를 이동하게 됩니

저는 테스트코드를 작성하는 시점이 개발 이전이라고 생각했습니다.

개발 중에는 얼마든지 버그/휴먼에러가 일어날 수 있고, 그로 인해 개발 이후에 테스트코드를 작성하게 된다면 현재 기능이 검증되지 않은 제 코드에 맞추어서 테스트코드를 작성하게 될 수 있으며 그렇게 된다면 테스트코드를 작성하는 의미 자체가 없어진다고 생각합니다.
개발 중에는 얼마든지 버그/휴먼에러가 일어날 수 있고, 그로 인해 개발 이후에 테스트코드를 작성하게 된다면 현재 기능이 검증되지 않은 제 코드에 맞추어서 테스트코드를 작성하게 될 수 있으며 그렇게 된다면 테스트코드를
작성하는 의미 자체가 없어진다고 생각합니다.

### `RaceUtils`와 `CarListGenerator`, `RandomDigitGenerator`의 분리

### `RaceUtils`와 `CarListGenerator`, `RandomDigitGenerator`의 분리

코드 작성중에 유틸함수를 분리하며 고민했던 기능입니다.

`utils/RaceUtils`는 경주를 진행하고, 우승자를 정하는 유틸함수의 모음집입니다.

`domain/Race`는 경주의 시작과 결과를 뽑아내는 클래스입니다. 이 과정에서 `RaceUtils`와 다른 `utils`를 가져옵니다.

현재의 코드에서 `CarListGenerator` 와 `RandomDigitGenerator`는 `Race`의 흐름에 사용되지만, `Race`가 가져다 쓰는 유틸일 뿐 유틸함수의 내용들이 `Race`와 연관이 있다고는 생각하지 않았습니다.

현재의 코드에서 `CarListGenerator` 와 `RandomDigitGenerator`는 `Race`의 흐름에 사용되지만, `Race`가 가져다 쓰는 유틸일 뿐 유틸함수의 내용들이 `Race`와 연관이
있다고는 생각하지 않았습니다.

그래서 RaceUtils와 위 유틸들을 분리해 미션을 진행하였습니다.

---

## 3단계 - 게임 실행

---

## 📝 요구사항

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.

## ➕ 새로운 프로그래밍 요구사항

- 메인 메서드를 추가하여 실행 가능한 애플리케이션으로 만든다.

## 💻 구현 과정

1. 프로그레밍 요구사항에 맞는 view 작성(mvc 패턴 도입)
2. 기존에 작성한 `Race` 클래스에서 `controller` 추출(`model`과 `controller`로 분리)
3. Main 컴포넌트 작동하게끔 수정

## 🤔 고민한 부분들

### mvc 패턴에서 패키지 분리는 어떻게 진행하면 좋을까?

기존 클래스를 mvc 패턴으로 분리하는 과정에서, 패키지 분리를 둘 중 어떤 방식으로 진행할지 고민했습니다.

1. 도메인 단위 페키지 분리

```
race/Race.java
race/RaceController.java
race/RaceView.java
```

2. 계층별 패키지 분리

```
domain/Race.java
controller/RaceController.java
view/RaceView.java
```

처음에는 특정 도메인 관련 클래스가 한 군데에 모여있어 관리가 쉬울 것이라고 생각해, 1번 방법을 선택할까 했습니다.

그러나 아래와 같은 두 가지 이유로 2번을 선택했습니다.

1. 데이터의 흐름

한 기능이 한 폴더에 모여있게 되어, 유저와의 상호작용과 데이터의 흐름을 생각했을 때 역할별로 분리하는 것이 유지보수가 쉽다고 생각했습니다.

2. 테스트 단위의 용이성

`modal` 부분만 테스트를 진행한다고 가정했을 때, 기능 단위로 테스트를 묶기 좋습니다.

### `RaceController`의 `runRace`의 위치와 역할

`runRace` 는, 요구사항의 마지막 단계에 부합하는, 유저의 입력을 받아 지금까지 만들어놓은 메서드를 조립하여

1. 유저에게 정보받기
2. 받은 정보로 경주진행
3. 결과출력

까지 진행하는 메서드입니다.

위 메서드에서 저는 2가지의 고민을 하였습니다.

#### 1. `runRace`를 분리할 수는 없을까?

저는 메서드를 작성할 때 항상 단일 책임 원칙을 지키려고 노력했습니다.

그러나 `runRace`는 하는 역할이 많음에도 불구하고 나눌 각이 잘 보이지 않았습니다.

처음에는

`raceSetting`

1. 유저에게 정보받기

`runRace`

2. 받은 정보로 경주진행
3. 결과출력

와 같이 분리하고자 했지만, runRace는 컨트롤러로써 유저에게서 받은 정보에 의존하고,

1,2와 3을 분리하자니 3의 `inputView` 파트는 `controller`로부터 받은 `model`의 정보에 의존합니다.

결국 `runRace` 는 `경주 절차를 오케스트레이션한다` 라는 단일 책임을 맡았다고 정의하여, 지금과 같은 형태가 되었습니다.

#### 2. `runRace`는 `controller`에서 관리하는 것이 맞을까?

runRace는 지금까지 작성한 메서드들을 **완성** 시킴과 동시에, `view` 와 `model`을 조립하는 역할을 합니다.
앞서 말씀드렸다시피 `runRace`는 많은 책임을 안고있기 때문에, `main`에서 마지막으로 조립하는 방식도 고려해 보았습니다만,
`main`은 **앱 시작점**의 역할만 담당하는 것이 바람직하다고 생각하기 때문에 `runRace`를 `controller`에 배치하였습니다.


---

## 3단계 - 게임 실행

---

## 📝 요구사항

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.

## ➕ 새로운 프로그래밍 요구사항

- 메인 메서드를 추가하여 실행 가능한 애플리케이션으로 만든다.

## 💻 구현 과정

1. 프로그레밍 요구사항에 맞는 view 작성(mvc 패턴 도입)
2. 기존에 작성한 `Race` 클래스에서 `controller` 추출(`model`과 `controller`로 분리)
3. Main 컴포넌트 작동하게끔 수정

## 🤔 고민한 부분들

### `model` 과 `controller`의 분리

### mvc 패턴에서 패키지 분리는 어떻게 진행하면 좋을까?

기존 클래스를 mvc 패턴으로 분리하는 과정에서, 패키지 분리를 둘 중 어떤 방식으로 진행할지 고민했습니다.

1. 도메인 단위 페키지 분리

```
race/Race.java
race/RaceController.java
race/RaceView.java
```

2. 계층별 패키지 분리

```
domain/Race.java
controller/RaceController.java
view/RaceView.java
```

처음에는 특정 도메인 관련 클래스가 한 군데에 모여있어 관리가 쉬울 것이라고 생각해, 1번 방법을 선택할까 했습니다.

그러나 아래와 같은 두 가지 이유로 2번을 선택했습니다.

1. 데이터의 흐름

한 기능이 한 폴더에 모여있게 되어, 유저와의 상호작용과 데이터의 흐름을 생각했을 때 역할별로 분리하는 것이 유지보수가 쉽다고 생각했습니다.

2. 테스트 단위의 용이성

`modal` 부분만 테스트를 진행한다고 가정했을 때, 기능 단위로 테스트를 묶기 좋습니다.

### `RaceController`의 `runRace`의 위치와 역할

`runRace` 는, 요구사항의 마지막 단계에 부합하는, 유저의 입력을 받아 지금까지 만들어놓은 메서드를 조립하여

1. 유저에게 정보받기
2. 받은 정보로 경주진행
3. 결과출력

까지 진행하는 메서드입니다.

위 메서드에서 저는 2가지의 고민을 하였습니다.

#### 1. `runRace`를 분리할 수는 없을까?

저는 메서드를 작성할 때 항상 단일 책임 원칙을 지키려고 노력했습니다.

그러나 `runRace`는 하는 역할이 많음에도 불구하고 나눌 각이 잘 보이지 않았습니다.

처음에는

`raceSetting`

1. 유저에게 정보받기

`runRace`

2. 받은 정보로 경주진행
3. 결과출력

와 같이 분리하고자 했지만, runRace는 컨트롤러로써 유저에게서 받은 정보에 의존하고,

1,2와 3을 분리하자니 3의 `inputView` 파트는 `controller`로부터 받은 `model`의 정보에 의존합니다.

결국 `runRace` 는 `경주 절차를 오케스트레이션한다` 라는 단일 책임을 맡았다고 정의하여, 지금과 같은 형태가 되었습니다.

#### 2. `runRace`는 `controller`에서 관리하는 것이 맞을까?

runRace는 지금까지 작성한 메서드들을 **완성** 시킴과 동시에, `view` 와 `model`을 조립하는 역할을 합니다.
앞서 말씀드렸다시피 `runRace`는 많은 책임을 안고있기 때문에, `main`에서 마지막으로 조립하는 방식도 고려해 보았습니다만,
`main`은 **앱 시작점**의 역할만 담당하는 것이 바람직하다고 생각하기 때문에 `runRace`를 `controller`에 배치하였습니다.

---

## 4단계 - 리팩터링

---

지를 알려준다. 우승자는 한 명 이상일 수 있다.

## ➕ 새로운 프로그래밍 요구사항

- 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외한다.
- 랜덤한 요소가 존재하는 코드는 어떻게 테스트할 수 있는지 경험한다.

## 💻 구현 과정

1. 클래스 전략 패턴으로 리팩토링
2. 메서드별 단위테스트 작성

## 🤔 고민한 부분들

제가 4단계에 단위 테스트가 나오는 줄 모르고 미리 단위테스트를 진행해서, 이 부분은 넘어가겠습니다!

18 changes: 8 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ plugins {
group = 'cholog'
version = '1.0-SNAPSHOT'

repositories {
mavenCentral()
}
repositories { mavenCentral() }

dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation platform('org.assertj:assertj-bom:3.25.1')
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation('org.assertj:assertj-core')
testImplementation platform('org.junit:junit-bom:5.10.2')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.mockito:mockito-core:5.12.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.12.0'
testImplementation 'org.assertj:assertj-core:3.25.3'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}

test {
useJUnitPlatform()
}
test { useJUnitPlatform() }
14 changes: 14 additions & 0 deletions src/main/java/Main.java

Choose a reason for hiding this comment

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

해당 부분 보시면 End Of Line 경고가 발생하고 있는데 확인 부탁드려요!

Copy link
Author

Choose a reason for hiding this comment

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

넵! 수정했습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import controller.RaceController;
import utils.RandomDigitGenerator;
import view.RaceInputView;
import view.RaceOutputView;

public class Main {
public static void main(String[] args) {
new RaceController(
new RaceInputView(),
new RaceOutputView(),
new RandomDigitGenerator()
).runRace();
}
}
Loading