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

[자동차 경주] 이동훈 미션 제출합니다. #76

Merged
merged 24 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5857b3e
feat. 자동차 클래스 구현
dh2906 Feb 3, 2025
6cc9ddb
feat. 자동차 단위 테스트 구현
dh2906 Feb 3, 2025
26708a2
feat. 자동차 경주 컨트롤러 구현
dh2906 Feb 3, 2025
963e2ef
feat. 자동차 리스트 게터 생성
dh2906 Feb 3, 2025
a7a4c24
feat. 자동차 경주 컨트롤러 단위 테스트 구현
dh2906 Feb 3, 2025
25b1a0e
feat. 테스트 메소드마다 중복되는 부분 beforeEach로 제거
dh2906 Feb 3, 2025
102d013
이름과 시도횟수 입력받는 컨트롤러, 그와 관련된 유틸 클래스 구현
dh2906 Feb 9, 2025
682c30a
refactory. 입력 컨트롤러의 입력 메소드 분할
dh2906 Feb 9, 2025
fba631e
feat. 출력 컨트롤러 구현
dh2906 Feb 9, 2025
4e45f28
chore. 클래스 명 수정
dh2906 Feb 9, 2025
c5b9d31
feat. 출력 기능 추가
dh2906 Feb 9, 2025
ff8481f
chore. 리스트 변수명 변경
dh2906 Feb 9, 2025
719ee11
chore. 출력문에 의미없는 개행문자 제거
dh2906 Feb 9, 2025
7eaee37
refactory. 구분자를 기준으로 공백도 포함해서 나누도록 수정
dh2906 Feb 9, 2025
e96b905
feat. 메인 클래스 구현
dh2906 Feb 9, 2025
2921327
refactory. 입력에 대한 예외 처리
dh2906 Feb 9, 2025
bacde73
refactory. 우승자 출력 기능을 메소드로 분할
dh2906 Feb 9, 2025
710a5a9
chore. 공동 우승자 이름 사이에 공백이 있도록 수정
dh2906 Feb 9, 2025
7dd4136
refactory. throws문 위치 변경
dh2906 Feb 9, 2025
b9b6084
chore. 클래스 패키지화
dh2906 Feb 9, 2025
9bc35c8
refactory. 입력 예외 발생 시 던지는 예외 종류 수정
dh2906 Feb 9, 2025
9c05c88
refactory. 공동 우승자 테스트 로직에 전진 추가
dh2906 Feb 9, 2025
14fc87e
feat. 입력 값에 대한 검증 단위 테스트 구현
dh2906 Feb 9, 2025
6a2af95
fix. 공동 우승자 테스트 메소드 오류 수정
dh2906 Feb 9, 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
21 changes: 21 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import model.Car;
import view.InputController;

import java.util.List;

public class Application {
public static void main(String[] args) {
InputController inputController = new InputController();
RacingGame racingGame = new RacingGame();

List<String> nameList = inputController.inputNames();
int times = inputController.inputTimes();

nameList.forEach((name) -> {
racingGame.join(new Car(name));
});

racingGame.race(times);
racingGame.printWinners();
}
}
46 changes: 46 additions & 0 deletions src/main/java/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import model.Car;
import view.OutputController;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

public class RacingGame {

Choose a reason for hiding this comment

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

RacingGame은 Model에 속한다고 생각되는데 동훈님은 어떻게 생각하시나요?

private final OutputController outputController = new OutputController();
private final List<Car> carList = new ArrayList<>();
private final Random random = new Random();

public void join(Car player) {
carList.add(player);
}

public void race(int times) {
while (times != 0) {
carList.forEach(
car -> car.move(random.nextInt(10))
);

outputController.printProgressResult(carList);

times--;
}
}
Comment on lines +18 to +28

Choose a reason for hiding this comment

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

하나의 함수에서 출력까지 담당하고 있는데, controller를 통해서 레이스의 결과를 출력 함수를 호출 하도록 하는건 어떨까요?


public void printWinners() {
List<Car> winners = getWinners();
outputController.printWinners(winners);
}

public List<Car> getWinners() {
int maxPos = carList.stream().mapToInt(Car::getPos).max().orElse(Integer.MIN_VALUE);

Choose a reason for hiding this comment

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

스트림을 잘 활용하셨네요!


return carList.stream().filter(
car -> car.getPos() == maxPos
).collect(Collectors.toList());
}

public List<Car> getCarList() {
return carList;
}
}
24 changes: 24 additions & 0 deletions src/main/java/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package model;

public class Car {
private final String name;
private int pos;

Choose a reason for hiding this comment

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

변수명을 지을 때 약어보다 풀로 써주는 것이 변수의 역할을 쉽게 알 수 있어서 좋다고 생각해요.
반대로 이름이 길어져서 풀네임이 되려 가독성을 해치는 경우라면, 약어 사용이 더 적절한 경우도 있습니다!


public Car(String name) {
this.name = name;
this.pos = 0;
}

public void move(int num) {
if (num >= 4)
Copy link

@Choon0414 Choon0414 Feb 16, 2025

Choose a reason for hiding this comment

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

4가 무엇을 의미하는 숫자인지 상수로 표현해보는 것도 괜찮을 것 같아요

pos++;
}

public String getName() {
return name;
}

public int getPos() {
return pos;
}
}
10 changes: 10 additions & 0 deletions src/main/java/util/NameParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package util;

import java.util.Arrays;
import java.util.List;

public class NameParser {
public List<String> parseName(String str) {
return Arrays.stream(str.split(",", -1)).toList();
}
}
39 changes: 39 additions & 0 deletions src/main/java/util/Validation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package util;

public class Validation {

Choose a reason for hiding this comment

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

유효성 검사를 하나의 validation클래스로 관리하도록 했군요!

이렇게 했을때의 장점과 단점은 어떤것이 있을까요?


public void validName(String name) throws RuntimeException {

Choose a reason for hiding this comment

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

하나의 검증 내에서도 종류별로 분리하셨군요👍👍

validNameIsNullOrBlank(name);
validNameSize(name);
}

public void validTimes(String strTimes) throws RuntimeException {
validParseTimes(strTimes);
validTimesRange(Integer.parseInt(strTimes));

}

public void validNameIsNullOrBlank(String name) {
if (name == null || name.isBlank())
throw new IllegalArgumentException("이름에 공백이 포함되었습니다.");
}

public void validNameSize(String name) {
if (name.length() > 5)
throw new IllegalArgumentException("이름의 길이가 5자를 넘었습니다.");
}

public void validParseTimes(String strTimes) {
try {
Integer.parseInt(strTimes);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("정수가 아닌 값이 입력되었습니다.");
}
}

public void validTimesRange(int times) {
if (times < 0) {
throw new IllegalArgumentException("시도 횟수에 음수가 입력되었습니다.");
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/view/InputController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package view;

import util.NameParser;
import util.Validation;

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

public class InputController {

Choose a reason for hiding this comment

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

View의 역할과 Controller의 역할이 공존하는 것 같습니다!
view패키지로 나눈 만큼 view와 controller로 관련된 역할을 분리해 보는건 어떨까요?

Scanner sc = new Scanner(System.in);
NameParser nameParser = new NameParser();
Validation validation = new Validation();
String strNames;
String strTimes;
int times;
List<String> nameList = new ArrayList<>();

Choose a reason for hiding this comment

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

접근제어자를 default로 두신 이유가 궁금합니다!


public List<String> inputNames() {
System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).");
strNames = sc.nextLine();

nameList = nameParser.parseName(strNames);

try {
nameList.forEach(
(name) -> validation.validName(name)
);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
System.exit(1);
}

return nameList;
}

public int inputTimes() {
System.out.println("시도할 회수는 몇회인가요?");
strTimes = sc.nextLine();

try {
validation.validTimes(strTimes);
} catch(IllegalArgumentException e) {
System.out.println(e.getMessage());
System.exit(1);
}

times = Integer.parseInt(strTimes);

return times;
}
}
24 changes: 24 additions & 0 deletions src/main/java/view/OutputController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package view;

import model.Car;

import java.util.List;
import java.util.stream.Collectors;

public class OutputController {
public void printProgressResult(List<Car> carList) {
for (Car car : carList) {
System.out.println(car.getName() + " : " + "-".repeat(car.getPos()));

Choose a reason for hiding this comment

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

String.format을 이용하면 문자열 결합 비용을 줄일 수 있답니.
사소하지만 습관을 들여두면 좋을 것 같아요👍�

}

System.out.println();
}

public void printWinners(List<Car> winnerList) {
String winnerNames = winnerList.stream()
.map(Car::getName)
.collect(Collectors.joining(", "));

System.out.println(winnerNames + "가 최종 우승했습니다.");
}
}
38 changes: 38 additions & 0 deletions src/test/java/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import model.Car;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

@DisplayName("움직이는 자동차 테스트 클래스")
public class CarTest {
private Car car;

@BeforeEach
public void beforeEach() {
car = new Car("Kim");
}
@Test
@DisplayName("자동차 객체 생성이 제대로 되었는가")
public void testCreateCar() {
assertThat(car.getName()).isEqualTo("Kim");
assertThat(car.getPos()).isEqualTo(0);
}

@Test
@DisplayName("자동차가 정상적으로 전진하는가")
public void testMoveCar() {
car.move(4);

assertThat(car.getPos()).isEqualTo(1);
}

@Test
@DisplayName("자동차가 정상적으로 정지하는가")
public void testStopCar() {
car.move(3);

assertThat(car.getPos()).isEqualTo(0);
}
}
51 changes: 51 additions & 0 deletions src/test/java/RacingGameTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import model.Car;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

@DisplayName("자동차 경주 관련 테스트 클래스")
public class RacingGameTest {
private RacingGame gc;
private Car car1, car2, car3;

@BeforeEach
public void beforeEach() {
gc = new RacingGame();

car1 = new Car("Kim");
car2 = new Car("Lee");
car3 = new Car("Park");

gc.join(car1);
gc.join(car2);
gc.join(car3);
}

@Test
@DisplayName("자동차 참여가 정상적으로 되는가")
public void testCarJoin() {
assertThat(gc.getCarList()).hasSize(3);
}

@Test
@DisplayName("단독 우승자가 정상적으로 구해지는가")
public void testGetSoloWinners() {
gc.getCarList().get(1).move(4);

assertThat(gc.getWinners())
.extracting(Car::getName)
.containsExactly("Lee");
}

@Test
@DisplayName("공동 우승자가 정상적으로 구해지는가")
public void testGetSameWinners() {
car1.move(9);
car3.move(9);

assertThat(gc.getWinners())
.containsExactlyInAnyOrder(car1, car3);
}
}
83 changes: 83 additions & 0 deletions src/test/java/ValidationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import util.NameParser;
import util.Validation;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

@DisplayName("입력한 값을 검증하는 기능 테스트")
public class ValidationTest {
private Validation validation;
private NameParser nameParser;

@BeforeEach
public void beforeEach() {
validation = new Validation();
nameParser = new NameParser();
}

@DisplayName("입력한 이름의 파싱이 제대로 되는가")
@Test
public void testNameParse() {
String str = "neo,brie,brown";

List<String> nameList = nameParser.parseName(str);

assertThat(nameList).containsExactly("neo", "brie", "brown");

}

@DisplayName("이름들이 정상적으로 입력된 경우 예외를 발생하지 않는가")
@Test
public void testNameCorrectFormat() {
String str = "neo,brie,brown";

List<String> nameList = nameParser.parseName(str);

Assertions.assertDoesNotThrow(() -> {
nameList.forEach((name) -> {
validation.validName(name);
});
}
);
}

@DisplayName("이름들이 비정상적으로 입력된 경우 예외를 발생하지 않는가")
@ParameterizedTest
@ValueSource(strings = {"", ",", ",neo", "neo,"})
public void testNameIncorrectFormat(String str) {
List<String> nameList = nameParser.parseName(str);

Assertions.assertThrows(IllegalArgumentException.class, () -> {
nameList.forEach((name) -> {
validation.validName(name);
});
}
);
}

@DisplayName("시도 횟수가 정상적으로 입력된 경우 예외를 발생시키지 않는가")
@Test
public void testTimesCorrectFormat() {
String strTimes = "5";

Assertions.assertDoesNotThrow(() -> {
validation.validTimes(strTimes);
});
}

@DisplayName("시도 횟수가 비정상적으로 입력된 경우 예외를 발생시키지 않는가")
@ParameterizedTest
@ValueSource(strings = { "-5", "asdf", "!!!", "" })
public void testTimesInCorrectFormat(String strTimes) {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
validation.validTimes(strTimes);
});
}
}