-
Notifications
You must be signed in to change notification settings - Fork 74
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
[자동차 경주] 김민창 4단계 미션 제출합니다. #62
base: idle2534
Are you sure you want to change the base?
Changes from 7 commits
c5928a7
298065d
49ecbf8
1ca6611
f461973
e6a5ece
5e1aff2
74656e5
664ce8d
83fbd71
13a911b
1a3d4b5
68f54a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
## 움직이는 자동차 [O] | ||
### 기능 요구사항 | ||
- 자동차는 이름을 가지고 있다. | ||
- 자동차는 움직일 수 있다. | ||
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. | ||
### 새로운 프로그래밍 요구사항 | ||
- 자동차가 움직이는 기능이 의도대로 동작하는지 테스트한다. | ||
- 자바 코드 컨벤션을 지키면서 프로그래밍한다. | ||
- 기본적으로 Java Style Guide을 원칙으로 한다. | ||
- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. | ||
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. | ||
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. | ||
- 3항 연산자를 쓰지 않는다. | ||
- else 예약어를 쓰지 않는다. | ||
- else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. | ||
- 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다. | ||
- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다. | ||
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. | ||
### 기존 프로그래밍 요구사항 | ||
- 메인 메서드는 만들지 않는다. | ||
|
||
## 우승 자동차 구하기 [O] | ||
### 기능 요구사항 | ||
- n대의 자동차가 참여할 수 있다. | ||
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. | ||
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. | ||
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 구할 수 있다. 우승자는 한 명 이상일 수 있다. | ||
### 새로운 프로그래밍 요구사항 | ||
- 우승자를 구하는 기능이 의도대로 동작하는지 테스트한다. | ||
|
||
## 게임 실행 [O] | ||
### 기능 요구사항 | ||
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. | ||
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. | ||
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. | ||
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. | ||
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다. | ||
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. | ||
<br>**실행 결과** | ||
- 위 요구사항에 따라 3대의 자동차가 5번 움직였을 경우 프로그램을 실행한 결과는 다음과 같다. | ||
``` | ||
경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분). | ||
neo,brie,brown | ||
시도할 회수는 몇회인가요? | ||
5 | ||
|
||
실행 결과 | ||
neo : - | ||
brie : - | ||
brown : - | ||
|
||
neo : -- | ||
brie : - | ||
brown : -- | ||
|
||
neo : --- | ||
brie : -- | ||
brown : --- | ||
|
||
neo : ---- | ||
brie : --- | ||
brown : ---- | ||
|
||
neo : ----- | ||
brie : ---- | ||
brown : ----- | ||
|
||
neo : ----- | ||
brie : ---- | ||
brown : ----- | ||
|
||
neo, brown가 최종 우승했습니다. | ||
``` | ||
|
||
### 새로운 프로그래밍 요구사항 | ||
- 메인 메서드를 추가하여 실행 가능한 애플리케이션으로 만든다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public class Main { | ||
public static void main(String[] args) { | ||
RacingCarGame game = new RacingCarGame(); | ||
game.setGame(); | ||
game.startGame(); | ||
game.resetGame(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Random; | ||
|
||
public class RacingCar { | ||
private final String name; | ||
private int score = 0; | ||
private final List<Boolean> results = new ArrayList<>(); | ||
|
||
public RacingCar(String name) { | ||
this.name = name; | ||
} | ||
|
||
private int randPower() { | ||
Random rand = new Random(); | ||
return rand.nextInt(10); | ||
} | ||
|
||
public int move() { | ||
return move(randPower()); | ||
} | ||
|
||
public int move(int power) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 메서드는 test말고 외부 클래스에서 호출된 적이 없는데, public이네요. |
||
boolean isMovable = checkPower(power); | ||
|
||
if (isMovable) | ||
++score; | ||
|
||
results.add(isMovable); | ||
|
||
return score; | ||
} | ||
|
||
private boolean checkPower(int power) { | ||
if (power < 0 || power > 9) | ||
throw new IllegalArgumentException("0~9 사이의 Power 값을 필요로 합니다."); | ||
|
||
return power >= 4; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public int getScore() { | ||
return score; | ||
} | ||
|
||
public String getResultString(int phase) { | ||
StringBuilder resultString = new StringBuilder(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 method 파라미터에 phase를 넣은 이유가 있을까요? |
||
for (int i = 0; i < phase && i < results.size(); i++) { | ||
resultString.append(resultToString(results.get(i))); | ||
} | ||
return resultString.toString(); | ||
} | ||
|
||
private String resultToString(Boolean result) { | ||
if (result) | ||
return "-"; | ||
|
||
return ""; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import java.util.ArrayList; | ||
import java.util.InputMismatchException; | ||
import java.util.List; | ||
import java.util.Scanner; | ||
|
||
public class RacingCarGame { | ||
|
||
int highScore; | ||
int phase = 0; | ||
List<RacingCar> racingCars = new ArrayList<>(); | ||
boolean isGameInit = false; | ||
|
||
public void setGame() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 메서드는 15줄을 넘기면 안된다는 요구사항을 만족하지 못한 것 같아요 어떻게 분리할 수 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 솔직히 제가 카톡에 장문 쓰기 위해서 요구사항을 다시 한번 정독하기 전까지는 15줄을 넘기면 안된다는 것을 못봤어서 그 전에 짠 코드인데 수정을 깜빡한 것 같네요. 저는 일단 자동차를 입력 받는 부분과 시도할 횟수를 입력받는 부분을 각각 함수로 따로 빼서 분리할 것 같습니다. 예외처리도 따로 분리 하고 싶은데 자동차의 경우 가능하지만 횟수(phase)는 try catch로 감싸서 처리했는데 try catch 안에 분리한 입력 함수를 넣는 방식은 조금 별로라고 생각해서 어떻게 분리해야할지 잘 모르겠습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거는 피드백을 남겨드리고 싶었는데, 지금 바뀌어서 피드백을 드리기가 힘들겠군요. 다만 궁금한게 있는데
라고 생각하신 이유가 있을까요? |
||
Scanner scanner = new Scanner(System.in); | ||
|
||
System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분)."); | ||
String input = scanner.nextLine(); | ||
|
||
for (String name : input.split(",")) | ||
racingCars.add(new RacingCar(name)); | ||
|
||
if (racingCars.size() < 2) | ||
throw new InputMismatchException("최소한 두 명 이상의 플레이어가 필요합니다."); | ||
|
||
System.out.println("시도할 회수는 몇회인가요?"); | ||
try { | ||
phase = scanner.nextInt(); | ||
if (phase < 0) { | ||
throw new NumberFormatException("음수는 유효한 숫자가 아닙니다."); | ||
} | ||
} catch (InputMismatchException e) { | ||
throw new InputMismatchException("유효한 숫자가 아닙니다."); | ||
} | ||
|
||
|
||
isGameInit = true; | ||
} | ||
|
||
public void startGame() { | ||
if (!isGameInit) | ||
throw new IllegalStateException("게임이 초기화되지 않았습니다."); | ||
|
||
for(int i = 0; i < phase; ++i){ | ||
moveAllRacingCars(); | ||
} | ||
|
||
printResult(); | ||
} | ||
|
||
public void resetGame() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resetGame을 만드신 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기능 요구사항에 없었고 실제로 Application에 별 의미없었으며 private으로 할 수도 있었고 애초에 함수로 따로 뺄 필요도 없었지만 그냥 게임을 다시 시작할 수 있도록 초기화해주는 외부 함수가 있으면 좋을 것 같아서 만들어 둔 것입니다. 지금 Main에서 처음부터 초기화 한다고 가정했을 때 setGame() -> startGame() -> resetGame() 이렇게 진행되는데 이를 private으로 바꾸고 public void game()으로 묶어서 외부에서 하나의 함수만 호출되게하는게 좋을까요? 아니면 3개를 쪼개서 필요에 따라 호출할 수 있기 하는 편이 좋을까요? 어떤 프로그램이냐에 따라 다를 것 같긴 한데 이번 프로그램에서는 어떻게 하실 것 같으신지 의견 듣고 싶습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 필요하지 않는 메서드는 굳이 만들지 않는 주의라서 resetGame이 필요없다고 생각해요. |
||
racingCars.clear(); | ||
highScore = 0; | ||
} | ||
|
||
private void moveAllRacingCars() { | ||
for(RacingCar racingCar : racingCars) | ||
racingCar.move(); | ||
} | ||
|
||
private void printResult() { | ||
System.out.println("\n실행 결과"); | ||
|
||
for (int i = 1; i <= phase; ++i){ | ||
printAllRacingCarResults(i); | ||
System.out.println(); | ||
} | ||
|
||
System.out.println(String.join(", ", getWinnerNames()) + "가 최종 우승했습니다."); | ||
} | ||
|
||
private void printAllRacingCarResults(int phase) { | ||
for (RacingCar racingCar : racingCars) { | ||
System.out.println(racingCar.getName() + " : " + racingCar.getResultString(phase)); | ||
} | ||
} | ||
|
||
private List<String> getWinnerNames() { | ||
return racingCars.stream().filter(this::checkWinner).map(RacingCar::getName).toList(); | ||
} | ||
|
||
private boolean checkWinner(RacingCar racingCar) { | ||
if (racingCar.getScore() > highScore) | ||
highScore = racingCar.getScore(); | ||
|
||
return racingCar.getScore() == highScore; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.util.InputMismatchException; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class RacingCarTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 클래스 이름은 RacingCarTest인데, RacingCarGame에 대한 부분도 포함된 것 같아요. 어떤 기준으로 테스트 클래스를 나눠볼 수 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외처리를 하고 싶은 부분이 어느 클래스에 있는지를 기준으로 테스트 클래스를 나뉠 것 같습니다. 이번의 경우 Nested로 처리한 SetRacingCarGameTest를 RacingCarGameTest 클래스를 새로 만들어 거기로 옮긴 후 Nested class 이름을 SetGameTest 수정할 듯 합니다. |
||
|
||
@Nested | ||
@DisplayName("자동차 이동 테스트") | ||
class RacingCarMove { | ||
@Test | ||
@DisplayName("자동차 랜덤 이동 테스트") | ||
void randomMove() { | ||
RacingCar racingCar = new RacingCar("Test"); | ||
int expected = racingCar.getScore(); | ||
int actual = racingCar.move(); | ||
|
||
assertThat(actual).isIn(expected, expected + 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 isIn 메서드로도 테스트를 어느정도 할 수 있긴 하지만, 명확하게 move에 대한 테스트는 하기 어려울 것 같아요. |
||
} | ||
|
||
@Test | ||
@DisplayName("자동차 이동 조건 테스트") | ||
void moveCondition() { | ||
RacingCar racingCar = new RacingCar("Test"); | ||
int expected = 0; | ||
int actual = racingCar.move(3); | ||
|
||
assertThat(actual).isEqualTo(expected); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assertThat을 하나의 테스트 메서드 안에 쓰는 경우 어떤 문제가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parameterizedTest를 활용해야하는 것은 이해했는데 assertThat을 하나의 테스트 메서드 안에 쓰는 경우가 정확히 무엇을 의미하는지 모르겠습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분에 대해서 설명이 좀 부족할 수 있네요. 만약 assertThat(case1)
assertThat(case2)
assertThat(case3) 이렇게 되어 있을 때 case1이 실패되면, case 2, case3에 대한 검증을 못합니다. assertAll 같은 함수도 학습해보면 좋을 것 같습니다. |
||
|
||
expected = 1; | ||
actual = racingCar.move(5); | ||
|
||
assertThat(actual).isEqualTo(expected); | ||
} | ||
|
||
@Test | ||
@DisplayName("자동차 이동 조건 예외처리 테스트") | ||
void moveConditionException() { | ||
RacingCar racingCar = new RacingCar("Test"); | ||
|
||
assertThatThrownBy(() -> racingCar.move(-1)).isInstanceOf(IllegalArgumentException.class).hasMessage("0~9 사이의 Power 값을 필요로 합니다."); | ||
|
||
assertThatThrownBy(() -> racingCar.move(10)).isInstanceOf(IllegalArgumentException.class).hasMessage("0~9 사이의 Power 값을 필요로 합니다."); | ||
} | ||
} | ||
|
||
|
||
@Test | ||
@DisplayName("자동차 결과 텍스트 테스트") | ||
void racingCarResultString() { | ||
RacingCar racingCar = new RacingCar("Test"); | ||
|
||
for (int i = 0; i < 5; ++i) | ||
racingCar.move(); | ||
|
||
|
||
String actual = racingCar.getResultString(5); | ||
String expected = new String(new char[racingCar.getScore()]).replace("\0", "-"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런 경우 여러줄로 분리하는 것도 좋아보여요 너무, 한줄에 많은 의미가 담겨있고 괄호가 많아 가독성이 떨어지게 보일 수도 있을 것 같아요 |
||
|
||
assertThat(actual).isEqualTo(expected); | ||
} | ||
|
||
@Nested | ||
@DisplayName("자동차 게임 설정 테스트") | ||
class SetRacingCarGameTest { | ||
@Test | ||
@DisplayName("자동차 이름 입력이 하나일 경우 테스트") | ||
void setGameWithLessThanTwoNameInput() { | ||
RacingCarGame racingCarGame = new RacingCarGame(); | ||
System.setIn(new ByteArrayInputStream("test1\n1".getBytes())); | ||
|
||
assertThatThrownBy(racingCarGame::setGame).isInstanceOf(InputMismatchException.class).hasMessage("최소한 두 명 이상의 플레이어가 필요합니다."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 얘는 어째서 ::와 같은 방식으로 사용할 수 있을까요? |
||
} | ||
|
||
@Test | ||
@DisplayName("시도할 횟수 입력이 음수일 경우 테스트") | ||
void setGameWithNegativePhaseInput() { | ||
RacingCarGame racingCarGame = new RacingCarGame(); | ||
System.setIn(new ByteArrayInputStream("test1,test2\n-1\n".getBytes())); | ||
|
||
assertThatThrownBy(racingCarGame::setGame).isInstanceOf(NumberFormatException.class).hasMessage("음수는 유효한 숫자가 아닙니다."); | ||
} | ||
|
||
@Test | ||
@DisplayName("시도할 횟수 입력이 숫자가 아닌 경우 테스트") | ||
void setGameWithInvalidPhaseInput() { | ||
RacingCarGame racingCarGame = new RacingCarGame(); | ||
System.setIn(new ByteArrayInputStream("test1,test2\nabc\n".getBytes())); | ||
|
||
assertThatThrownBy(racingCarGame::setGame).isInstanceOf(InputMismatchException.class).hasMessage("유효한 숫자가 아닙니다."); | ||
} | ||
} | ||
} |
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.
이름은 5자 이하만 가능하다하였는데, 이 부분은 구현이 안 된 것 같아요!