Repository: next-step/java-racingcar
Pull request #5963: 3단계 - 자동차 경주
Author: login: gan-ta
Labels: name: cc-java-20
Created: 2025-03-15T08:36:37Z | Merged: 2025-03-17T01:38:02Z

3단계 리뷰도 잘 부탁 드립니다. 🙇

다음과 같은 부분을 생각하면서 구현했습니다.
- 이번 미션의 요구사항대로 모든 로직에 단위 테스트를 구현하는 것을 목표로 코드 구성을 했습니다.
- mock을 이용해 random한 값을 제어하면서 테스트 경우를 생각해 구현했습니다.
- stream, lamda, collection은 사용하지 말고 구현해봤습니다.
- getter, setter는 구현하지 않도록 했습니다.
- 제일 고민이 됐던 부분은 접근 제어자 부분입니다,
CarRacingGameExecutor 클래스가 EntryPoint이고 하위에 사용되는 클래스는 용도별로 디렉토리 구분을 했는데요
사실 외부로 사용되는 인터페이스는 CarRacingGameExecutor 클래스 정도이고 나머지 클래스는 외부에서 접근이 안되도록 구성을 하고 싶은데 디렉토리 구분을 하다 보니 결국 public으로 하는 부분이 고민이 됐습니다.
그렇다고 default 접근 제어자로 생각하면서 모든 클래스를  CarRacingGameExecutor 클래스가 있는 디렉토리에 하는 것도 안좋지 않나 생각이 들어 우선은 PR처럼 구분을 했습니다.
CarRacingGameSimulator의 tryMoveCar 메서드도(이 부분은 수업때 배운대로 로직이 2 depth로 구현되지 않게 구분을 해봤습니다.) 사실 private 메서드여야 생각이 드는데 이번 미션이 "모든 로직에 단위 테스트를 구현"이라 default 접근 제어자로 풀었고 이 부분도 완전 좋은 방법이 아니라고 생각은 들지만 구현했고 리뷰 드립니다.


Diff:
diff --git a/README.md b/README.md
index c550c4c2a09..36620b86367 100644
--- a/README.md
+++ b/README.md
@@ -6,4 +6,27 @@
 * 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.
 
 ## 온라인 코드 리뷰 과정
-* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
\ No newline at end of file
+* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
+
+## 요구사항 정리
+### Step2
+- 빈 문자열이 입력되면 0을 반환한다.
+- null이 입력되면 0을 반환한다.
+- 구분자가 없이 숫자 하나이면 그대로 반환한다.
+- "," 로 구분자로 가지는 문자열을 split해서 수를 더한다.
+- ":" 로 구분자로 가지는 문자열을 split해서 수를 더한다.
+- 커스컴 구분자를 입력받아 구분해서 수를 더한다. (ex. //;\n1;2;3” 형식)
+- 커스텀 구분자를 입력받은 형태가 아닌데 "," 혹은 ":" 구분자가 아닌 경우는  RuntimeException을 던진다.
+- 음수를 전달하면 RuntimeException을 던진다.
+- 커스컴 구분자를 입력받아 나눴는데 나눈 결과에 숫자 이외 문자가 들어 있으면  RuntimeException을 던진다.
+
+### Step3
+- 자동차 대수를 입력 받아야 한다.
+- 자동차 대수를 입력 받을 때 0, 1, 음수일 경우 RuntimeException을 던진다.
+- 주어진 횟수를 입력 받아야 한다.
+- 주어진 횟수를 입력 받을 때 0 또는 음수일 경우 RuntimeException을 던진다.
+- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈춘다.
+- 전진하는 조건은 Random한 확률로 움직이고 0~9까지의 수를 뽑아 4이상이면 움직인다.
+- 자동차의 상태를 주어진 횟수만큼 출력한다.
+- 한 객체에서 시뮬레이션을 여러번 수행했을 때 새로운 대회 환경에서 경주를 시작하도록 작동한다.
+- 자동차의 처음 위치는 0이다.
diff --git a/src/main/java/calculator/DelimitedStringCalculator.java b/src/main/java/calculator/DelimitedStringCalculator.java
index ae4a84fbb55..55d2994d8a7 100644
--- a/src/main/java/calculator/DelimitedStringCalculator.java
+++ b/src/main/java/calculator/DelimitedStringCalculator.java
@@ -1,7 +1,7 @@
 package calculator;
 
-import java.util.regex.Pattern;
 import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class DelimitedStringCalculator {
 
@@ -9,7 +9,7 @@ public class DelimitedStringCalculator {
   private static final String DEFAULT_DELIMITER_REGEX = "^[0-9,:]+$";
 
   private DelimitedStringCalculator() {
-
+    throw new UnsupportedOperationException("DelimitedStringCalculator 클래스는 생성자를 지원하지 않습니다.");
   }
 
   public static int sum(String input) {
@@ -35,7 +35,7 @@ private static boolean isNotValidString(String input) {
   private static boolean hasCustomDelimiter(String input) {
     Matcher matcher = Pattern.compile(CUSTOM_DELIMITER_REGEX).matcher(input);
 
-    return  matcher.matches();
+    return matcher.matches();
   }
 
   private static int sumNonNegativeNumberWithCustomDelimiter(String input) {
@@ -56,9 +56,9 @@ private static int sumNonNegativeNumberWithCustomDelimiter(String input) {
     }
 
     throw new IllegalArgumentException(String.format(
-            "커스텀 구분자가 들어가있는 문자열 형태가 아닙니다. - %s",
-            input
-        ));
+        "커스텀 구분자가 들어가있는 문자열 형태가 아닙니다. - %s",
+        input
+    ));
   }
 
   private static boolean hasDefaultDelimiter(String input) {
@@ -109,7 +109,7 @@ private static int[] convertStringsToNumbers(String[] values) {
 
   private static int convertStringToNumber(String value) {
     try {
-       return Integer.parseInt(value);
+      return Integer.parseInt(value);
     } catch (NumberFormatException e) {
       throw new IllegalArgumentException(String.format(
           "유효하지 않는 숫자 형식의 문자가 문자열에 있습니다. - %s ",
diff --git a/src/main/java/racing/CarRacingGameExecutor.java b/src/main/java/racing/CarRacingGameExecutor.java
new file mode 100644
index 00000000000..1bb71667593
--- /dev/null
+++ b/src/main/java/racing/CarRacingGameExecutor.java
@@ -0,0 +1,42 @@
+package racing;
+
+import racing.simulator.CarRacingGameSimulator;
+import racing.simulator.RandomCarMovingStrategy;
+import racing.types.CarCount;
+import racing.types.SimulateCount;
+import racing.util.InputView;
+import racing.util.OutputView;
+
+import java.util.List;
+
+public class CarRacingGameExecutor {
+
+  public static void main(String[] args) {
+    CarCount carCount = getCarCount();
+    SimulateCount simulateCount = getSimulateCount();
+
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(carCount, new RandomCarMovingStrategy());
+
+    OutputView.showWhitespace();
+    OutputView.showExecutionResultText();
+    printSimulationResult(simulator.run(simulateCount));
+
+  }
+
+  private static CarCount getCarCount() {
+    int carCount = InputView.showCarCountInput();
+    return new CarCount(carCount);
+  }
+
+  private static SimulateCount getSimulateCount() {
+    int simulateCount = InputView.showSimulateCountInput();
+    return new SimulateCount(simulateCount);
+  }
+
+  private static void printSimulationResult(List<List<Integer>> simulationResults) {
+    simulationResults.forEach(roundResult -> {
+      OutputView.showLocation(roundResult);
+      OutputView.showWhitespace();
+    });
+  }
+}
diff --git a/src/main/java/racing/simulator/Car.java b/src/main/java/racing/simulator/Car.java
new file mode 100644
index 00000000000..0606a53e752
--- /dev/null
+++ b/src/main/java/racing/simulator/Car.java
@@ -0,0 +1,31 @@
+package racing.simulator;
+
+public class Car {
+
+  private int location;
+  private final CarMovingStrategy carMovingStrategy;
+
+  public Car(CarMovingStrategy carMovingStrategy) {
+    this.location = 0;
+    this.carMovingStrategy = carMovingStrategy;
+  }
+
+  public Car(Car car) {
+    this.location = car.location;
+    this.carMovingStrategy = car.carMovingStrategy;
+  }
+
+  public int getLocation() {
+    return location;
+  }
+
+  public void go() {
+    if (carMovingStrategy.canMove()) {
+      this.location += 1;
+    }
+  }
+
+  public void reset() {
+    location = 0;
+  }
+}
diff --git a/src/main/java/racing/simulator/CarMovingStrategy.java b/src/main/java/racing/simulator/CarMovingStrategy.java
new file mode 100644
index 00000000000..f1d9336c6a1
--- /dev/null
+++ b/src/main/java/racing/simulator/CarMovingStrategy.java
@@ -0,0 +1,5 @@
+package racing.simulator;
+
+public interface CarMovingStrategy {
+  boolean canMove();
+}
diff --git a/src/main/java/racing/simulator/CarRacingGameSimulator.java b/src/main/java/racing/simulator/CarRacingGameSimulator.java
new file mode 100644
index 00000000000..caddfbff091
--- /dev/null
+++ b/src/main/java/racing/simulator/CarRacingGameSimulator.java
@@ -0,0 +1,51 @@
+package racing.simulator;
+
+import racing.types.CarCount;
+import racing.types.SimulateCount;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CarRacingGameSimulator {
+
+  private final List<Car> cars = new ArrayList<>();
+
+  public CarRacingGameSimulator(CarCount carCount, CarMovingStrategy carMovingStrategy) {
+    for (int i = 0; i < carCount.getCount(); i++) {
+      cars.add(new Car(carMovingStrategy));
+    }
+  }
+
+  public List<List<Integer>> run(SimulateCount simulateCount) {
+    List<List<Integer>> res = new ArrayList<>();
+    for (int i = 0; i < simulateCount.getCount(); i++) {
+      moveCars();
+      res.add(getCarLocations());
+    }
+    resetCars();
+    return res;
+  }
+
+  public List<Integer> getCarLocations() {
+    return cars.stream().map(Car::getLocation).collect(Collectors.toList());
+  }
+
+  public void moveCars() {
+    cars.forEach(Car::go);
+  }
+
+  public List<Car> copyCars() {
+    List<Car> res = new ArrayList<>();
+    for (Car car : cars) {
+      res.add(new Car(car));
+    }
+    return res;
+  }
+
+  public void resetCars() {
+    for (Car car : cars) {
+      car.reset();
+    }
+  }
+}
diff --git a/src/main/java/racing/simulator/RandomCarMovingStrategy.java b/src/main/java/racing/simulator/RandomCarMovingStrategy.java
new file mode 100644
index 00000000000..7018b1749ac
--- /dev/null
+++ b/src/main/java/racing/simulator/RandomCarMovingStrategy.java
@@ -0,0 +1,22 @@
+package racing.simulator;
+
+import java.util.Random;
+
+public class RandomCarMovingStrategy implements CarMovingStrategy {
+
+  private final Random random;
+  private static final int MOVE_THRESHOLD = 4;
+  private static final int RANDOM_RANGE_LIMIT = 10;
+
+  public RandomCarMovingStrategy() {
+    this.random = new Random();
+  }
+
+  public RandomCarMovingStrategy(Random random) {
+    this.random = random;
+  }
+
+  public boolean canMove() {
+    return random.nextInt(RANDOM_RANGE_LIMIT) >= MOVE_THRESHOLD;
+  }
+}
diff --git a/src/main/java/racing/types/CarCount.java b/src/main/java/racing/types/CarCount.java
new file mode 100644
index 00000000000..3e76a5309bb
--- /dev/null
+++ b/src/main/java/racing/types/CarCount.java
@@ -0,0 +1,19 @@
+package racing.types;
+
+public class CarCount {
+
+  private static final int MIN_CAR_COUNT = 2;
+
+  private final int count;
+
+  public CarCount(int carCount) {
+    if (carCount < MIN_CAR_COUNT) {
+      throw new IllegalArgumentException("자동차 수는 최소 " + MIN_CAR_COUNT + "대여야 합니다.");
+    }
+    this.count = carCount;
+  }
+
+  public int getCount() {
+    return count;
+  }
+}
diff --git a/src/main/java/racing/types/SimulateCount.java b/src/main/java/racing/types/SimulateCount.java
new file mode 100644
index 00000000000..23150675832
--- /dev/null
+++ b/src/main/java/racing/types/SimulateCount.java
@@ -0,0 +1,19 @@
+package racing.types;
+
+public class SimulateCount {
+
+  private static final int MIN_SIMULATE_COUNT = 1;
+
+  private final int count;
+
+  public SimulateCount(int simulateCount) {
+    if (simulateCount < MIN_SIMULATE_COUNT) {
+      throw new IllegalArgumentException("시뮬레이션 횟수는 최소 " + MIN_SIMULATE_COUNT + "회여야 합니다.");
+    }
+    this.count = simulateCount;
+  }
+
+  public int getCount() {
+    return count;
+  }
+}
diff --git a/src/main/java/racing/util/InputView.java b/src/main/java/racing/util/InputView.java
new file mode 100644
index 00000000000..8ef6d4029d0
--- /dev/null
+++ b/src/main/java/racing/util/InputView.java
@@ -0,0 +1,18 @@
+package racing.util;
+
+import java.util.Scanner;
+
+public class InputView {
+
+  private static final Scanner scanner = new Scanner(System.in);
+
+  public static int showCarCountInput() {
+    System.out.println("자동차 대수는 몇 대 인가요?");
+    return scanner.nextInt();
+  }
+
+  public static int showSimulateCountInput() {
+    System.out.println("시도할 회수는 몇 회 인가요?");
+    return scanner.nextInt();
+  }
+}
diff --git a/src/main/java/racing/util/OutputView.java b/src/main/java/racing/util/OutputView.java
new file mode 100644
index 00000000000..2ae77753c62
--- /dev/null
+++ b/src/main/java/racing/util/OutputView.java
@@ -0,0 +1,18 @@
+package racing.util;
+
+import java.util.List;
+
+public class OutputView {
+
+  public static void showExecutionResultText() {
+    System.out.println("실행 결과");
+  }
+
+  public static void showLocation(List<Integer> locations) {
+    locations.forEach(location -> System.out.println("-".repeat(Math.max(0, location))));
+  }
+
+  public static void showWhitespace() {
+    System.out.println();
+  }
+}
diff --git a/src/test/java/calculator/DelimitedStringCalculatorTest.java b/src/test/java/calculator/DelimitedStringCalculatorTest.java
index 71454377a22..0a37ee41b86 100644
--- a/src/test/java/calculator/DelimitedStringCalculatorTest.java
+++ b/src/test/java/calculator/DelimitedStringCalculatorTest.java
@@ -5,6 +5,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
 
 public class DelimitedStringCalculatorTest {
 
@@ -20,7 +21,7 @@ void sum_emptyString_returnsZero() {
 
   @DisplayName("null이 주어졌을 때 sum()을 호출하면 0을 반환한다.")
   @Test
-  void sum_null_returnZero(){
+  void sum_null_returnZero() {
     String input = null;
 
     int result = DelimitedStringCalculator.sum(input);
@@ -67,8 +68,10 @@ void sum_customDelimiterString_returnsSumOfNumbers() {
     int result1 = DelimitedStringCalculator.sum(input1);
     int result2 = DelimitedStringCalculator.sum(input2);
 
-    assertThat(result1).isEqualTo(6);
-    assertThat(result2).isEqualTo(7);
+    assertAll(
+        () -> assertThat(result1).isEqualTo(6),
+        () -> assertThat(result2).isEqualTo(7)
+    );
   }
 
 
diff --git a/src/test/java/racing/simulator/CarRacingGameSimulatorTest.java b/src/test/java/racing/simulator/CarRacingGameSimulatorTest.java
new file mode 100644
index 00000000000..48cf8edba5b
--- /dev/null
+++ b/src/test/java/racing/simulator/CarRacingGameSimulatorTest.java
@@ -0,0 +1,104 @@
+package racing.simulator;
+
+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 racing.types.CarCount;
+import racing.types.SimulateCount;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+class CarRacingGameSimulatorTest {
+
+  @DisplayName("시뮬레이션을 수행하면 simulateCount 만큼의 결과를 담은 리스트를 반환하고, 종료 후 자동차를 리셋한다.")
+  @Test
+  void run_givenSimulateCount_returnLocationResult() {
+    int carCount = 3;
+    int simulateCount = 4;
+
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(new CarCount(carCount), () -> true);
+
+    List<List<Integer>> simulateResults = Objects.requireNonNull(simulator.run(new SimulateCount(simulateCount)));
+    List<Car> currentCars = simulator.copyCars();
+
+    assertAll(
+        () -> assertThat(simulateResults).hasSize(simulateCount),
+        () -> IntStream.range(0, simulateResults.size())
+            .forEach(i -> assertThat(simulateResults.get(i)).containsOnly(i + 1)),
+        () -> assertThat(currentCars)
+            .extracting(Car::getLocation)
+            .containsOnly(0)
+    );
+  }
+
+  @DisplayName("각 자동차의 현재 위치를 정수 형태로 반환한다.")
+  @Test
+  void getCarLocations_returnCurrentLocations() {
+    int carCount = 3;
+    int simulateMoves = 5;
+
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(new CarCount(carCount), () -> true);
+
+    IntStream.range(0, simulateMoves).forEach(i -> simulator.moveCars());
+
+    List<Integer> carLocations = Objects.requireNonNull(simulator.getCarLocations());
+
+    assertAll(
+        () -> assertThat(carLocations).hasSize(carCount),
+        () -> carLocations.forEach(location -> assertThat(location).isEqualTo(simulateMoves))
+    );
+  }
+
+  @DisplayName("차들을 move 하면 전략에 따라 차를 움직인다.")
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  void tryMoveCars_carsMovedByStrategy(boolean strategyResult) {
+    int carCount = 3;
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(new CarCount(carCount), () -> strategyResult);
+
+    simulator.moveCars();
+    List<Car> result = simulator.copyCars();
+
+    assertThat(result)
+        .extracting(Car::getLocation)
+        .containsOnly(strategyResult ? 1 : 0);
+  }
+
+  @DisplayName("차들을 copy 하면 자동차 배열의 깊은 복사를 반환한다.")
+  @Test
+  void copyCars_returnDeepCopiedCarArray() {
+    int carCount = 3;
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(new CarCount(carCount), () -> true);
+
+    List<Car> firstCopiedCars = simulator.copyCars();
+    List<Car> secondCopiedCars = simulator.copyCars();
+
+    assertAll(
+        () -> assertThat(firstCopiedCars).hasSize(carCount),
+        () -> assertThat(secondCopiedCars).hasSize(carCount),
+        () -> assertThat(firstCopiedCars)
+            .zipSatisfy(secondCopiedCars, (element1, element2) -> assertThat(element1).isNotSameAs(element2))
+    );
+  }
+
+  @DisplayName("차들을 reset 하면 차의 위치가 0이 된다.")
+  @Test
+  void reset_setCarPositionToZero() {
+    int carCount = 3;
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(new CarCount(carCount), () -> true);
+
+    simulator.moveCars();
+    simulator.resetCars();
+    List<Car> result = simulator.copyCars();
+
+    assertThat(result)
+        .extracting(Car::getLocation)
+        .containsOnly(0);
+  }
+}
diff --git a/src/test/java/racing/simulator/CarTest.java b/src/test/java/racing/simulator/CarTest.java
new file mode 100644
index 00000000000..b9f785c2613
--- /dev/null
+++ b/src/test/java/racing/simulator/CarTest.java
@@ -0,0 +1,40 @@
+package racing.simulator;
+
+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 static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+class CarTest {
+
+  @DisplayName("전략에 따라 차를 이동한다.")
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  void go_carMovedByStrategy(boolean strategyResult) {
+    int simulateCount = 4;
+    Car car = new Car(() -> strategyResult);
+
+    for (int i = 0; i < simulateCount; i++) {
+      car.go();
+    }
+
+    assertThat(car.getLocation()).isEqualTo(strategyResult ? simulateCount : 0);
+  }
+
+  @DisplayName("reset하면 차의 위치를 0으로 만든다.")
+  @Test
+  void reset_setLocationZero() {
+    int simulateCount = 4;
+    Car car = new Car(() -> true);
+
+    for (int i = 0; i < simulateCount; i++) {
+      car.go();
+    }
+
+    car.reset();
+
+    assertThat(car.getLocation()).isEqualTo(0);
+  }
+}
diff --git a/src/test/java/racing/simulator/RandomCarMovingStrategyTest.java b/src/test/java/racing/simulator/RandomCarMovingStrategyTest.java
new file mode 100644
index 00000000000..376e26f7e71
--- /dev/null
+++ b/src/test/java/racing/simulator/RandomCarMovingStrategyTest.java
@@ -0,0 +1,43 @@
+package racing.simulator;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RandomCarMovingStrategyTest {
+
+  @DisplayName("움직일지 판단 할 시 3 이하 random 수로 뽑힐시 false를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, 1, 2, 3})
+  void isMove_returnFalse(int randomNum) {
+
+    Random randomStub = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return randomNum;
+      }
+    };
+
+    assertFalse(new RandomCarMovingStrategy(randomStub).canMove());
+  }
+
+  @DisplayName("움직일지 판단 할 시 4 이상 random 수로 뽑힐시 true를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {4, 5, 6, 7, 8, 9})
+  void isMove_returnTrue(int randomNum) {
+
+    Random randomStub = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return randomNum;
+      }
+    };
+
+    assertTrue(new RandomCarMovingStrategy(randomStub).canMove());
+  }
+}
diff --git a/src/test/java/racing/types/CarCountTest.java b/src/test/java/racing/types/CarCountTest.java
new file mode 100644
index 00000000000..41620815eb2
--- /dev/null
+++ b/src/test/java/racing/types/CarCountTest.java
@@ -0,0 +1,17 @@
+package racing.types;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class CarCountTest {
+
+  @DisplayName("자동차 수 값이 1, 0, 음수일 때 생성자를 호출하면 RuntimeException을 던진다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, 1, -1})
+  void constructor_givenZeroOrOneOrNegativeCarCount_throwsRuntimeException(int carCount) {
+    assertThrows(RuntimeException.class, () -> new CarCount(carCount));
+  }
+}
diff --git a/src/test/java/racing/types/SimulateCountTest.java b/src/test/java/racing/types/SimulateCountTest.java
new file mode 100644
index 00000000000..cf7ed3a7a14
--- /dev/null
+++ b/src/test/java/racing/types/SimulateCountTest.java
@@ -0,0 +1,17 @@
+package racing.types;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class SimulateCountTest {
+
+  @DisplayName("시뮬레이션 수 값이 0 또는 음수일 때 생성자를 호출하면 RuntimeException을 던진다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, -1})
+  void constructor_givenNotPositiveNumberSimulationCount_throwRuntimeException(int simulateCount) {
+    assertThrows(RuntimeException.class, () -> new SimulateCount(simulateCount));
+  }
+}
diff --git a/src/test/java/study/StringTest.java b/src/test/java/study/StringTest.java
index 5513a21ca59..384c92987da 100644
--- a/src/test/java/study/StringTest.java
+++ b/src/test/java/study/StringTest.java
@@ -4,6 +4,7 @@
 import org.junit.jupiter.api.Test;
 
 import java.util.Random;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -35,7 +36,7 @@ void testCharAtStringIndexOutOfBoundsException() {
     int targetIdx = 0;
 
     Random random = new Random();
-    while(targetIdx <= 2) {
+    while (targetIdx <= 2) {
       targetIdx = random.nextInt(Integer.MAX_VALUE) + 1;
     }
 
diff --git a/todo/step2 b/todo/step2
deleted file mode 100644
index d0f795e951d..00000000000
--- a/todo/step2
+++ /dev/null
@@ -1,11 +0,0 @@
-DONE
-- 빈 문자열이 입력되면 0을 반환한다.
-- null이 입력되면 0을 반환한다.
-- 구분자가 없이 숫자 하나이면 그대로 반환한다.
-- "," 로 구분자로 가지는 문자열을 split해서 수를 더한다.
-- ":" 로 구분자로 가지는 문자열을 split해서 수를 더한다.
-- 커스컴 구분자를 입력받아 구분해서 수를 더한다. (ex. //;\n1;2;3” 형식)
-- 커스텀 구분자를 입력받은 형태가 아닌데 "," 혹은 ":" 구분자가 아닌 경우는  RuntimeException을 던진다.
-- 음수를 전달하면 RuntimeException을 던진다.
-- 커스컴 구분자를 입력받아 나눴는데 나눈 결과에 숫자 이외 문자가 들어 있으면  RuntimeException을 던진다.
-


Reviews:
user: login: neojjc2
body: 안녕하세요 희섭님 🙇 

3단계 구현 잘 해주셨습니다 💯 
커밋 단위나 메세지도 작성 잘해주셨고
객체들간에 역할도 잘 정의 해주신 것 같네요 😄 

문의 주신 부분에 대해서는 리뷰 코멘트로 남겨드렸는데요,
조금 정리되면 좋을 것 같아서 소소한 의견들 드렸습니다.

한번 보시고 개선 검토 해주시면 감사하겠습니다 🙏 
그럼 재 리뷰 요청 기다리고 있겠습니다 🙇 
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-15T23:06:51Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T08:28:24Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T08:32:16Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T08:32:28Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T09:32:18Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T10:27:53Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T10:50:07Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T11:13:50Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T11:38:47Z
user: login: neojjc2
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T00:32:54Z
user: login: neojjc2
body: 안녕하세요 희섭님 🙇 

다소 많은 의견을 드린게 아닌가 걱정했는데,
많이 고민해주시고 깔끔하게 개선해주셨네요 👍 

문의 주신 부분에 대해서는 별도 코멘트로 남겨드렸습니다 😄 
소소한 의견 드렸는데 다음단계 진행하시면서
검토해주시면 감사하겠습니다 🙏 

이 정도면 다음단계 가도 될 것 같네요 👍 
개선된 구조에서 요구사항 반영이 어떻게 될지 
기대가 됩니다 🔥 

3단계 진행하시고 개선하시느라 수고 많으셨습니다. 
이만 `merge` 하겠습니다.
state: APPROVED
author_association: NONE
submitted_at: 2025-03-17T01:37:10Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T13:21:22Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T13:22:08Z
user: login: gan-ta
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T13:23:05Z

Review comments:
diff_hunk: @@ -6,4 +6,27 @@
 * 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.
 
 ## 온라인 코드 리뷰 과정
-* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
\ No newline at end of file
+* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
+
+## 요구사항 정리
+### Step2
+- 빈 문자열이 입력되면 0을 반환한다.
+- null이 입력되면 0을 반환한다.
+- 구분자가 없이 숫자 하나이면 그대로 반환한다.
+- "," 로 구분자로 가지는 문자열을 split해서 수를 더한다.
+- ":" 로 구분자로 가지는 문자열을 split해서 수를 더한다.
+- 커스컴 구분자를 입력받아 구분해서 수를 더한다. (ex. //;\n1;2;3” 형식)
+- 커스텀 구분자를 입력받은 형태가 아닌데 "," 혹은 ":" 구분자가 아닌 경우는  RuntimeException을 던진다.
+- 음수를 전달하면 RuntimeException을 던진다.
+- 커스컴 구분자를 입력받아 나눴는데 나눈 결과에 숫자 이외 문자가 들어 있으면  RuntimeException을 던진다.
+
+### Step3
+- 자동차 대수를 입력 받아야 한다.
+- 자동차 대수를 입력 받을 때 0, 1, 음수일 경우 RuntimeException을 던진다.
+- 주어진 횟수를 입력 받아야 한다.
+- 주어진 횟수를 입력 받을 때 0 또는 음수일 경우 RuntimeException을 던진다.
+- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈춘다.
+- 전진하는 조건은 Random한 확률로 움직이고 0~9까지의 수를 뽑아 4이상이면 움직인다.
+- 자동차의 상태를 주어진 횟수만큼 출력한다.
+- 한 객체에서 시뮬레이션을 여러번 수행했을 때 새로운 대회 환경에서 경주를 시작하도록 작동한다.
+- 자동차의 처음 위치는 0이다.
path: README.md
user: login: neojjc2
body: 요구사항 정리 너무 잘해주셨는데요?? 👍 💯 
created_at: 2025-03-15T22:04:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+package racing;
+
+import racing.simulator.CarMoveDecider;
+import racing.simulator.CarRacingGameSimulator;
+import racing.util.InputValidator;
+import racing.util.InputView;
+import racing.util.OutputView;
+
+import java.util.Random;
+
+public class CarRacingGameExecutor {
path: src/main/java/racing/CarRacingGameExecutor.java
user: login: neojjc2
body: 네이밍 좋습니다 👍 
created_at: 2025-03-15T22:06:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+package racing;
+
+import racing.simulator.CarMoveDecider;
+import racing.simulator.CarRacingGameSimulator;
+import racing.util.InputValidator;
+import racing.util.InputView;
+import racing.util.OutputView;
+
+import java.util.Random;
+
+public class CarRacingGameExecutor {
+
+  public static void main(String[] args) {
+    int carCount = getCarCount();
+    int simulateCount = getSimulateCount();
+
+    OutputView.showWhitespace();
+    OutputView.showExecutionResultText();
+    execute(carCount, simulateCount);
+  }
+
+  private static int getCarCount() {
+    int carCount = InputView.showCarCountInput();
+    InputValidator.checkNotValidCarCount(carCount);
+    return carCount;
+  }
+
+  private static int getSimulateCount() {
+    int simulateCount = InputView.showSimulateCountInput();
+    InputValidator.checkNotValidSimulateCount(simulateCount);
+    return simulateCount;
+  }
+
+  private static void execute(int carCount, int simulateCount) {
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(carCount, new CarMoveDecider(new Random()));
+    for (int i = 0; i < simulateCount; i++) {
+      simulator.tryMoveCars();
+      OutputView.showCarLocation(simulator.copyCars());
+      OutputView.showWhitespace();
+    }
+    simulator.resetCars();
+  }
+}
path: src/main/java/racing/CarRacingGameExecutor.java
user: login: neojjc2
body: 접근 제한자 관련해서 고민을 많이 하신 것 같네요 😄 
일단 희섭님의 설계원칙을 그대로 따른다는 가정하에 제가 희섭님이 작성하신 자동차 게임을
제가 만든 도메인클래스, 혹은 서비스에서 사용을 해본다고 했을 때
외부로 제공된 클래스는 `CarRacingGameExecutor`입니다. 

API 형태로 제공을 해야 하는 상황이고, 클라이언트는 콘솔이 아닌 앱 어플리케이션이라고 가정했을 때
저는 어떻게 사용을 할 수 있을까요?? 🤔 

`CarRacingGameExecutor`는 `main`만 제공을 하고 있는 상태입니다. 
혹 `private`로 되어있는 `method`들을 `public`으로 변경한다고 해도
콘솔 입, 출력에 강결합 되어있어서 바로 사용은 어렵습니다 🤔 

그럼 여기서 선택할 수 있는 대안은 `CarRacingGameSimulator`을 사용하는 건데 
`CarRacingGameSimulator`을 사용하더라도 `simulateCount`를 이용한 반복문을 계속
관리를 해줘야 합니다. 

여러군데서 사용한다면 같은 코드를 계속 붙여넣기 해야하는 상황입니다 😄 
코드로 설명을 드리자면, 
```java
CarRacingGameSimulator simulator = new CarRacingGameSimulator(carCount, new CarMoveDecider(new Random()));
//----- 계속 사용할 때 마다 반복이 되는 부분 
for (int i = 0; i < simulateCount; i++) {
  simulator.tryMoveCars();
  OutputView.showCarLocation(simulator.copyCars());
  OutputView.showWhitespace();
}
simulator.resetCars();
//-----
// 약간 아래처럼 사용이 되어야 CarRacingGameSimulator가 하나의 도메인 객체단위가 될 것 같습니다 :-) 
CarRacingGameSimulator simulator = new CarRacingGameSimulator(carCount, simulateCount, new CarMoveDecider(new Random()));
CarRacingGameResult result = simulator.run();
```

그리고 `CarRacingGameExecutor`는 `simulateCount`만큼 잘 경기를 수행하는지에 대한 부분도
테스트가 되어야 할 것 같은데 지금 구조에서 테스트 작성은 좀 어렵습니다 😄 

그리고 고민하신 접근제한자 부분에 대해서 제 의견을 드리자면, 
접근 제한자는 `"이 클래스가 어디서 사용되는가"보다는 "이 클래스/멤버가 외부에 어떤 인터페이스를 제공해야 하는가"`에 
따라 결정되어야 한다고 생각합니다. 

물론 클래스들에 대해서도 강하게 접근 통제를 해서 여기저기에서 사용되는 것을 막으려고 하신 
희섭님의 고민하신 포인트도 잘 이해 됩니다 😄 
하지만 처음부터 강하게 제약을 하면 나중에 확장이나, 유지보수 상황에서 
더 큰(?) 개선을 해야할 상황이 생기기도 합니다. 
예를 들어 `Car`의 종류가 다양해진다거나, 
다른 게임인데 `Car`나, `CarMoveDecider` 같은 룰은 동일하게 사용이 필요할 때  등등의 상황에 
닥쳤을 때 큰 리팩토링이 필요할 수 있습니다. 그리고 패키지 구조가 변경되거나 다른 개발자가 코드를 수정할 때 
예상치 못한 의존성 문제가 발생할 수 있습니다.
그리고 위와 같은 룰 때문에 다른 패키지에 동일한 기능을 하거나 거의 비슷한 클래스들이 여러개 생길 수 있습니다. 
그렇게 되면 또 추후 추가 요구사항을 개발할 때 확인해야할 포인트가 늘어나고 그만큼 신경쓸 부분도 많아지게 됩니다. 

처음엔 최대한 객체중심으로 접근제한자를 결정하시고 나중에 정말 클래스 들의 용도가 확실하고 필요하다면 package 중심으로 접근제한자를 조정하시는게 낫지 않나?? 생각됩니다 🙏 
그리고 인터페이스를 가지고도 충분히 비슷한 효과를 낼 수 있으니까요 
(인터페이스를 외부로 `public`으로 `method`를 제공하고 구현 `package`를 하나 만들어서 외부로 접근 못하게 접근제한자 조정하고, 추가로 다형성이 필요한 상황이라면 정의한 인터페이스를 구현해서 다른 `package`에 같은 방식으로) 

접근제한자 관련 부분은 아무래도 제 개인적인 생각이 더 많이 들어간 의견이다 보니, 
참고 정도만 해주시면 될 것 같습니다 🙏 

그리고 `CarRacingGameSimulator`쪽은 다른 객체에서 사용이 쉽도록 좀 더 캡슐화가 필요할 것 같습니다 🙇  
요 부분은 개선 검토 부탁 드릴께요 😄 
> 프로그래밍요구사항
> * 모든 로직에 단위 테스트를 구현한다


created_at: 2025-03-15T22:37:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+package racing.simulator;
+
+public class Car {
+  private int location;
+
+  Car() {
+    this.location = 0;
+  }
+
+  Car(Car car) {
+    this.location = car.location;
+  }
+
+  void go() {
+    this.location += 1;
+  }
+
+  public String getLocationConsoleFormat() {
+    return "-".repeat(Math.max(0, location));
path: src/main/java/racing/simulator/Car.java
user: login: neojjc2
body: `Car`에서 출력에 대한 포맷까지 관리하고 있습니다 😄 
만약 포맷 형식이 변경된다거나, 콘솔이 아닌 다른 형식으로 출력이 될 경우
아니면 콘솔일 땐 `"-"`으로 웹이나 앱일 땐 `json`으로 출력되도록 확장이 되어야 한다면
이 부분은 `Car` 객체의 비즈니스 요구사항이 아닌 `output` 형식 때문에 계속 적인 수정이 발생할 수 밖에 없습니다.

이 부분은 좀 개선이 필요할 것 같습니다 🙏 
> 프로그래밍요구사항
> UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.

created_at: 2025-03-15T22:43:34Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+package racing.simulator;
+
+public class Car {
+  private int location;
+
+  Car() {
+    this.location = 0;
+  }
+
+  Car(Car car) {
+    this.location = car.location;
+  }
path: src/main/java/racing/simulator/Car.java
user: login: neojjc2
body: 생성자 정도는 `public`으로 가셔도 될 것 같습니다 🙇 
created_at: 2025-03-15T22:44:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,18 @@
+package racing.simulator;
+
+import java.util.Random;
+
+public class CarMoveDecider {
path: src/main/java/racing/simulator/CarMoveDecider.java
user: login: neojjc2
body: CarMoveDecider 구조 좋습니다 👍 

근데 뭐랄까 `canMove`를 `CarMoveDecider`가 가지고 있는게 맞나?? 라는 생각이 듭니다. 
왜냐하면 `Car`와 `CarMoveDecider`같에 연관이 없습니다. 

꼭 자동차와 운전석이 분리되어있는 느낌인데요 😅 
구조적으로 `Car`를 움직이게 하려면 `CarMoveDecider`를 통해서 움직이게 할 수 있도록
설계적 제약이 필요할 것 같은데 
지금 구조는 `CarRacingGameSimulator.tryMoveCar`에서 조립이 되고 있어서 
`CarMoveDecider`로 `Car`를 조정하기 어려운 구조이긴 합니다 😄 

**움직일지 말지는 Car가 결정**하게 하고 **어떤 조건에서 움직일지**를 
외부에서 제어하는 쪽으로 좀 정리해보시는건 어떨까요?? 🤔 
요 내용 한번 보시면 도움 되실 것 같습니다 🙇 
> https://jackjeong.tistory.com/108
created_at: 2025-03-15T22:50:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,18 @@
+package racing.simulator;
+
+import java.util.Random;
+
+public class CarMoveDecider {
+
+  private final Random random;
+  private static final int MOVE_THRESHOLD = 4;
+  private static final int RANDOM_RANGE_LIMIT = 10;
path: src/main/java/racing/simulator/CarMoveDecider.java
user: login: neojjc2
body: 상수 추출도 좋고 네이밍도 적절합니다 👍 
created_at: 2025-03-15T22:50:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+package racing.simulator;
+
+public class CarRacingGameSimulator {
+
+  private final int carCount;
+  private final CarMoveDecider carMoveDecider;
+  private final Car[] cars;
+
+  public CarRacingGameSimulator(int carCount, CarMoveDecider carMoveDecider) {
+    this.carCount = carCount;
+    this.carMoveDecider = carMoveDecider;
+    this.cars = new Car[carCount];
+    for (int i = 0; i < carCount; i++) {
+      cars[i] = new Car();
+    }
+  }
+
+  public void tryMoveCars() {
+    for (Car car : cars) {
+      tryMoveCar(car);
+    }
+  }
+
+  void tryMoveCar(Car car) {
+    if (this.carMoveDecider.canMove()) {
+      car.go();
+    }
+  }
+
+  public Car[] copyCars() {
+    Car[] res = new Car[carCount];
+    for (int i = 0; i < carCount; i++) {
+      res[i] = new Car(cars[i]);
+    }
+    return res;
path: src/main/java/racing/simulator/CarRacingGameSimulator.java
user: login: neojjc2
body: 리뷰어님들 마다 약간 다르실 수 있는데, 
이미 `lamda`, `stream`을 잘 아신다면 굳이 `array`로 하지 않으셔도 될 것 같습니다 😄 
`List`가 훨씬 좋잖아요 🤣 

그리고 원본객체를 보호하기 위한 복사 방식 선택하신 것 너무 좋습니다 👍 
일부러 `get`을 사용하지 않으셨기 때문에 이 방식을 선택하신 것 같은데요
추가적으로 불변객체 관련해서도 정리하고 가시면 좋을 것 같습니다 🙇 
> https://velog.io/@conatuseus/Java-Immutable-Object%EB%B6%88%EB%B3%80%EA%B0%9D%EC%B2%B4
created_at: 2025-03-15T22:52:03Z
author_association: NONE
diff_hunk: @@ -0,0 +1,19 @@
+package racing.util;
+
+public class InputValidator {
+
+  private static final int MIN_CAR_COUNT = 2;
+  private static final int MIN_SIMULATE_COUNT = 1;
+
+  public static void checkNotValidCarCount(int carCount) {
+    if (carCount < MIN_CAR_COUNT) {
+      throw new IllegalArgumentException("자동차 수는 최소 " + MIN_CAR_COUNT + "대여야 합니다.");
+    }
+  }
path: src/main/java/racing/util/InputValidator.java
user: login: neojjc2
body: Validator너무 좋습니다 👍 값에 대한 검증도 너무 좋은데요 💯 
문제는 Validator를 호출 안해버리면 통과 되는게 문제입니다 😅 
즉 
```java
private static int getCarCount() {
    int carCount = InputView.showCarCountInput();
    InputValidator.checkNotValidCarCount(carCount); // 이렇게 호출하지 않으면 검증이 누락됩니다. 
    return carCount;
}
```
비즈니스 요건에서 꼭 필요한 검증인데 누락되지 않게 하는 법이 없을까요?? 🤔 
`carCount`가 원시객체로 포장이 되면 어떨까요?? 

이 후 단계에서 계속 연습하실 것 같은데, 
미리 알고가시는 것도 좋을 것 같습니다 🙇 
요 내용 한번 보고 가시죠 
> https://velog.io/@kanamycine/Java-%EC%9B%90%EC%8B%9C%EA%B0%92-%ED%8F%AC%EC%9E%A5

created_at: 2025-03-15T22:56:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,18 @@
+package racing.util;
+
+import java.util.Scanner;
+
+public class InputView {
+
+  public static int showCarCountInput() {
+    System.out.println("자동차 대수는 몇 대 인가요?");
+    Scanner scanner = new Scanner(System.in);
+    return scanner.nextInt();
+  }
+
+  public static int showSimulateCountInput() {
+    System.out.println("시도할 회수는 몇 회 인가요?");
+    Scanner scanner = new Scanner(System.in);
path: src/main/java/racing/util/InputView.java
user: login: neojjc2
body: `method` 호출 마다 `scanner`가 초기화 될 필요는 없을 것 같습니다. 
`System.in`은 소중한(?) 자원이니 한번만 초기화 되도록 하셔도 될 것 같아요 🙇 
created_at: 2025-03-15T22:57:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,37 @@
+package racing.simulator;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
+
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+class CarMoveDeciderTest {
+
+  @DisplayName("움직일지 판단 할 시 3 이하 random 수로 뽑힐시 false를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, 1, 2, 3})
+  void canMove_returnFalse(int randomNum) {
+    Random mockRandom = Mockito.mock(Random.class);
+
+    Mockito.when(mockRandom.nextInt(anyInt())).thenReturn(randomNum);
+
+    assertFalse(new CarMoveDecider(mockRandom).canMove());
+  }
+
+  @DisplayName("움직일지 판단 할 시 4 이상 random 수로 뽑힐시 true를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {4, 5, 6, 7, 8, 9})
+  void canMove_returnTrue(int randomNum) {
+    Random mockRandom = Mockito.mock(Random.class);
+
+    Mockito.when(mockRandom.nextInt(anyInt())).thenReturn(randomNum);
path: src/test/java/racing/simulator/CarMoveDeciderTest.java
user: login: neojjc2
body: Mocking을 사용하신 부분 좋습니다 👍 
하지만 `Mocking`을 사용하지 않고도 테스트를 작성하실 수 있도록 구조를 잘 잡아주신 것 같은데요 😄 
`Stub`등을 활용해보시는 것도 나쁘지 않을 것 같습니다. 

이러한 의견을 드리는 이유는, 최대한 `mocking`은 자제하도록 연습해보시는게 어떨가 해서 그렇습니다. 
`mocking`을 사용하게 되다보면 사실 `mocking`된 객체가 변경되었을 때 알아차리기도 어렵고, 
실제 컴포넌트 들이 잘 동작하는지도 알기 어렵습니다. 

아마 `Random` 값 때문에 그러신 것 같은데, 위에서도 의견 드렸지만 
`Car`와 `CarMoveDecider`를 적절하게 리팩토링 하시면
`Random`값 `mocking`없이도 테스트가 잘 작성될 것 같습니다 😄 
요 부분도 한번 고민 부탁 드리겠습니다 🙇 
created_at: 2025-03-15T23:03:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+package racing;
+
+import racing.simulator.CarMoveDecider;
+import racing.simulator.CarRacingGameSimulator;
+import racing.util.InputValidator;
+import racing.util.InputView;
+import racing.util.OutputView;
+
+import java.util.Random;
+
+public class CarRacingGameExecutor {
+
+  public static void main(String[] args) {
+    int carCount = getCarCount();
+    int simulateCount = getSimulateCount();
+
+    OutputView.showWhitespace();
+    OutputView.showExecutionResultText();
+    execute(carCount, simulateCount);
+  }
+
+  private static int getCarCount() {
+    int carCount = InputView.showCarCountInput();
+    InputValidator.checkNotValidCarCount(carCount);
+    return carCount;
+  }
+
+  private static int getSimulateCount() {
+    int simulateCount = InputView.showSimulateCountInput();
+    InputValidator.checkNotValidSimulateCount(simulateCount);
+    return simulateCount;
+  }
+
+  private static void execute(int carCount, int simulateCount) {
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(carCount, new CarMoveDecider(new Random()));
+    for (int i = 0; i < simulateCount; i++) {
+      simulator.tryMoveCars();
+      OutputView.showCarLocation(simulator.copyCars());
+      OutputView.showWhitespace();
+    }
+    simulator.resetCars();
+  }
+}
path: src/main/java/racing/CarRacingGameExecutor.java
user: login: gan-ta
body: 상세하게 설명해 주셔서 감사합니다~!

첫번째로는, CarRacingGameExecutor에서 시뮬레이션 게임을 하는 동작이 simulator에 있어야 좀 더 유연하게 코드 재활용이 좋겠네요! 수정해보겠습니다. 👍
CarRacingGameExecutor에서 시뮬레이션 게임을 하는 동작이 처음 들어간 이유는 사실 시뮬레이션이 1회씩 수행되면서 콘솔 아웃풋을 하는데요
콘솔 아웃풋을 하는 동작은 Executor에서 수행을 하고 싶어서 구현이 됐습니다.
차라리, simulator에서 run을 하면 시뮬레이터 횟수마다 결과를 넣어서 반환하면 어떨까 생각이 됐습니다.
CarRacingGameExecutor는 Simulator를 작동 시키는 용도로 하나의 방법으로 콘솔 입력을 받아 작동시킬 뿐 메인 비지니스 로직은 simulator에 있는 것이 훨씬 좋은 구조인거 같아요 😄

두번째로는, 접근제어자는 외부로 드러나는 인터페이스를 만들고 구현체에서 접근제어자를 조절하되 신중히 하는 것도 좋을 것 같다는 말이 많이 도움이 된거 같아요. 앞으로의 미션에서는 적용할 수 있는 부분에서 적용해봐도 좋을 거 같아요. 감사합니다! 😄
created_at: 2025-03-16T08:28:24Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+package racing.simulator;
+
+public class Car {
+  private int location;
+
+  Car() {
+    this.location = 0;
+  }
+
+  Car(Car car) {
+    this.location = car.location;
+  }
+
+  void go() {
+    this.location += 1;
+  }
+
+  public String getLocationConsoleFormat() {
+    return "-".repeat(Math.max(0, location));
path: src/main/java/racing/simulator/Car.java
user: login: gan-ta
body: 클래스 필드의 getter 메서드를 안만든다고 계속 염두하고 구현하다보니 구현이 된 거 같아요! (수업때 나왔던 내용이였습니다.)
이 부분은 UI 로직의 일부라 InputView 클래스 쪽에 구현을 옮겨도 좋을거 같다고 생각이 들어서 한번 수정해 보겠습니다.
꼭 getter를 구현 안하는 것이 항상 좋지는 않고 경우에 따라서 유연하게 생각하는게 좋다고는 생각이 피드백을 보며 들긴 합니다. 😄
이 부분은 Car의 location 변수 getter 함수를 만들고 InputView에서 구현되도록 해보겠습니다.
감사합니다~!

created_at: 2025-03-16T08:32:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+package racing.simulator;
+
+public class Car {
+  private int location;
+
+  Car() {
+    this.location = 0;
+  }
+
+  Car(Car car) {
+    this.location = car.location;
+  }
path: src/main/java/racing/simulator/Car.java
user: login: gan-ta
body: 넵넵 감사합니다!
created_at: 2025-03-16T08:32:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,18 @@
+package racing.simulator;
+
+import java.util.Random;
+
+public class CarMoveDecider {
path: src/main/java/racing/simulator/CarMoveDecider.java
user: login: gan-ta
body: 너무 도움이 많이 됐습니다.
움직일지 판단하는 strategy를 만들고 다양한 조건을 구현하는 식으로 빼면 확장성이 좋네요 😄
테스트 할 때도 테스트 할 때의 전략을 만들어서 넣으면 유연하게 테스트 가능하고 mock도 사용하지 않을 수도 있고요
수정해보겠습니다. 감사합니다.😄
created_at: 2025-03-16T09:32:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,43 @@
+package racing.simulator;
+
+public class CarRacingGameSimulator {
+
+  private final int carCount;
+  private final CarMoveDecider carMoveDecider;
+  private final Car[] cars;
+
+  public CarRacingGameSimulator(int carCount, CarMoveDecider carMoveDecider) {
+    this.carCount = carCount;
+    this.carMoveDecider = carMoveDecider;
+    this.cars = new Car[carCount];
+    for (int i = 0; i < carCount; i++) {
+      cars[i] = new Car();
+    }
+  }
+
+  public void tryMoveCars() {
+    for (Car car : cars) {
+      tryMoveCar(car);
+    }
+  }
+
+  void tryMoveCar(Car car) {
+    if (this.carMoveDecider.canMove()) {
+      car.go();
+    }
+  }
+
+  public Car[] copyCars() {
+    Car[] res = new Car[carCount];
+    for (int i = 0; i < carCount; i++) {
+      res[i] = new Car(cars[i]);
+    }
+    return res;
path: src/main/java/racing/simulator/CarRacingGameSimulator.java
user: login: gan-ta
body: 불변객체를 만들어서 불필요한 메모리 사용을 줄일 수 있는 것도 한번 생각하고 가게 됩니다. 감사합니다. 😄

created_at: 2025-03-16T10:27:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,18 @@
+package racing.util;
+
+import java.util.Scanner;
+
+public class InputView {
+
+  public static int showCarCountInput() {
+    System.out.println("자동차 대수는 몇 대 인가요?");
+    Scanner scanner = new Scanner(System.in);
+    return scanner.nextInt();
+  }
+
+  public static int showSimulateCountInput() {
+    System.out.println("시도할 회수는 몇 회 인가요?");
+    Scanner scanner = new Scanner(System.in);
path: src/main/java/racing/util/InputView.java
user: login: gan-ta
body: 넵 확인해주셔서 감사합니다!
created_at: 2025-03-16T10:50:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,19 @@
+package racing.util;
+
+public class InputValidator {
+
+  private static final int MIN_CAR_COUNT = 2;
+  private static final int MIN_SIMULATE_COUNT = 1;
+
+  public static void checkNotValidCarCount(int carCount) {
+    if (carCount < MIN_CAR_COUNT) {
+      throw new IllegalArgumentException("자동차 수는 최소 " + MIN_CAR_COUNT + "대여야 합니다.");
+    }
+  }
path: src/main/java/racing/util/InputValidator.java
user: login: gan-ta
body: 넵! 도움이 많이 됐습니다. 감사합니다~! 이 부분 적용해보겠습니다.
created_at: 2025-03-16T11:13:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,37 @@
+package racing.simulator;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
+
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+class CarMoveDeciderTest {
+
+  @DisplayName("움직일지 판단 할 시 3 이하 random 수로 뽑힐시 false를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, 1, 2, 3})
+  void canMove_returnFalse(int randomNum) {
+    Random mockRandom = Mockito.mock(Random.class);
+
+    Mockito.when(mockRandom.nextInt(anyInt())).thenReturn(randomNum);
+
+    assertFalse(new CarMoveDecider(mockRandom).canMove());
+  }
+
+  @DisplayName("움직일지 판단 할 시 4 이상 random 수로 뽑힐시 true를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {4, 5, 6, 7, 8, 9})
+  void canMove_returnTrue(int randomNum) {
+    Random mockRandom = Mockito.mock(Random.class);
+
+    Mockito.when(mockRandom.nextInt(anyInt())).thenReturn(randomNum);
path: src/test/java/racing/simulator/CarMoveDeciderTest.java
user: login: gan-ta
body: 넵! 감사합니다.
제가 이해하기로는 "Stub은 인스턴스화하여 구현한 가짜 객체(Dummy, 기능 구현이 없음)을 이용해 실제로 동작하는 것처럼 보이게 만드는 객체" ([참고](https://azderica.github.io/00-test-mock-and-stub)) 라서 RandomCarMovingStrategy 클래스에 Random 클래스의 nextInt 메서드를 재정의한 익명 클래스 ([link](https://github.com/next-step/java-racingcar/pull/5963/files#diff-9b79a270591ef49711adc5592558ce1ccf59f8b36f4c3a1099e830ed40d9e28cR19)) 를 넣어서 테스트 해봤습니다!

하나 궁금한 점은
> mocking을 사용하게 되다보면 사실 mocking된 객체가 변경되었을 때 알아차리기도 어렵고,
> 실제 컴포넌트 들이 잘 동작하는지도 알기 어렵습니다.

이 부분인데 조금 구체적으로 어떤 점이 안좋은지 바로 느껴지지는 않아서 혹시 조금 더 설명해 주실 수 있을까요?

**mocking된 객체가 변경**
이 부분은 어떤 포인트를 고려하셔서 말씀 주신건지 궁금합니다!
mock 객체의 return 설정을 여러곳에서 할 수 있어서 그런것인지? 이런 생각도 들었고요
stub은 유동적으로 return 값들이 수정되지는 않아서 장점이 있지만 mock은 그렇지 않은 것으로 생각을 해봤습니다.
추가적으로 인자 값을 anyInt()로 주는 등 너무 자유도 높게 메서드 작동을 조절할 수 있어서 그런 건지도 생각이 들었습니다.
 
**실제 컴포넌트 들이 잘 동작하는지도 알기 어렵습니다.** 
이 부분은 어떤 점을 고려해서 코멘트 남겨주신 것인지 궁금해서 한번 여쭤봤습니다.😄

생각할 부분들을 많이 주셔서 감사합니다. 도움이 많이 되었습니다. 👍 🙇
created_at: 2025-03-16T11:38:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,37 @@
+package racing.simulator;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
+
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+class CarMoveDeciderTest {
+
+  @DisplayName("움직일지 판단 할 시 3 이하 random 수로 뽑힐시 false를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, 1, 2, 3})
+  void canMove_returnFalse(int randomNum) {
+    Random mockRandom = Mockito.mock(Random.class);
+
+    Mockito.when(mockRandom.nextInt(anyInt())).thenReturn(randomNum);
+
+    assertFalse(new CarMoveDecider(mockRandom).canMove());
+  }
+
+  @DisplayName("움직일지 판단 할 시 4 이상 random 수로 뽑힐시 true를 반환한다.")
+  @ParameterizedTest
+  @ValueSource(ints = {4, 5, 6, 7, 8, 9})
+  void canMove_returnTrue(int randomNum) {
+    Random mockRandom = Mockito.mock(Random.class);
+
+    Mockito.when(mockRandom.nextInt(anyInt())).thenReturn(randomNum);
path: src/test/java/racing/simulator/CarMoveDeciderTest.java
user: login: neojjc2
body: @gan-ta 네 희섭님 
아 제가 설명이 좀 부족했던 것 같습니다 😄 
`mocking`된 클래스나 `service`의 경우 `mocking`되어있기 때문에 초기화 할 때 인자가 변경되거나 혹은 클래스 내부에 
`method`들이 변경되어도 제 테스트코드에서 `mocking`객체를 초기화 하는 코드가 없거나  그 `method`를 직접 호출하지 
않으면 수정된 사항을 알기가 어렵습니다. 

그러다가 나중에 TC가 수정도 안했는데 깨져있는 경우도 있고 알아차리기도 어려워서 
그 부분에 대한 설명이었습니다 😄 

그리고 `mock`객체를 초기화 할 때 필요한 객체들도 `mock`으로 보통 하기 때문에 한단계 건너가면
`mocking`객체의 변경을 거의 알기 어렵죠 😄 

그런 맥락에서 드린 말씀이니 참고 정도만 해주시면 될 것 같습니다 🙏 
created_at: 2025-03-17T00:32:54Z
author_association: NONE
diff_hunk: @@ -1,43 +1,42 @@
 package racing;
 
-import racing.simulator.CarMoveDecider;
 import racing.simulator.CarRacingGameSimulator;
-import racing.util.InputValidator;
+import racing.simulator.RandomCarMovingStrategy;
+import racing.types.CarCount;
+import racing.types.SimulateCount;
 import racing.util.InputView;
 import racing.util.OutputView;
 
-import java.util.Random;
+import java.util.List;
 
 public class CarRacingGameExecutor {
 
   public static void main(String[] args) {
-    int carCount = getCarCount();
-    int simulateCount = getSimulateCount();
+    CarCount carCount = getCarCount();
+    SimulateCount simulateCount = getSimulateCount();
+
+    CarRacingGameSimulator simulator = new CarRacingGameSimulator(carCount, new RandomCarMovingStrategy());
path: src/main/java/racing/CarRacingGameExecutor.java
user: login: neojjc2
body: `main`만 봐도 어떻게 개선하셨는지 알 것 같습니다 😄 
많이 정리가 됐네요 👍 
created_at: 2025-03-17T00:51:29Z
author_association: NONE
diff_hunk: @@ -1,25 +1,31 @@
 package racing.simulator;
 
 public class Car {
+
   private int location;
+  private final CarMovingStrategy carMovingStrategy;
 
-  Car() {
+  public Car(CarMovingStrategy carMovingStrategy) {
     this.location = 0;
+    this.carMovingStrategy = carMovingStrategy;
   }
 
-  Car(Car car) {
+  public Car(Car car) {
     this.location = car.location;
+    this.carMovingStrategy = car.carMovingStrategy;
   }
 
-  void go() {
-    this.location += 1;
+  public int getLocation() {
+    return location;
   }
 
-  public String getLocationConsoleFormat() {
-    return "-".repeat(Math.max(0, location));
+  public void go() {
+    if (carMovingStrategy.canMove()) {
+      this.location += 1;
+    }
path: src/main/java/racing/simulator/Car.java
user: login: neojjc2
body: 아주 소소한 의견이긴 한데 `go`의 인자로 `CarMovingStrategy`를 주는것도 괜찮아 보입니다. 
이렇게 되면 매번 움직일 때 마다 전략을 다르게 가져갈 수 있는 장점이 있습니다 😄 
요건 구조적 의견이니 참고 정도만 해주시면 될 것 같습니다 🙇 
created_at: 2025-03-17T00:52:38Z
author_association: NONE
diff_hunk: @@ -1,36 +1,44 @@
 package racing.simulator;
 
+import racing.types.CarCount;
+import racing.types.SimulateCount;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class CarRacingGameSimulator {
 
-  private final int carCount;
-  private final CarMoveDecider carMoveDecider;
-  private final Car[] cars;
+  private final List<Car> cars = new ArrayList<>();
 
-  public CarRacingGameSimulator(int carCount, CarMoveDecider carMoveDecider) {
-    this.carCount = carCount;
-    this.carMoveDecider = carMoveDecider;
-    this.cars = new Car[carCount];
-    for (int i = 0; i < carCount; i++) {
-      cars[i] = new Car();
+  public CarRacingGameSimulator(CarCount carCount, CarMovingStrategy carMovingStrategy) {
+    for (int i = 0; i < carCount.getCount(); i++) {
+      cars.add(new Car(carMovingStrategy));
     }
   }
 
-  public void tryMoveCars() {
-    for (Car car : cars) {
-      tryMoveCar(car);
+  public List<List<Integer>> run(SimulateCount simulateCount) {
+    List<List<Integer>> res = new ArrayList<>();
path: src/main/java/racing/simulator/CarRacingGameSimulator.java
user: login: neojjc2
body: 게임 결과만을 담당하는 객체로 하나 분리되도 나쁘지 않을 것 같습니다 🙏 
created_at: 2025-03-17T00:53:57Z
author_association: NONE
diff_hunk: @@ -1,36 +1,44 @@
 package racing.simulator;
 
+import racing.types.CarCount;
+import racing.types.SimulateCount;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class CarRacingGameSimulator {
 
-  private final int carCount;
-  private final CarMoveDecider carMoveDecider;
-  private final Car[] cars;
+  private final List<Car> cars = new ArrayList<>();
path: src/main/java/racing/simulator/CarRacingGameSimulator.java
user: login: neojjc2
body: 자연스럽게 일급컬렉션에 대한 의견을 드릴 수 있을 것 같네요 😄 
개념이 약간 어려우실 수도 있는데
`strict`하게 가져가실 필요는 없을 것 같고 연습삼아 한번 해보시면 좋을 것 같습니다 🙇 
요 내용 한번 보시고 가면 좋을 것 같습니다 🙇 
> https://jojoldu.tistory.com/412
created_at: 2025-03-17T00:55:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,19 @@
+package racing.types;
+
+public class CarCount {
+
+  private static final int MIN_CAR_COUNT = 2;
+
+  private final int count;
+
+  public CarCount(int carCount) {
+    if (carCount < MIN_CAR_COUNT) {
+      throw new IllegalArgumentException("자동차 수는 최소 " + MIN_CAR_COUNT + "대여야 합니다.");
+    }
+    this.count = carCount;
+  }
path: src/main/java/racing/types/CarCount.java
user: login: neojjc2
body: 원시객체 포장 잘 해주셨고 자연스럽게 검증도 `mandatory`하게 되었네요 😄 
created_at: 2025-03-17T00:59:30Z
author_association: NONE
diff_hunk: @@ -0,0 +1,19 @@
+package racing.types;
+
+public class SimulateCount {
+
+  private static final int MIN_SIMULATE_COUNT = 1;
+
+  private final int count;
+
+  public SimulateCount(int simulateCount) {
+    if (simulateCount < MIN_SIMULATE_COUNT) {
+      throw new IllegalArgumentException("시뮬레이션 횟수는 최소 " + MIN_SIMULATE_COUNT + "회여야 합니다.");
+    }
+    this.count = simulateCount;
+  }
+
+  public int getCount() {
+    return count;
+  }
+}
path: src/main/java/racing/types/SimulateCount.java
user: login: neojjc2
body: 💯 💯 💯 💯 💯 
created_at: 2025-03-17T00:59:42Z
author_association: NONE
diff_hunk: @@ -4,15 +4,15 @@
 
 public class InputView {
 
+  private static final Scanner scanner = new Scanner(System.in);
path: src/main/java/racing/util/InputView.java
user: login: neojjc2
body: ```suggestion
  private static final Scanner SCANNER = new Scanner(System.in);
```
created_at: 2025-03-17T00:59:56Z
author_association: NONE
diff_hunk: @@ -0,0 +1,17 @@
+package racing.types;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class SimulateCountTest {
+
+  @DisplayName("시뮬레이션 수 값이 0 또는 음수일 때 생성자를 호출하면 RuntimeException을 던진다.")
+  @ParameterizedTest
+  @ValueSource(ints = {0, -1})
+  void constructor_givenNotPositiveNumberSimulationCount_throwRuntimeException(int simulateCount) {
+    assertThrows(RuntimeException.class, () -> new SimulateCount(simulateCount));
+  }
+}
path: src/test/java/racing/types/SimulateCountTest.java
user: login: neojjc2
body: 테스트도 훨씬 풍성해진 느낌입니다 💯 
created_at: 2025-03-17T01:00:18Z
author_association: NONE
diff_hunk: @@ -1,36 +1,44 @@
 package racing.simulator;
 
+import racing.types.CarCount;
+import racing.types.SimulateCount;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class CarRacingGameSimulator {
 
-  private final int carCount;
-  private final CarMoveDecider carMoveDecider;
-  private final Car[] cars;
+  private final List<Car> cars = new ArrayList<>();
path: src/main/java/racing/simulator/CarRacingGameSimulator.java
user: login: gan-ta
body: 도움이 많이 되었습니다.
해당 [코멘트](https://github.com/next-step/java-racingcar/pull/5963#discussion_r1997799286) 에서 적용해 볼 수 있지 않을까 싶네요 ㅎㅎ
simulator는 run 행위를 하는 클래스이고
나머지는 Car 들의 콜렉션 단위로 작동을 하는 메서드이니 RacingCarGroup 이라는 일급 콜렉션으로 관리하는 것도 훨씬 좋을 것 같고 확장성 면에서도 좋을 것 같아요

지금보니 "원시객체로 포장" 의 콜렉션 버전 같기도 하네요!
created_at: 2025-03-17T13:21:22Z
author_association: NONE
diff_hunk: @@ -1,36 +1,44 @@
 package racing.simulator;
 
+import racing.types.CarCount;
+import racing.types.SimulateCount;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
 public class CarRacingGameSimulator {
 
-  private final int carCount;
-  private final CarMoveDecider carMoveDecider;
-  private final Car[] cars;
+  private final List<Car> cars = new ArrayList<>();
 
-  public CarRacingGameSimulator(int carCount, CarMoveDecider carMoveDecider) {
-    this.carCount = carCount;
-    this.carMoveDecider = carMoveDecider;
-    this.cars = new Car[carCount];
-    for (int i = 0; i < carCount; i++) {
-      cars[i] = new Car();
+  public CarRacingGameSimulator(CarCount carCount, CarMovingStrategy carMovingStrategy) {
+    for (int i = 0; i < carCount.getCount(); i++) {
+      cars.add(new Car(carMovingStrategy));
     }
   }
 
-  public void tryMoveCars() {
-    for (Car car : cars) {
-      tryMoveCar(car);
+  public List<List<Integer>> run(SimulateCount simulateCount) {
+    List<List<Integer>> res = new ArrayList<>();
path: src/main/java/racing/simulator/CarRacingGameSimulator.java
user: login: gan-ta
body: 여기 [코멘트](https://github.com/next-step/java-racingcar/pull/5963#discussion_r1998720627) 에서 연관해서 언급 하나 남겼습니다!
created_at: 2025-03-17T13:22:08Z
author_association: NONE
diff_hunk: @@ -1,25 +1,31 @@
 package racing.simulator;
 
 public class Car {
+
   private int location;
+  private final CarMovingStrategy carMovingStrategy;
 
-  Car() {
+  public Car(CarMovingStrategy carMovingStrategy) {
     this.location = 0;
+    this.carMovingStrategy = carMovingStrategy;
   }
 
-  Car(Car car) {
+  public Car(Car car) {
     this.location = car.location;
+    this.carMovingStrategy = car.carMovingStrategy;
   }
 
-  void go() {
-    this.location += 1;
+  public int getLocation() {
+    return location;
   }
 
-  public String getLocationConsoleFormat() {
-    return "-".repeat(Math.max(0, location));
+  public void go() {
+    if (carMovingStrategy.canMove()) {
+      this.location += 1;
+    }
path: src/main/java/racing/simulator/Car.java
user: login: gan-ta
body: 이것도 좋네요! 더 확장성을 열어두는 구조일 것 같아서 동감하고 수정해볼게요!
created_at: 2025-03-17T13:23:05Z
author_association: NONE

PR comments:
user: login: neojjc2
created_at: 2025-03-15T22:00:27Z
body: #tag @neojjc2 
author_association: NONE
user: login: gan-ta
created_at: 2025-03-16T12:20:19Z
body: 이번 코멘트 적용하면서 많이 배워간 것 같아서 감사합니다~! 

추가적으로 이번에 피드백 적용하면서 궁금한 점이 있습니다.

제가 잘 이해하지 못하고 한 것일수도 있지만 결국 Car 클래스에 location 필드값을 get하는 getter 메서드가 구현이 되었는데요
수업때, 그렇게 꼭 하라는 것은 아니라고 이해했지만 getter, setter 를 구현하지 말고 구현해보는 것도 도움이 될 수 있다 언급해 주신 부분이 있어서요. 이번에 코멘트를 반영하면서도 느꼈지만 어쩔수 없이 getter가 필요한 부분도 있지 않나? 필요하면 구현해서 써야하지 않나 생각이 들어서 제가 가는 방향이 맞을지 궁금해서 여쭤봤습니다.

추가적으로, 조금 이상(?)한 질문일 수도 있지만 결국 getter가 생기면서 사실 이외 이번 미션이 "모든 로직에 단위 테스트를 구현한다." 라서  생성자 이외 모든 로직에 테스트 코드 구현을 했었는데 우리가 getter 메서드는 보통 테스트 코드 구현을 안해서(저만 그런거일수도 있지만..?) getter는 테스트 메서드를 안추가했는데 과제 취지에 맞는 방향으로 구현을 한것일지? 한번 생각하고 넘어가게 되어 여쭤봅니다.🙇

감사합니다!
author_association: NONE
user: login: neojjc2
created_at: 2025-03-17T00:47:26Z
body: > 이번 코멘트 적용하면서 많이 배워간 것 같아서 감사합니다~!
> 
> 추가적으로 이번에 피드백 적용하면서 궁금한 점이 있습니다.
> 
> 제가 잘 이해하지 못하고 한 것일수도 있지만 결국 Car 클래스에 location 필드값을 get하는 getter 메서드가 구현이 되었는데요 수업때, 그렇게 꼭 하라는 것은 아니라고 이해했지만 getter, setter 를 구현하지 말고 구현해보는 것도 도움이 될 수 있다 언급해 주신 부분이 있어서요. 이번에 코멘트를 반영하면서도 느꼈지만 어쩔수 없이 getter가 필요한 부분도 있지 않나? 필요하면 구현해서 써야하지 않나 생각이 들어서 제가 가는 방향이 맞을지 궁금해서 여쭤봤습니다.
> 
> 추가적으로, 조금 이상(?)한 질문일 수도 있지만 결국 getter가 생기면서 사실 이외 이번 미션이 "모든 로직에 단위 테스트를 구현한다." 라서 생성자 이외 모든 로직에 테스트 코드 구현을 했었는데 우리가 getter 메서드는 보통 테스트 코드 구현을 안해서(저만 그런거일수도 있지만..?) getter는 테스트 메서드를 안추가했는데 과제 취지에 맞는 방향으로 구현을 한것일지? 한번 생각하고 넘어가게 되어 여쭤봅니다.🙇
> 
> 감사합니다!

이런 심도있는(?) 질문 좋습니다 😄 
사실 `setter`, `getter`를 사용하지 말라는건 다소 강압(?) 적인 가이드일 수도 있는데요 😅 
이렇게 된 배경에 대한 맥락을 좀 설명 드려야 할 것 같습니다. 

일단 저는 결론부터 말씀드리자면 객체가 가지고 있는 원본 데이터의 변경이 아닌 `setter`(근데 사실 이것도 생각해보니 좀 위험하긴 하네요), 
그리고 원본데이터의 단순조회만 가능한 `getter`등에 대해서는 쓸 수 있다는 의견입니다 🙏 

중요한 것은 객체가 가지고 있는 원본데이터가 정말 원본데이터가 맞아?? 입니다.
예전에 한창 (지금도 많이 쓰고 있고 코드도 남아있습니다만) 만능 `DTO`라고 클래스 내부에 수많은 변수들과 `getter`, `setter`만 있는 
패턴이 있었는데요, 사용자로부터 입력받은 값을 (회원 가입을 예로 들자면) 담아서 `service`를 거쳐 `DB`에 저장되기 까지 한방에 
해결할 수 있는 엄청난 편의성(?)을 자랑하는 녀석이었습니다 😅 

근데 문제는 요구사항이 점점 늘어나게 되고 다변화 되면서 부터 `DTO`에 중간 중간 값을 넣었다가 빼기도 하고 
새로 `update`하기도 하면서 여기저기에 사용되다보니, 이 `DTO`를 사용하는 특정 시점의 `service`에서는 
이 안에 값이 정말 사용자가 입력한 값이 그대로 들어있는게 맞는지?? 혹은 어딘가의 `service`를 거치게 되면서
`null`로 바뀐건 아닌지를 계속 생각하면서 사용하다보니, 쓸데 없는 방어코드, 주석들이 늘어나게 됐죠.
여기에 더해져서 특정 비즈니스 인데 `DTO`에서 몇 개 데이터가 추가가 필요하기도 하고 또 안쓰는것도 있다보니
나름 리팩토링 한다면서 `DTO`를 상속받아 `SignInDto`, `SignUpDto`등등을 또 만들어서 사용합니다. 

이렇다 보니 나중이 되면 정말 수정이 어려운 코드들이 여기저기 산재 되고, 
분명이 장애날 곳이 아닌데 확인해보면 어딘가를 거쳐와서 `null`이 된 값에 접근을 해서 `NPE`가 터지기도 하고 그렇습니다. 
(지금도 이런 코드들과 사투중이구요 😄)

그리고 사실 데이터베이스 중심의 아키텍처가 빠르게 개발하고 초기엔 유지보수 하기도 쉬우니, 장점도 있긴 합니다만
> 참고 - [마이크로서비스 내부아키텍처 - 1회 : 데이터 중심 아키텍처의 문제점](https://engineering-skcc.github.io/microservice%20inner%20achitecture/inner-architecture-1/)

그 보다는 서비스가 성장하면서 단점들이 명확하고 결국 개선을 해야 하다 보니 위와 같은 강압(?)적인 가이드를 통해서 
`setter`, `getter`가 왜 필요한지, 필요 없는지, 써야 한다면 어떻게 쓰는게 맞는 것인지에 
대한 연습 과정이라고 보시면 될 것 같습니다 🙇 

이게 글로 설명 드리려니 엄청 내용이 길어졌네요 😅 
추가적으로 궁금하시거나 질문 있으시면 언제든 코멘트나 `DM`주시면
제가 바로 확인은 어렵겠지만 꼭 답변 드리도록 하겠습니다. 

고민에 도움되셨기를 바라봅니다 🙏 




author_association: NONE