Skip to content
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
fbfab99
Git -commit feat:1주차 과제(서현진)
Sep 11, 2025
1c54f83
Git -commit feat:1주차 과제(서현진)
Sep 11, 2025
dee665e
Git -commit feat: 하드코딩 수정
Sep 12, 2025
a9af148
Git -commit feat: RaceSetting 클래스 생성
Sep 12, 2025
c6d9acc
feat: Race,RaceSetting 클래스 생성
Sep 13, 2025
560ac39
feat: RaceWinner 클래스 생성
Sep 13, 2025
aeb4ab7
feat: setMoveCnt 메소드 생성
Sep 13, 2025
dcb4ec5
feat : 전체적인 코드 수정 및 구조 변경 ( Car, Race, RaceSetting, RaceWinner)
Sep 13, 2025
c11d098
feat : RaceSetting,RaceWinner 클래스 수정
Sep 13, 2025
480f751
feat : depth 2 이상 수정
Sep 13, 2025
c0d037a
feat : README 수정
Sep 13, 2025
62b3941
feat : README 수정2
Sep 13, 2025
864e6c0
feat : mvc 패턴 적용 - Car클래스 수정
Sep 15, 2025
97b4d95
feat : mvc 패턴 적용 - 새로운 클래스 'Cars'
Sep 15, 2025
035ecdd
feat : mvc 패턴 적용 - 새로운 클래스 'InputView'
Sep 15, 2025
0de29df
feat : mvc 패턴 적용 - 새로운 클래스 'OutputView'
Sep 15, 2025
f660a28
feat : mvc 패턴 적용 - 새로운 클래스 'RacingCController'
Sep 15, 2025
7f5f429
feat : mvc 패턴 적용 - 새로운 클래스 'Application'
Sep 15, 2025
630df3a
feat : EOF 관련 수정
Sep 15, 2025
22dc5a0
feat : 첫 커밋
Sep 16, 2025
704cf09
feat : 3,4단계 구현
Sep 18, 2025
c19c73a
feat : GenerateRandom의 반환 값을 boolean으로 변경 및 car클래스에서 random 메소드 제거
Sep 18, 2025
b608f5b
feat : GenerateRandom의 반환 값을 boolean으로 변경 및 car클래스에서 random 메소드 제거 2
Sep 18, 2025
b471111
feat : cars에 moveAll을 move로 변경
Sep 18, 2025
cd70b8a
feat : new function 'isValid' in InputView
Sep 18, 2025
66e64b6
Delete src/main/java/CarMain.java
nonactress Sep 18, 2025
68814e7
Delete src/main/java/Race.java
nonactress Sep 18, 2025
4f1b377
Delete src/main/java/RaceSetting.java
nonactress Sep 18, 2025
ced1092
Delete src/main/java/RaceWinner.java
nonactress Sep 18, 2025
fd0a317
feat : generate package
Sep 19, 2025
76330fc
깃 작동 여부 확인
Sep 19, 2025
03ef929
refactor(random): Improve name of random generation logic
Sep 19, 2025
3e12240
refactor(random): making 'RacingService'and refactoring 'RacingContro…
Sep 19, 2025
cd944bc
refactor(controller): making 'RacingService'and refactoring 'RacingCo…
Sep 19, 2025
5ca9b26
refactor(model): constructor 'car' delete
Sep 19, 2025
fb563ce
refactor(model): 'isFindingFirstPizes'changes to 'checkAndAddWinner'
Sep 19, 2025
482cf13
refactor(view): method 'PrintCarPostion' rogic
Sep 19, 2025
d5ef197
Merge branch 'hyeonjin-2' of https://github.com/nonactress/java-racin…
Sep 19, 2025
ad763d8
refactor(input): method 'getCarNames' rogic
Sep 19, 2025
cab4c5f
refactor(input): renaming about Random package
Sep 20, 2025
b160676
feat : RandomMoveStrategyTest
Sep 20, 2025
2831966
feat : isValid over 2 depth
Sep 20, 2025
ada34f1
feat (CarsWinner) : new class 'CarWinner'
Sep 22, 2025
eb25bf2
feat (Test) : delete 'Racing ControllerTest'
Sep 22, 2025
c2e519f
refactor (input) : change rogic 'getCarName'
Sep 22, 2025
43190b6
refactor (model,controller) : new class 'Carwinner'2
Sep 22, 2025
f6c6e25
refactor (view) :apply 'try-catch' rogic
Sep 22, 2025
3fb578e
refactor (view) : making Random rogic
Sep 24, 2025
7d22b1d
refactor (Number) : making Package 'Number'
Sep 24, 2025
72497ef
refactor (Number) : making 'FixedNumberGenerator'
Sep 24, 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
File renamed without changes.
9 changes: 9 additions & 0 deletions src/main/java/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import controller.RacingController;

public class RacingGame {
public static void main(String[] args) {
RacingController racingController = new RacingController();
racingController.run();
}
}

20 changes: 20 additions & 0 deletions src/main/java/controller/RacingController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package controller;

import model.Cars;
import view.InputView;
import view.OutputView;

public class RacingController {
public void run() {
String[] carNames = InputView.getCarNames();
int raceCount = InputView.getRaceCount();

Cars cars = new Cars(carNames);

OutputView.printResultMessage();
RacingService racingService = new RacingService();
racingService.startRace(cars, raceCount);

OutputView.printWinners(cars.findWinners());
}
}
13 changes: 13 additions & 0 deletions src/main/java/controller/RacingService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package controller;

import model.Cars;
import view.OutputView;

public class RacingService {
public void startRace(Cars cars, int raceCount) {
for (int i = 0; i < raceCount; i++) {
cars.moveAll(); // Model의 상태 변경 요청
OutputView.printRoundResult(cars.getCars()); // View에 출력 요청
}
}
}
23 changes: 23 additions & 0 deletions src/main/java/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package model;

public class Car {

private final String name;
private int position = 0;

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

public void move() {
this.position++;
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}
}
67 changes: 67 additions & 0 deletions src/main/java/model/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import model.random.RandomMoveStrategy;
import model.random.RandomGenerator;

public class Cars {
private final List<Car> cars;
RandomGenerator generateRandom = new RandomMoveStrategy();
//model.random.GenerateRandom testGenerateRandom = new model.random.TestGenerateRandom(); //단위 테스트용 코드

public Cars(String[] carNames) {
this.cars = new ArrayList<>();
for (String name : carNames) {
cars.add(new Car(name));
}
}

public void moveAll() {
for (Car car : cars) {
move(car);
}
}

private void move(Car car) {
if (generateRandom.generate()) {
car.move();
}
// if (testGenerateRandom.generate()) {
// car.move();
// }
}

public List<String> findWinners() {
int maxPosition = findMaxPosition();
List<String> winners = new ArrayList<>();
for (Car car : cars) {
checkAndAddWinner(winners, car, maxPosition);
}
return winners;
}

private void checkAndAddWinner(List<String> winners, Car car, int maxPosition) {
if (car.getPosition() == maxPosition) {
winners.add(car.getName());
}
}

private int findMaxPosition() {
int maxPosition = 0;
for (Car car : cars) {
maxPosition = Math.max(car.getPosition(), maxPosition);
}
return maxPosition;
}

public List<Car> getCars() {
return Collections.unmodifiableList(cars);

/*return this.cars;
*위 방식을 안쓴 이유는 객체를 받아서 다른 메소드나 클래스에서
*수정이 가능해 지기 때문에 위 컬렉션 메소드로 반환값 설정
* */
}
}
13 changes: 13 additions & 0 deletions src/main/java/model/random/FixedNumberMoveStub.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package model.random;

public class FixedNumberMoveStub implements RandomGenerator {
private final int TEST_NUM = 5;

@Override
public boolean generate() {
if (TEST_NUM > MOVE_CONDITION) {
return true;
}
return false;
}
}
7 changes: 7 additions & 0 deletions src/main/java/model/random/RandomGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package model.random;

public interface RandomGenerator {
static final int MOVE_CONDITION = 4;

boolean generate();
}
16 changes: 16 additions & 0 deletions src/main/java/model/random/RandomMoveStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package model.random;

import java.util.Random;

public class RandomMoveStrategy implements RandomGenerator {
private Random random = new Random();

@Override
public boolean generate() {
if (random.nextInt(10) > RandomGenerator.MOVE_CONDITION) {
return true;
}
return false;

}
}

Choose a reason for hiding this comment

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

코드를 보니 RandomMoveStrategygenerate() 메서드가 boolean 값을 반환하도록 설계하셨는데요, 혹시 이렇게 구현하신 이유가 있을까요? random.nextInt(10)으로 생성한 숫자를 바로 반환하지 않고, 내부에서 MOVE_CONDITION과 직접 비교해서 truefalse로 만들어주신 의도가 궁금합니다!

저는 Random 관련 객체들의 역할을 "랜덤 숫자 생성"에만 딱 집중시키는 건 어떨까 제안하고 싶어요. RandomMoveStrategy는 주사위 같은 역할이라고 생각했는데 1에서 6 사이의 숫자를 사용자에게 보여주는 것까지가 주사위의 역할인 것 같아요! “주사위 숫자가 4 이상이니 전진하세요"라고 말하는 것은 주사위가 아니라, 규칙을 아는 플레이어의 역할이라고 생각합니다.

현재는 약간의 코드 중복(if문)도 있어서, 만약 판단 비즈니스 요구사항이 4보다 작은 경우로 바뀐다면 모든 전략을 수정해줘야하는 번거로움이 생길 수도 있을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

  1. RandomMoveStrategy
    리뷰어님이 말씀하신 내용처럼 위 객체를 생성할 때 return 값에 대해 많이 고민해보았습니다! 하지만 저는 위 객체를 주사위 같은 역할의 객체로 생각하기 보단 전진 가능한 조건에 초점을 맞추고 만든 객체인 만큼 플레이어한테 어떤 랜덤한 값이 나왔는지 알려줄 이유가 없다고 생각했습니다!
  2. RandomMoveStrategy의 유지보수
    위 객체는 'RandomGenerator' 인터페이스로 구현되어 있어서 안에 static final int MOVE_CONDITION = 4; 에서 정수 값만 변경 한다면 충분히 조건변경이 용이하다고 생각했습니다!!!

54 changes: 54 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package view;

import java.util.Scanner;

public class InputView {
private static final Scanner scanner = new Scanner(System.in);

public static String[] getCarNames() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String[] splitCar = scanner.nextLine().split(",");
while(true)
{
String[] splits = getStrings(splitCar);
if (splits != null) return splits;
splitCar = scanner.nextLine().split(",");

Choose a reason for hiding this comment

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

depth를 논의해보기 전에 먼저

  1. 스페이스 + 쉼표의 경우 에러 메시지 출력 O
    image

  2. 쉼표 + 쉼표의 경우 에러 메시지 출력 X
    image

<2번 상황 원인>
Java의 기본 split() 메서드는 결과 배열의 맨 뒤에 오는 모든 공백 문자열을 자동으로 제거해버립니다.

  • "a,b,,".split(",")["a", "b"] (길이 2)
  • ",,,".split(",")[] (길이가 0인 빈 배열)
    따라서 ,,,를 입력하면, carNames는 아무 요소도 없는, length0인 배열이 됩니다.
String[] carNames = scanner.nextLine().split(",", -1);

→ 이렇게 하면 해결 가능하다고 하네요!

Copy link
Author

Choose a reason for hiding this comment

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

메소드를 만들고 입력값으로 ,,,을 테스트를 안해봤었네요 ㅠㅠ
감사합니다!!

}

}

private static String[] getStrings(String[] splitCar) {
if(isValid(splitCar)){
return splitCar;
}
return null;
}

public static int getRaceCount() {
System.out.println("시도할 회수는 몇회인가요?");
int count = scanner.nextInt();
scanner.nextLine(); // 개행 문자 제거
return count;
}

public static boolean isValid(String [] cars){
if(cars.length == 1 ){
System.out.println("한 개 이상의 자동차를 입력해주세요!");

Choose a reason for hiding this comment

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

➕ 현재는 메시지를 콘솔에 출력하고 있는데 자바 예외처리 문법을 학습해보시면 좋을 것 같습니다.

if (cars.length <= 1) {
        // boolean을 반환하는 대신, 예외를 생성하고 던집니다.
        throw new IllegalArgumentException("[ERROR] 두 개 이상의 자동차를 입력해주세요!");
    }
while (true) {
        try {
            System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
            String[] carNames = scanner.nextLine().split(",", -1);

            // 예외가 발생할 수 있는 검증 메서드를 호출
            validateCarNames(carNames);
						
						... // 이 외 로직
        } catch (IllegalArgumentException e) {
            // validateCarNames에서 예외를 던지면 이 코드가 실행됨
            // 예외에 담긴 메시지를 출력하고, while 루프의 처음으로 돌아감
            System.out.println(e.getMessage());
        }
    }

Copy link
Author

Choose a reason for hiding this comment

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

[f6c6e25]
적용해봤습니다!

Copy link
Author

Choose a reason for hiding this comment

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

throw 문법은 어떻게 적용할지 잘 모르겠어서 더 공부해보겠습니다!

return false;
}

for (String car : cars) {
if (isEmpty(car)) return false;
}
return true;
}

private static boolean isEmpty(String car) {
if(car.trim().isEmpty())
{
System.out.println("자동차의 이름이 공백일 순 없습니다!");
return true;
}
return false;
}
}
30 changes: 30 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package view;

import model.Car;

import java.util.List;

public class OutputView {

public static void printResultMessage() {
System.out.println("\n실행 결과");
}

public static void printRoundResult(List<Car> cars) {
for (Car car : cars) {
System.out.print(car.getName() + " : ");
printCarPosition(car);
System.out.println();
}
System.out.println();
}

private static void printCarPosition(Car car) {
System.out.print("-".repeat(car.getPosition()));
}

public static void printWinners(List<String> winners) {
String winnerNames = String.join(", ", winners);
System.out.println("최종 우승자 : " + winnerNames);
}
}
4 changes: 4 additions & 0 deletions src/test/java/controller/RacingControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package controller;

public class RacingControllerTest {

Choose a reason for hiding this comment

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

내용 없는 파일은 지워주셔도 될 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

넵 수정하겠습니다!

}
51 changes: 51 additions & 0 deletions src/test/java/model/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package model;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CarTest {

@Test
@DisplayName("자동차 생성 시 이름과 초기 위치(0)를 올바르게 설정한다.")
void createCar() {
// given: "pobi"라는 이름을 가진 자동차를 준비하고
String carName = "pobi";

// when: 해당 이름으로 자동차를 생성하면
Car car = new Car(carName);

// then: 이름은 "pobi"이고 위치는 0이어야 한다.
assertEquals(carName, car.getName());
assertEquals(0, car.getPosition());
}

@Test
@DisplayName("move 메서드를 호출하면 위치가 1 증가한다.")
void moveCar() {
// given: "woni"라는 이름의 자동차를 생성하고
Car car = new Car("woni");

// when: move 메서드를 한 번 호출하면
car.move();

// then: 자동차의 위치는 1이 되어야 한다.
assertEquals(1, car.getPosition());
}

@Test
@DisplayName("move 메서드를 여러 번 호출하면 호출한 횟수만큼 위치가 증가한다.")
void moveCarMultipleTimes() {
// given: "jun"이라는 이름의 자동차를 생성하고
Car car = new Car("jun");
int moveCount = 5;

// when: move 메서드를 5번 호출하면
for (int i = 0; i < moveCount; i++) {
car.move();
}

// then: 자동차의 위치는 5가 되어야 한다.
assertEquals(moveCount, car.getPosition());
}
}

Choose a reason for hiding this comment

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

give-when-then으로 테스트 잘 작성해주셨네요 👍
@DisplayName 대신 한국어로 테스트 이름을 바로 지을 수도 있어요!

void move_메서드를_호출하면_위치가_1_증가한다() 

20 changes: 20 additions & 0 deletions src/test/java/model/random/RandomMoveStrategyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package model.random;

import model.random.*;
import model.Car;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class RandomMoveStrategyTest {

@Test
@DisplayName("값이 랜덤한지 확인")
void checkRandom (){
// given: "pobi"라는 이름을 가진 자동차를 준비하고
String carName = "pobi";
FixedNumberMoveStub fixedNumberMoveStub = new FixedNumberMoveStub();

Choose a reason for hiding this comment

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

이 테스트가 어떤 걸 테스트하고 싶은지 잘 모르겠습니다! 실제로 랜덤한 값이 나오는 것을 테스트하고 싶으신 건가요?? 만약 그렇다면, 그건 저희가 직접 테스트하기 아주 어려운 영역이라고 생각합니다. '진짜 랜덤'은 테스트를 실행할 때마다 결과가 계속 바뀌기 때문에, 결과를 예측하고 검증하는 자동화된 테스트를 만들기 거의 불가능하기 때문입니다. 이건 저희 코드의 로직보다는 Java의 Random 클래스 자체의 기능을 테스트하는 셈이라 테스트하지 않아도 되는 영역 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

아아 위 코드를 작성한 이유는 만약에 제가 RandomGenerator를 만들지 않았다면 랜덤한 값이 나오는 메소드인지 확인해보지 않을까?? 하는 생각에서 작성한 코드 였습니다!
만약 지금은 프로젝트의 단위가 작지만 커진다면 위와 같이 자신이 작성하지 않은 코드를 사용할 때 랜덤값이 아니여도 확인해야하는 경우도 있는지 궁금합니다!

Choose a reason for hiding this comment

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

결론부터 말씀드리면, 프로젝트 규모가 아무리 커져도 java.util.Random과 같은 외부 표준 라이브러리가 정말 랜덤한 값을 생성하는지 직접 테스트하는 경우는 거의 없는 것 같습니다.

이유를 크게 두 가지로 생각해봤는데요,

  1. 신뢰와 책임의 범위
    Java의 표준 라이브러리처럼 엄청 많은 개발자가 사용하는 도구는 이미 오랜 시간에 걸쳐 검증되었다고 생각합니다! 만약 저희가 자동차 회사라고 생각해봤을 때, 저희의 책임은 타이어나 이외의 자동차 부품들을 검증하는 것이 아니라, 이 부품들을 가져와서 우리가 만든 자동차가 잘 달리게 하는 것이 아닐까요? 그래서 저희는 직접 작성한 코드의 로직을 테스트하는 데 집중하는 것이 더 효율적이라고 생각합니다.
  2. 테스트의 목적
    저희가 진짜 테스트하고 싶은 것은 랜덤 값 그 자체가 아니라, '랜덤 값을 사용하는 우리 코드가 예상대로 잘 동작하는가' 일 것 같아요!

저희는 외부 라이브러리의 기능 자체를 검증하기보다는 '그 라이브러리를 사용하는 우리 코드가 생각대로 잘 동작하는가'에 집중하는 것이 일반적인 테스트 전략인 것 같아요!

덕분에 테스트의 역할과 범위에 대해 다시 한 번 생각해볼 수 있었네요. 좋은 질문 감사합니다! 😊

}

}