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

[자동차 경주] 이해승 미션 제출합니다. #1467

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f5131a9
Docs: readme 문서 작성
lee-haeseung Oct 28, 2024
a6d725e
chore: mvc 패턴 사용을 위한 세팅
lee-haeseung Oct 28, 2024
86566df
docs: readme 수정
lee-haeseung Oct 28, 2024
d637765
test: 자동차에 대한 test 코드
lee-haeseung Oct 28, 2024
81fcc92
test: 누락된 테스트 추가
lee-haeseung Oct 28, 2024
7dca641
feat: 자동차 생성시 이름 부여 가능하게 구현
lee-haeseung Oct 28, 2024
3660d70
feat: 자동차 전진/정지 기능 구현
lee-haeseung Oct 28, 2024
5a848b3
docs: 전진/정지 구현 기능 완료 표시
lee-haeseung Oct 28, 2024
81e3d45
feat: 확률에 따른 전진/정지 기능 구현
lee-haeseung Oct 28, 2024
fd304a9
docs: 자동차 생성시 처리할 수 있는 오류 추가
lee-haeseung Oct 28, 2024
f02f625
test: 잘못된 test 방식 수정
lee-haeseung Oct 28, 2024
b1f389a
feat: 자동차 이름이 5글자 이상인 경우 예외 처리
lee-haeseung Oct 28, 2024
65c5cc3
feat: 자동차 이름 없는 경우 예외 처리
lee-haeseung Oct 28, 2024
fef4a61
docs: 예외처리 추가
lee-haeseung Oct 28, 2024
d71c6f6
feat: 자동차 이름 및 시도 횟수 입력 구현
lee-haeseung Oct 28, 2024
c6d0396
feat: 이동 횟수 입력 구현
lee-haeseung Oct 28, 2024
b854187
test: 입력에 대한 test 추가
lee-haeseung Oct 28, 2024
8c90330
feat: 입력시 자동차 객체 생성
lee-haeseung Oct 28, 2024
4af7d71
test: test 이름 수정
lee-haeseung Oct 28, 2024
bf28d9e
feat: 같은 이름 중복 예외 처리
lee-haeseung Oct 28, 2024
c31b90f
feat: 자동차 이름이 없는 경우에 대한 예외 처리
lee-haeseung Oct 28, 2024
0d54bb9
test: 잘못된 test 수정
lee-haeseung Oct 28, 2024
4ebbf7d
feat: 시도 횟수가 올바르지 않은 경우 예외 처리
lee-haeseung Oct 28, 2024
8c56fec
test: 출력 테스트 구현
lee-haeseung Oct 28, 2024
6eba633
feat: 자동차 전진도 출력
lee-haeseung Oct 28, 2024
51337b1
docs: 구현한 기능 표시
lee-haeseung Oct 28, 2024
2cbaa6f
feat: 우승자 출력
lee-haeseung Oct 28, 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
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# java-racingcar-precourse

# 기능 목록

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다.

# 구현할 기능

## 자동차

- [x] 주어진 횟수 동안 자동차의 전진/정지 기능
- [x] 자동차에 이름 부여 기능
- [x] 0에서 9까지의 무작위 값 구하기 및 4 이상인 경우 전진
- [x] 예외 처리
- [x] 이름 없는 경우
- [x] 자동차 이름이 5글자를 초과한 경우

## 입력

- [x] 쉼표(,)를 기준으로 구분된 5자 이하의 자동차 이름 입력
- [x] 이동 횟수 입력
- [x] 잘못된 값을 입력한 경우 예외 처리
- [x] 같은 이름의 자동차가 들어오는 경우
- [x] 자동차 이름이 5글자를 초과한 경우
- [x] 이름이 없는 경우 → 오류 출력
- [x] 시도하는 횟수 입력이 올바르지 않은 경우

## 출력

- [x] 자동차 출력 (이름과 전진한 정도)
- [x] 우승자 출력
- [x] 여러명인 경우 쉼표(,)로 구분하여 출력
41 changes: 40 additions & 1 deletion src/main/java/racingcar/Application.java

Choose a reason for hiding this comment

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

Application과 Controller의 역할이 중복되는 것 같은데 통합된다면 좋을 것 같습니다!

Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String carNameStrings = Console.readLine();

Choose a reason for hiding this comment

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

View에서 Output과 Input으로도 나누어 볼 수 있겠네요!


// view 생성
Output view = new Output();

// model 생성
List<Car> cars = new ArrayList<>();
Set<String> carNamesSet = new HashSet<>();
for (String carName : carNameStrings.split(",", -1)) {
if (carNamesSet.contains(carName)) {
throw new IllegalArgumentException();
}
cars.add(new Car(carName));
carNamesSet.add(carName);
}

// controller 생성
RacingCarController controller = new RacingCarController(view, cars);

System.out.println("시도할 횟수는 몇 회인가요?");
String playTimeString = Console.readLine();

int playTime = -1;
try {
playTime = Integer.parseInt(playTimeString);
} catch (Exception e) {
throw new IllegalArgumentException();
}
if (playTime < 0) {
throw new IllegalArgumentException();
}

controller.play(playTime);
}
}
43 changes: 43 additions & 0 deletions src/main/java/racingcar/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package racingcar;

import camp.nextstep.edu.missionutils.Randoms;

public class Car {
private Integer location;
private String name;

public Car(String name) {
location = 0;
name = name.trim();
if (name.isEmpty() || name.length() > 5) {
throw new IllegalArgumentException();
}
this.name = name;
}
Comment on lines +9 to +16

Choose a reason for hiding this comment

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

검증하는 부분을 함수로 처리한다면 가독성이 높아질 것 같습니다.

Suggested change
public Car(String name) {
location = 0;
name = name.trim();
if (name.isEmpty() || name.length() > 5) {
throw new IllegalArgumentException();
}
this.name = name;
}
public Car(String name) {
location = 0;
name = name.trim();
validateName(name);
this.name = name;
}
validateName이라는 메서드를 만들어서 이런식으로 처리하면 좋습니다!


public Car() {
location = 0;
throw new IllegalArgumentException();
}

private void move() {
location++;
}

public void randomMove() {
int randomValue = Randoms.pickNumberInRange(0,9);
if (randomValue >= 4) {
this.move();
} else {
// 정지
}
}

public Integer getLocation() {
return location;
}

public String getName() {
return name;
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/Output.java

Choose a reason for hiding this comment

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

Output에 대한 분리는 잘하신 것 같습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar;

import java.util.List;

public class Output {
private static final String ONE_STEP = "-";

public void printResultString() {
System.out.println("실행 결과");
}

public void printCars(List<Car> cars) {
for (Car car : cars) {
System.out.print(car.getName());
System.out.print(" : ");
System.out.println(ONE_STEP.repeat(car.getLocation()));
}
System.out.println();
}

public void printWinners(List<String> winners) {
System.out.print("최종 우승자 : ");
for (String winner : winners) {
System.out.print(winner);
}
}
}
43 changes: 43 additions & 0 deletions src/main/java/racingcar/RacingCarController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package racingcar;

import java.util.ArrayList;
import java.util.List;

public class RacingCarController {

private Output output;
private List<Car> cars;

public RacingCarController(Output output, List<Car> cars) {
this.output = output;
this.cars = cars;
}
Comment on lines +11 to +14

Choose a reason for hiding this comment

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

매개변수로 받아 컨트롤러를 생성하기보다는

Suggested change
public RacingCarController(Output output, List<Car> cars) {
this.output = output;
this.cars = cars;
}
public RacingCarController(Output output, List<Car> cars) {
this.output = new Output;
this.cars = new Cars;
}

이런식으로 생성하는 생성하는게 나을 것 같아요


public void play(int time) {
output.printResultString();
for (int i = 0; i < time; i++) {
for (Car car : cars) {
car.randomMove();
}
output.printCars(cars);
}
List<String> winners = findWinner();
output.printWinners(winners);
}

public List<String> findWinner() {
List<String> winners = new ArrayList<>();
int maxLocation = 0;

for (Car car : cars) {
if (maxLocation < car.getLocation()) {
winners.clear();
winners.add(car.getName());
} else if (maxLocation == car.getLocation()) {
winners.add(car.getName());
}
}

return winners;
}
Comment on lines +16 to +42

Choose a reason for hiding this comment

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

해당 부분(비지니스 로직)은 model 쪽에서 관리할 수 있게 하는게 좋을 것 같아요.
controller의 역할은 입/출력 또는 Model과 View의 매개체라고 생각하시면 될 것같습니다!

}
106 changes: 105 additions & 1 deletion src/test/java/racingcar/ApplicationTest.java

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
Expand Up @@ -12,6 +12,54 @@ class ApplicationTest extends NsTest {
private static final int MOVING_FORWARD = 4;
private static final int STOP = 3;

@Test
void 중복_이름_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,pobi", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 뒤_빈_이름_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 앞_빈_이름_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException(",pobi", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 가운데_빈_이름_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,,hello", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 시도_횟수_비정상_알파벳_예외_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,hello", "a"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 시도_횟수_음수_예외_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,hello", "-1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 기능_테스트() {
assertRandomNumberInRangeTest(
Expand All @@ -24,13 +72,69 @@ class ApplicationTest extends NsTest {
}

@Test
void 예외_테스트() {
void 글자_5자_초과_예외_테스트() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("pobi,javaji", "1"))
.isInstanceOf(IllegalArgumentException.class)
);
}

// 출력 테스트
@Test
void 자동차_1개_출력_테스트() {
assertRandomNumberInRangeTest(
() -> {
run("pobi", "2");
assertThat(output()).contains("pobi : --");
},
MOVING_FORWARD, STOP
);
}

@Test
void 자동차_2개_출력_테스트() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni", "1");
assertThat(output()).contains("pobi : -", "woni : ");
},
MOVING_FORWARD, STOP
);
}

@Test
void 자동차_3개_출력_테스트() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni,hehe", "3");
assertThat(output()).contains("pobi : -", "woni : ", "hehe : --");
},
MOVING_FORWARD, STOP
);
}

@Test
void 전체_출력_테스트() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni,hehe", "3");
assertThat(output()).contains("pobi : -", "woni : ", "hehe : --", "최종 우승자 : hehe");
},
MOVING_FORWARD, STOP
);
}

@Test
void 공동_우승_출력_테스트() {
assertRandomNumberInRangeTest(
() -> {
run("pobi,woni,hehe", "1");
assertThat(output()).contains("pobi : -", "woni : -", "hehe : ", "최종 우승자 : pobi, woni");
},
MOVING_FORWARD, STOP
);
}

@Override
public void runMain() {
Application.main(new String[]{});
Expand Down
54 changes: 54 additions & 0 deletions src/test/java/racingcar/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package racingcar;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.Test;

public class CarTest {

@Test
void createCarWithName() {
// 자동차는 초기 위치가 0
Car car = new Car("car1");
assertEquals(car.getLocation(), 0);
assertEquals(car.getName(), "car1");
}

@Test
void createCarWithWrongName() {
// 자동차의 이름은 5자 이하
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new Car("haeseung"));
}

@Test
void createCarWithoutName() {
// 자동차 이름이 없는 경우 오류 출력
// 없는 경우
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new Car());
// 공백
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new Car(""));
// 스페이스바
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new Car(" "));
// 탭
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new Car(" "));
}

@Test
void randomMoveCar() {
// 자동차 확률적으로 전진/정지
Car car = new Car("car1");
int preLocation = car.getLocation();
car.randomMove();
int curLocation = car.getLocation();
assertThat(curLocation).isBetween(preLocation, curLocation);
}

}