diff --git a/README.md b/README.md new file mode 100644 index 00000000..14d24702 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# 🏎️ μžλ™μ°¨ κ²½μ£Ό κ²Œμž„ (Car Racing) + +## πŸš€ κΈ°λŠ₯ κ΅¬ν˜„ λͺ©λ‘ + +### 1. μž…λ ₯ 및 데이터 처리 +- [ ] **μžλ™μ°¨ 이름 μž…λ ₯**: μ‰Όν‘œ(`,`)λ₯Ό κΈ°μ€€μœΌλ‘œ κ΅¬λΆ„ν•˜μ—¬ 경주에 μ°Έμ—¬ν•  μžλ™μ°¨ 이름을 μž…λ ₯λ°›λŠ”λ‹€. +- [ ] **데이터 λ³€ν™˜**: μž…λ ₯된 λ¬Έμžμ—΄μ„ λΆ„λ¦¬ν•˜μ—¬ `Car` 객체 리슀트둜 μƒμ„±ν•œλ‹€. +- [ ] **μ‹œλ„ 횟수 μž…λ ₯**: 전체 μžλ™μ°¨κ°€ 이동을 μ‹œλ„ν•  총 횟수λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. + +### 2. λ ˆμ΄μ‹± 둜직 +- [ ] **μ „μ§„ 쑰건 확인**: λ§€ λΌμš΄λ“œλ§ˆλ‹€ 각 μžλ™μ°¨λ³„λ‘œ λ¬΄μž‘μœ„ 값을 μƒμ„±ν•˜μ—¬ μ „μ§„ μ—¬λΆ€λ₯Ό κ²°μ •ν•œλ‹€. +- [ ] **μœ„μΉ˜ μ—…λ°μ΄νŠΈ**: μ „μ§„ 쑰건을 λ§Œμ‘±ν•˜λŠ” 경우 μžλ™μ°¨μ˜ μœ„μΉ˜λ₯Ό $1$ μ”© μ¦κ°€μ‹œν‚¨λ‹€. +- [ ] **우승자 νŒλ³„**: λͺ¨λ“  λΌμš΄λ“œ μ’…λ£Œ ν›„ μ „μ§„ 거리가 κ°€μž₯ κΈ΄ μžλ™μ°¨λ₯Ό 우승자둜 μ„ μ •ν•œλ‹€. (곡동 우승 κ°€λŠ₯) + +### 3. 좜λ ₯ +- [ ] **λΌμš΄λ“œ κ²°κ³Ό**: λ§€ λΌμš΄λ“œ μ’…λ£Œ μ‹œμ μ˜ μžλ™μ°¨λ³„ 이름과 μ „μ§„ μƒνƒœ(`-`)λ₯Ό 좜λ ₯ν•œλ‹€. +- [ ] **μ΅œμ’… 우승자**: κ²½μ£Ό μ’…λ£Œ ν›„ μ΅œμ’… 우승자의 이름을 좜λ ₯ν•œλ‹€. (곡동 우승 μ‹œ μ‰Όν‘œλ‘œ ꡬ뢄) + +--- + +## ⚠️ μ˜ˆμ™Έ 처리 κ·œμΉ™ (Exception Handling) +잘λͺ»λœ κ°’ μž…λ ₯ μ‹œ `IllegalArgumentException`을 λ°œμƒμ‹œν‚€λ©°, ν”„λ‘œκ·Έλž¨μ€ μ¦‰μ‹œ μ’…λ£Œλ˜κ±°λ‚˜ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•΄μ•Ό ν•œλ‹€. + +### [μžλ™μ°¨ 이름 κ΄€λ ¨] +1. **ν˜•μ‹ 였λ₯˜**: μ•ŒνŒŒλ²³κ³Ό ν•œκΈ€ μ΄μ™Έμ˜ 문자(특수문자, 숫자 λ“±)κ°€ ν¬ν•¨λœ 경우. +2. **곡백 포함**: 이름 내뢀에 곡백이 μžˆκ±°λ‚˜, μž…λ ₯값이 곡백으둜만 κ΅¬μ„±λœ 경우. +3. **μž…λ ₯ λΆ€μž¬**: μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ§€ μ•Šκ³  μ§„ν–‰ν•˜λ € ν•˜λŠ” 경우. +4. **쀑볡 λ°œμƒ**: λ™μΌν•œ 이름을 κ°€μ§„ μžλ™μ°¨κ°€ 리슀트 내에 μ€‘λ³΅μœΌλ‘œ μ‘΄μž¬ν•˜λŠ” 경우. + +### [μ‹œλ„ 횟수 κ΄€λ ¨] +1. **νƒ€μž… 였λ₯˜**: 숫자 μ΄μ™Έμ˜ 문자, 특수기호, 곡백이 ν¬ν•¨λœ 경우. +2. **λ²”μœ„ 였λ₯˜**: μž…λ ₯값이 $0$ μ΄ν•˜μ˜ μ •μˆ˜μΈ 경우 (μ΅œμ†Œ $1$ 회 이상 ν•„μš”). +3. **λ―Έμž…λ ₯**: μ‹œλ„ 횟수λ₯Ό μž…λ ₯ν•˜μ§€ μ•Šκ³  μ—”ν„°λ₯Ό μž…λ ₯ν•œ 경우. + +--- + +## πŸ’» ν”„λ‘œκ·Έλž¨ μ§„ν–‰ 방식 +1. **[Step 1]** `InputView`λ₯Ό ν†΅ν•œ μžλ™μ°¨ 이름 및 μ‹œλ„ 횟수 μž…λ ₯. +2. **[Step 2]** μž…λ ₯ 데이터 검증 및 `List` 객체 생성. +3. **[Step 3]** μ„€μ •λœ 횟수만큼 κ²½μ£Ό μ‹€ν–‰ 및 μ‹€μ‹œκ°„ κ²°κ³Ό 좜λ ₯. +4. **[Step 4]** μ΅œμ’… 우승자 μ—°μ‚° 및 `OutputView`λ₯Ό ν†΅ν•œ κ²°κ³Ό λ°œν‘œ. \ No newline at end of file diff --git a/src/main/java/racing/Application.java b/src/main/java/racing/Application.java new file mode 100644 index 00000000..04012476 --- /dev/null +++ b/src/main/java/racing/Application.java @@ -0,0 +1,9 @@ +package racing; +import racing.controller.Controller; + +public class Application { + public static void main(String[] args) { + Controller controller = new Controller(); + controller.run(); + } +} diff --git a/src/main/java/racing/controller/Controller.java b/src/main/java/racing/controller/Controller.java new file mode 100644 index 00000000..289b06a4 --- /dev/null +++ b/src/main/java/racing/controller/Controller.java @@ -0,0 +1,43 @@ +package racing.controller; + +import racing.view.InputView; + +import racing.domain.Cars; +import racing.domain.Race; +import racing.domain.Car; +import racing.view.OutputView; +import java.util.List; +import java.util.stream.Collectors; + +import racing.domain.RandomMoveStrategy; + +public class Controller { + public void run() { + // [1] μžλ™μ°¨ 이름 및 μ‹œλ„ 횟수 μž…λ ₯ λ°›κΈ° + OutputView.printInputCarNameMessage(); + String carNameInput = InputView.inputCarName(); + + OutputView.printInputTrialCountMessage(); + int trialNumber = InputView.inputTrialNumberCount(); + // [2] 데이터 λ³€ν™˜: λ¬Έμžμ—΄ -> μžλ™μ°¨ 객체 리슀트 + List carList = InputView.parse(carNameInput); + List raceCarList = carList.stream() + .map(Car::new) + .collect(Collectors.toList()); + Cars cars = new Cars(raceCarList); + + Race race = new Race(cars,trialNumber,new RandomMoveStrategy()); + //[3] λ ˆμ΄μ”½ κ²½κΈ° μ‹œμž‘ + OutputView.println(); + OutputView.printExecutionResultMessage(); + + while (race.hasMoreRounds()) { + race.playRound(); + OutputView.printRoundResult(cars.getCarList()); + + } + //[4] κ²°κ³Ό 좜λ ₯ + OutputView.printWinners(race.getWinners()); + + } +} diff --git a/src/main/java/racing/domain/Car.java b/src/main/java/racing/domain/Car.java new file mode 100644 index 00000000..6badf1f8 --- /dev/null +++ b/src/main/java/racing/domain/Car.java @@ -0,0 +1,41 @@ +package racing.domain; + +public class Car { + private final String name; + private int position; + + public Car(String name) { + validateName(name); + this.name = name; + this.position = 0; + } + + private void validateName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("[ERROR] μžλ™μ°¨ 이름은 1자 이상이어야 ν•©λ‹ˆλ‹€."); + } + if (name.length() > 5) { + throw new IllegalArgumentException("[ERROR] μžλ™μ°¨ 이름은 5자 μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + if (name.contains(" ")) { + throw new IllegalArgumentException("[ERROR] μžλ™μ°¨ 이름에 곡백을 포함할 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + if (!name.matches("^[a-zA-Z0-9κ°€-힣]*$")) { + throw new IllegalArgumentException("[ERROR] μžλ™μ°¨ 이름에 특수문자λ₯Ό 포함할 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } + + public void move() { + this.position++; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + +} diff --git a/src/main/java/racing/domain/Cars.java b/src/main/java/racing/domain/Cars.java new file mode 100644 index 00000000..8b48bbc5 --- /dev/null +++ b/src/main/java/racing/domain/Cars.java @@ -0,0 +1,38 @@ +package racing.domain; + +import java.util.Collections; +import java.util.List; + +public class Cars { + private final List cars; + + public Cars(List cars) { + this.cars = cars; + validateDuplicate(); + } + + private void validateDuplicate() { + long distinctCount = cars.stream() + .map(Car::getName) + .distinct() + .count(); + if (distinctCount != cars.size()) { + throw new IllegalArgumentException("[ERROR] μ€‘λ³΅λœ μžλ™μ°¨ 이름이 μ‘΄μž¬ν•©λ‹ˆλ‹€."); + } + } + public void moveAll(MoveStrategy moveStrategy){ + for (Car car : cars) { + if (moveStrategy.isMovable()) { + car.move(); + } + } + } + + // λΆˆλ³€μ„± 보μž₯: μ™ΈλΆ€μ—μ„œ 리슀트λ₯Ό μˆ˜μ •ν•˜μ§€ λͺ»ν•˜λ„둝 읽기 μ „μš© λ·° λ°˜ν™˜ + public List getCarList() { + return Collections.unmodifiableList(cars); + } + + + +} diff --git a/src/main/java/racing/domain/MoveStrategy.java b/src/main/java/racing/domain/MoveStrategy.java new file mode 100644 index 00000000..94eca260 --- /dev/null +++ b/src/main/java/racing/domain/MoveStrategy.java @@ -0,0 +1,5 @@ +package racing.domain; + +public interface MoveStrategy { + boolean isMovable(); +} diff --git a/src/main/java/racing/domain/Race.java b/src/main/java/racing/domain/Race.java new file mode 100644 index 00000000..41301032 --- /dev/null +++ b/src/main/java/racing/domain/Race.java @@ -0,0 +1,45 @@ +package racing.domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class Race { + private final Cars cars; + private final int totalRounds; + private int currentRound; + private final MoveStrategy moveStrategy; + + public Race(Cars cars, int totalRounds, MoveStrategy moveStrategy) { + if (totalRounds <= 0) { + throw new IllegalArgumentException("[ERROR] μ‹œλ„ νšŸμˆ˜λŠ” 1 이상이어야 ν•©λ‹ˆλ‹€."); + } + this.cars = cars; + this.totalRounds = totalRounds; + this.currentRound = 0; + this.moveStrategy = moveStrategy; + } + + public boolean hasMoreRounds() { + return currentRound < totalRounds; + } + + public void playRound() { + if (!hasMoreRounds()) { + throw new IllegalStateException("[ERROR] 이미 λͺ¨λ“  λΌμš΄λ“œκ°€ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + } + cars.moveAll(moveStrategy); + currentRound++; + } + + public List getWinners() { + int maxPosition = cars.getCarList().stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + + return cars.getCarList().stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/racing/domain/RandomMoveStrategy.java b/src/main/java/racing/domain/RandomMoveStrategy.java new file mode 100644 index 00000000..1cc4f1a9 --- /dev/null +++ b/src/main/java/racing/domain/RandomMoveStrategy.java @@ -0,0 +1,23 @@ + package racing.domain; + + import java.util.Random; + + public class RandomMoveStrategy implements MoveStrategy { + private static final int RANDOM_RANGE = 10; + private static final int MOVE_THRESHOLD = 4; + private final Random random; + + public RandomMoveStrategy() { + this(new Random()); + } + + public RandomMoveStrategy(Random random) { + this.random = random; + } + + @Override + public boolean isMovable() { + return random.nextInt(RANDOM_RANGE) >= MOVE_THRESHOLD; + } + } + diff --git a/src/main/java/racing/view/InputView.java b/src/main/java/racing/view/InputView.java new file mode 100644 index 00000000..6a382ede --- /dev/null +++ b/src/main/java/racing/view/InputView.java @@ -0,0 +1,57 @@ +package racing.view; + +import racing.domain.Car; + +import java.util.Scanner; + + + + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + + + +public class InputView { + private static final Scanner SCANNER = new Scanner(System.in); + + public static String inputCarName() { + String carName = SCANNER.nextLine(); + validateCarNameFormat(carName); + return carName; + } + + public static int inputTrialNumberCount() { + String input = SCANNER.nextLine(); + return parseTrialCount(input); + } + + private static void validateCarNameFormat(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("[ERROR] μž…λ ₯값이 μ—†μŠ΅λ‹ˆλ‹€."); + } + String noSpaceInput = input.replace(" ", ""); + if (noSpaceInput.contains(",,")) { + throw new IllegalArgumentException("[ERROR] μ‰Όν‘œκ°€ μ—°μ†μœΌλ‘œ μž…λ ₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + } + if (input.startsWith(",") || input.endsWith(",")) { + throw new IllegalArgumentException("[ERROR] μž…λ ₯κ°’μ˜ μ‹œμž‘μ΄λ‚˜ 끝에 μ‰Όν‘œκ°€ μžˆμŠ΅λ‹ˆλ‹€."); + } + } + + private static int parseTrialCount(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("[ERROR] μ‹œλ„ νšŸμˆ˜λŠ” μ •μˆ˜ ν˜•μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } + + public static List parse(String input) { + return Arrays.stream(input.split(",")) + .map(String::trim) // 양끝 곡백 제거 + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/racing/view/OutputView.java b/src/main/java/racing/view/OutputView.java new file mode 100644 index 00000000..44b07522 --- /dev/null +++ b/src/main/java/racing/view/OutputView.java @@ -0,0 +1,42 @@ +package racing.view; + +import racing.domain.Car; +import java.util.List; + +public class OutputView { + private static final String INPUT_CAR_NAME_MESSAGE = "κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”(이름은 μ‰Όν‘œ(,)λ₯Ό κΈ°μ€€μœΌλ‘œ ꡬ뢄)."; + private static final String INPUT_CAR_COUNT_MESSAGE = "μ‹œλ„ν•  νšŒμˆ˜λŠ” λͺ‡νšŒμΈκ°€μš”?"; + private static final String EXECUTION_RESULT_MESSAGE = "μ‹€ν–‰ κ²°κ³Ό"; + + public static void printInputCarNameMessage() { + System.out.println(INPUT_CAR_NAME_MESSAGE); + } + + public static void printInputTrialCountMessage() { + System.out.println(INPUT_CAR_COUNT_MESSAGE); + } + + public static void printExecutionResultMessage() { + System.out.println(EXECUTION_RESULT_MESSAGE); + } + + public static void printRoundResult(List cars) { + for (Car car : cars) { + System.out.println(car.getName() + " : " + "-".repeat(car.getPosition())); + } + System.out.println(""); + + } + + public static void printWinners(List winnerNames) { + if (winnerNames == null || winnerNames.isEmpty()) { + return; + } + String result = String.join(", ", winnerNames); + System.out.println(result + "κ°€ μ΅œμ’… μš°μŠΉν–ˆμŠ΅λ‹ˆλ‹€."); + } + public static void println() { + System.out.println(); + } + +} diff --git a/src/test/java/racing/domain/CarTest.java b/src/test/java/racing/domain/CarTest.java new file mode 100644 index 00000000..23b4c429 --- /dev/null +++ b/src/test/java/racing/domain/CarTest.java @@ -0,0 +1,64 @@ +package racing.domain; + +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.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CarTest { + + @Test + void μœ νš¨ν•œ_μ΄λ¦„μœΌλ‘œ_μžλ™μ°¨λ₯Ό_μƒμ„±ν•˜λ©΄_초기_μœ„μΉ˜λŠ”_0이닀() { + Car car = new Car("pobi"); + + assertThat(car.getName()).isEqualTo("pobi"); + assertThat(car.getPosition()).isEqualTo(0); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void 이름이_λΉ„μ–΄μžˆκ±°λ‚˜_곡백만_있으면_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€(String invalidName) { + assertThatThrownBy(() -> new Car(invalidName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μžλ™μ°¨ 이름은 1자 이상이어야 ν•©λ‹ˆλ‹€."); + } + + @Test + void 이름이_null이면_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + assertThatThrownBy(() -> new Car(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μžλ™μ°¨ 이름은 1자 이상이어야 ν•©λ‹ˆλ‹€."); + } + + @Test + void 이름이_5자λ₯Ό_μ΄ˆκ³Όν•˜λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + assertThatThrownBy(() -> new Car("abcdef")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μžλ™μ°¨ 이름은 5자 μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @Test + void 이름에_곡백이_ν¬ν•¨λ˜λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + assertThatThrownBy(() -> new Car("po bi")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μžλ™μ°¨ 이름에 곡백을 포함할 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + @ParameterizedTest + @ValueSource(strings = {"pobi!", "crong@", "honx#"}) + void 이름에_νŠΉμˆ˜λ¬Έμžκ°€_ν¬ν•¨λ˜λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€(String invalidName) { + assertThatThrownBy(() -> new Car(invalidName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μžλ™μ°¨ 이름에 특수문자λ₯Ό 포함할 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + @Test + void move_ν˜ΈμΆœμ‹œ_μœ„μΉ˜κ°€_1_μ¦κ°€ν•˜μ§€_μ•ŠλŠ”λ‹€λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + Car car = new Car("pobi"); + car.move(); + + assertThat(car.getPosition()).isEqualTo(1); + } +} \ No newline at end of file diff --git a/src/test/java/racing/domain/CarsTest.java b/src/test/java/racing/domain/CarsTest.java new file mode 100644 index 00000000..52779514 --- /dev/null +++ b/src/test/java/racing/domain/CarsTest.java @@ -0,0 +1,39 @@ +package racing.domain; + +import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CarsTest { + + @Test + void μ€‘λ³΅λœ_이름이_있으면_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + List carList = List.of(new Car("pobi"), new Car("pobi")); + + assertThatThrownBy(() -> new Cars(carList)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μ€‘λ³΅λœ μžλ™μ°¨ 이름이 μ‘΄μž¬ν•©λ‹ˆλ‹€."); + } + + @Test + void moveAll_ν˜ΈμΆœμ‹œ_μ „λ‹¬λœ_μ „λž΅μ΄_참이면_λͺ¨λ“ _μžλ™μ°¨κ°€_μ΄λ™ν•˜μž_μ•ŠλŠ”λ‹€λ©΄_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€() { + Cars cars = new Cars(List.of(new Car("pobi"), new Car("crong"))); + + cars.moveAll(() -> true); + + assertThat(cars.getCarList().get(0).getPosition()).isEqualTo(1); + assertThat(cars.getCarList().get(1).getPosition()).isEqualTo(1); + } + + @Test + void moveAll_ν˜ΈμΆœμ‹œ_μ „λ‹¬λœ_μ „λž΅μ΄_거짓이면_λͺ¨λ“ _μžλ™μ°¨κ°€_μ΄λ™ν•˜μ§€_μ•ŠλŠ”λ‹€λ©΄_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€() { + Cars cars = new Cars(List.of(new Car("pobi"), new Car("crong"))); + + cars.moveAll(() -> false); + + assertThat(cars.getCarList().get(0).getPosition()).isEqualTo(0); + assertThat(cars.getCarList().get(1).getPosition()).isEqualTo(0); + } +} \ No newline at end of file diff --git a/src/test/java/racing/domain/RaceTest.java b/src/test/java/racing/domain/RaceTest.java new file mode 100644 index 00000000..aaed994f --- /dev/null +++ b/src/test/java/racing/domain/RaceTest.java @@ -0,0 +1,59 @@ +package racing.domain; + +import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RaceTest { + + @Test + void μ‹œλ„_νšŸμˆ˜κ°€_0_μ΄ν•˜λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + Cars cars = new Cars(List.of(new Car("pobi"))); + + assertThatThrownBy(() -> new Race(cars, 0, () -> true)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] μ‹œλ„ νšŸμˆ˜λŠ” 1 이상이어야 ν•©λ‹ˆλ‹€."); + } + + @Test + void λͺ¨λ“ _λΌμš΄λ“œκ°€_μ’…λ£Œλœ_ν›„_playRoundλ₯Ό_ν˜ΈμΆœν•˜λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { + Cars cars = new Cars(List.of(new Car("pobi"))); + Race race = new Race(cars, 1, () -> true); + + race.playRound(); // 1λΌμš΄λ“œ μ§„ν–‰ μ™„λ£Œ + + assertThatThrownBy(race::playRound) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("[ERROR] 이미 λͺ¨λ“  λΌμš΄λ“œκ°€ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."); + } + + @Test + void 단독_우승자λ₯Ό_μ •ν™•νžˆ_λ°˜ν™˜ν•œλ‹€() { + Car pobi = new Car("pobi"); + Car crong = new Car("crong"); + pobi.move(); // pobi만 1μΉΈ 이동 + + Cars cars = new Cars(List.of(pobi, crong)); + Race race = new Race(cars, 1, () -> true); + + List winners = race.getWinners(); + + assertThat(winners).containsExactly("pobi"); + } + + @Test + void 곡동_우승자λ₯Ό_μ •ν™•νžˆ_λ°˜ν™˜ν•œλ‹€() { + Car pobi = new Car("pobi"); + Car crong = new Car("crong"); + // λ‘˜ λ‹€ 0μΉΈ 이동 μƒνƒœ (동점) + + Cars cars = new Cars(List.of(pobi, crong)); + Race race = new Race(cars, 1, () -> true); + + List winners = race.getWinners(); + + assertThat(winners).containsExactly("pobi", "crong"); + } +} \ No newline at end of file diff --git a/src/test/java/racing/domain/RandomMoveStrategyTest.java b/src/test/java/racing/domain/RandomMoveStrategyTest.java new file mode 100644 index 00000000..3ba03740 --- /dev/null +++ b/src/test/java/racing/domain/RandomMoveStrategyTest.java @@ -0,0 +1,38 @@ +package racing.domain; + +import org.junit.jupiter.api.Test; +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +class RandomMoveStrategyTest { + + @Test + void μΆ”μΆœλœ_랜덀_값이_4_이상이면_trueλ₯Ό_λ°˜ν™˜ν•˜μ§€_μ•ŠλŠ”λ‹€λ©΄_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€() { + // Mock 객체 λŒ€μ‹  읡λͺ… 클래슀λ₯Ό ν™œμš©ν•˜μ—¬ 결정둠적 ν…ŒμŠ€νŠΈ 보μž₯ + Random predictableRandom = new Random() { + @Override + public int nextInt(int bound) { + return 4; // 경계값 + } + }; + + MoveStrategy strategy = new RandomMoveStrategy(predictableRandom); + + assertThat(strategy.isMovable()).isTrue(); + } + + @Test + void μΆ”μΆœλœ_랜덀_값이_4_미만이면_falseλ₯Ό_λ°˜ν™˜ν•˜μ§€_μ•ŠλŠ”λ‹€λ©΄_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€() { + Random predictableRandom = new Random() { + @Override + public int nextInt(int bound) { + return 3; // 경계값 미만 + } + }; + + MoveStrategy strategy = new RandomMoveStrategy(predictableRandom); + + assertThat(strategy.isMovable()).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/racing/view/InputViewTest.java b/src/test/java/racing/view/InputViewTest.java new file mode 100644 index 00000000..45221f84 --- /dev/null +++ b/src/test/java/racing/view/InputViewTest.java @@ -0,0 +1,40 @@ +package racing.view; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class InputViewTest { + + private final InputStream standardIn = System.in; + + @AfterEach + void tearDown() { + System.setIn(standardIn); + } + + private void provideInput(String data) { + System.setIn(new ByteArrayInputStream(data.getBytes())); + } + + @Test + + void μžλ™μ°¨_이름_ν˜•μ‹μ΄_μ˜¬λ°”λ₯΄μ§€_λͺ»ν•˜λ©΄_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€() { + provideInput("pobi,,woni\n"); + assertThatThrownBy(InputView::inputCarName) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR]"); + } + + @Test + void μ‹œλ„_νšŸμˆ˜κ°€_μˆ«μžκ°€_μ•„λ‹ˆλ©΄_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€() { + provideInput("notNumber\n"); + assertThatThrownBy(InputView::inputTrialNumberCount) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR]"); + } +}