From ba0cc34d8c06fff0e708ceea8de13f6bdac6fcaf Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Fri, 14 Feb 2025 19:31:30 +0900 Subject: [PATCH 1/6] [DOCS](README): add planned feature list Added a list of features to be implemented in README.md. This serves as a roadmap for the upcoming development tasks. --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0286c8..73d2745 100644 --- a/README.md +++ b/README.md @@ -1 +1,107 @@ -# java-racingcar-precourse +# java-racingCar-precourse + +## 기능 요구 사항 +초간단 자동차 경주 게임을 구현한다. + +- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. +- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. + +## 입출력 요구 사항 +**입력** +- 경주 할 자동차 이름(이름은 쉼표(,) 기준으로 구분) +``` +pobi,woni,jun +``` + +- 시도할 회수 +``` +5 +``` + +## 출력 +**각 차수별 실행 결과** +``` +pobi : -- +woni : ---- +jun : --- +``` + +**단독 우승자 안내 문구** +``` +최종 우승자 : pobi +``` + +**공동 우승자 안내 문구** +``` +최종 우승자 : pobi, jun +``` + +**실행 결과 예시** +``` +경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) +pobi,woni,jun +시도할 회수는 몇회인가요? +5 + +실행 결과 +pobi : - +woni : +jun : - + +pobi : -- +woni : - +jun : -- + +pobi : --- +woni : -- +jun : --- + +pobi : ---- +woni : --- +jun : ---- + +pobi : ----- +woni : ---- +jun : ----- + +최종 우승자 : pobi, jun +``` + +## Java - 프로그래밍 요구 사항 + +- JDK 17 버전에서 실행 가능해야 한다. **JDK 17에서 정상적으로 동작하지 않을 경우 0점 처리한다.** +- 프로그램 실행의 시작점은 `Application`의 `main()`이다. +- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. +- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다. +- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. +- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** +- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. + +### 추가된 요구 사항 + +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- 3항 연산자를 쓰지 않는다. +- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. + - 테스트 도구 사용법이 익숙하지 않다면 `test/java/study`를 참고하여 학습한 후 테스트를 구현한다. + +### 라이브러리 + +- JDK에서 제공하는 Random 및 Scanner API 대신 `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다. + - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다. + - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. + +### 사용 예시 + +- 0에서 9까지의 정수 중 한 개의 정수 반환 +``` +Randoms.pickNumberInRange(0,9); +``` \ No newline at end of file From 677963077a7895359ad9bd492d842b5a1516ce89 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Fri, 14 Feb 2025 23:32:54 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat(view):=20InputView=20=EB=B0=8F=20Outpu?= =?UTF-8?q?tView=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 입력값을 InputValidator를 통해 검증하여 유효성을 체크. - trim()을 사용해 불필요한 공백 제거 --- src/main/java/racingcar/view/InputView.java | 28 ++++++++++++++++++++ src/main/java/racingcar/view/OutputView.java | 24 +++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/main/java/racingcar/view/InputView.java create mode 100644 src/main/java/racingcar/view/OutputView.java diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000..58e30f7 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,28 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; +import racingcar.validator.InputValidator; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class InputView { + public List getCarNames() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String input = Console.readLine(); + List carNames = Arrays.stream(input.split(",")) + .map(String::trim) + .filter(name -> !name.isEmpty()) // 빈 문자열 제거 + .collect(Collectors.toList()); + InputValidator.validateCarNames(carNames); + return carNames; + } + + public int getAttemptCount() { + System.out.println("시도할 회수는 몇회인가요?"); + String input = Console.readLine(); + InputValidator.validateAttemptCount(input); + return Integer.parseInt(input); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000..fc54a04 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,24 @@ +package racingcar.view; + +import java.util.stream.Collectors; +import racingcar.domain.Car; + +import java.util.List; + +public class OutputView { + public void printStartMessage() { + System.out.println("\n실행 결과"); + } + + public void printRaceStatus(List cars) { + String raceStatus = cars.stream() + .map(car -> car.getName() + " : " + "-".repeat(Math.max(0, car.getPosition()))) + .collect(Collectors.joining("\n")); + + System.out.println(raceStatus + "\n"); + } + + public void printWinners(List winners) { + System.out.println("최종 우승자 : " + String.join(", ", winners)); + } +} \ No newline at end of file From e3b600768509deed6d9de35ceda7357be3d8f56f Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Fri, 14 Feb 2025 23:35:14 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat(validator):=20InputValidator=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동차 이름 검증 - 입력값이 비어있는 경우 - 자동차 이름이 중복되지 않도록 - 자동차 이름이 5자 이하인지 검증 - 시도 횟수 검증 - 숫자가 아닌 경우 - 입력값이 1 이상인지 검증 (0 이하인 경우 예외 발생) --- .../racingcar/validator/InputValidator.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/racingcar/validator/InputValidator.java diff --git a/src/main/java/racingcar/validator/InputValidator.java b/src/main/java/racingcar/validator/InputValidator.java new file mode 100644 index 0000000..931a7bd --- /dev/null +++ b/src/main/java/racingcar/validator/InputValidator.java @@ -0,0 +1,29 @@ +package racingcar.validator; + +import java.util.List; + +public class InputValidator { + public static void validateCarNames(List carNames) { + if (carNames.isEmpty()) { + throw new IllegalArgumentException("[ERROR] 자동차 이름을 입력해야 합니다."); + } + if (carNames.size() != carNames.stream().distinct().count()) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 중복될 수 없습니다."); + } + + if (carNames.stream().anyMatch(name -> name.length() > 5)) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 5자 이하만 가능합니다."); + } + } + + public static void validateAttemptCount(String input) { + try { + int count = Integer.parseInt(input); + if (count <= 0) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 1 이상이어야 합니다."); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("[ERROR] 유효한 숫자를 입력해야 합니다."); + } + } +} \ No newline at end of file From 250378e837ae429e03867d298356bf6417bd5560 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Fri, 14 Feb 2025 23:41:54 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat(controller):=20=ED=95=B5=EC=8B=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모델 대신 서비스와 도메인으로 로직을 분리 - service: 게임 전체 흐름 관리 - domain: car & race 객체가 데이터+동작이 포함되게하여 개별적으로 작동하게 구현 - util - 자동차가 움직일 확률 결정하는 랜덤 숫자 생성기 구현 --- .../controller/RacingGameController.java | 23 ++++++++++++ src/main/java/racingcar/domain/Car.java | 33 +++++++++++++++++ src/main/java/racingcar/domain/Race.java | 36 +++++++++++++++++++ .../java/racingcar/service/RacingService.java | 33 +++++++++++++++++ .../racingcar/util/RandomNumberGenerator.java | 14 ++++++++ 5 files changed, 139 insertions(+) create mode 100644 src/main/java/racingcar/controller/RacingGameController.java create mode 100644 src/main/java/racingcar/domain/Car.java create mode 100644 src/main/java/racingcar/domain/Race.java create mode 100644 src/main/java/racingcar/service/RacingService.java create mode 100644 src/main/java/racingcar/util/RandomNumberGenerator.java diff --git a/src/main/java/racingcar/controller/RacingGameController.java b/src/main/java/racingcar/controller/RacingGameController.java new file mode 100644 index 0000000..f3e93b2 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingGameController.java @@ -0,0 +1,23 @@ +package racingcar.controller; + +import racingcar.service.RacingService; +import racingcar.view.InputView; + +import java.util.List; + +public class RacingGameController { + private final InputView inputView; + private final RacingService racingService; + + public RacingGameController(InputView inputView, RacingService racingService) { + this.inputView = inputView; + this.racingService = racingService; + } + + public void run() { + List carNames = inputView.getCarNames(); + int attemptCount = inputView.getAttemptCount(); + + racingService.startRace(carNames, attemptCount); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/domain/Car.java b/src/main/java/racingcar/domain/Car.java new file mode 100644 index 0000000..8a329f1 --- /dev/null +++ b/src/main/java/racingcar/domain/Car.java @@ -0,0 +1,33 @@ +package racingcar.domain; + +import racingcar.util.RandomNumberGenerator; + +public class Car { + private final String name; + private int position; + private final RandomNumberGenerator randomNumberGenerator; + + public Car(String name, RandomNumberGenerator randomNumberGenerator) { + this.name = name; + this.position = 0; + this.randomNumberGenerator = randomNumberGenerator; + } + + public void move() { + move(randomNumberGenerator.canMove()); // 기본적으로 랜덤한 이동 + } + + public void move(boolean canMove) { + if (canMove) { + position++; + } + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/domain/Race.java b/src/main/java/racingcar/domain/Race.java new file mode 100644 index 0000000..b1e697d --- /dev/null +++ b/src/main/java/racingcar/domain/Race.java @@ -0,0 +1,36 @@ +package racingcar.domain; + +import racingcar.util.RandomNumberGenerator; + +import java.util.List; +import java.util.stream.Collectors; + +public class Race { + private final List cars; + + public Race(List carNames, RandomNumberGenerator randomNumberGenerator) { + this.cars = carNames.stream() + .map(name -> new Car(name, randomNumberGenerator)) + .collect(Collectors.toList()); + } + + public void moveCars() { + cars.forEach(Car::move); + } + + public List getCars() { + return cars; + } + + public List getWinners() { + int maxPosition = cars.stream() + .mapToInt(Car::getPosition) + .max() + .orElse(0); + + return cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/service/RacingService.java b/src/main/java/racingcar/service/RacingService.java new file mode 100644 index 0000000..39b9397 --- /dev/null +++ b/src/main/java/racingcar/service/RacingService.java @@ -0,0 +1,33 @@ +package racingcar.service; + +import racingcar.domain.Race; +import racingcar.util.RandomNumberGenerator; +import racingcar.view.OutputView; + +import java.util.List; +import java.util.stream.IntStream; + +public class RacingService { + private final RandomNumberGenerator randomNumberGenerator; + private final OutputView outputView; + + public RacingService(RandomNumberGenerator randomNumberGenerator, OutputView outputView) { + this.randomNumberGenerator = randomNumberGenerator; + this.outputView = outputView; + } + + public void startRace(List carNames, int attemptCount) { + Race race = new Race(carNames, randomNumberGenerator); + outputView.printStartMessage(); + runRace(race, attemptCount); + outputView.printWinners(race.getWinners()); + } + + private void runRace(Race race, int attemptCount) { + IntStream.range(0, attemptCount) + .forEach(i -> { + race.moveCars(); + outputView.printRaceStatus(race.getCars()); + }); + } +} \ No newline at end of file diff --git a/src/main/java/racingcar/util/RandomNumberGenerator.java b/src/main/java/racingcar/util/RandomNumberGenerator.java new file mode 100644 index 0000000..c4080de --- /dev/null +++ b/src/main/java/racingcar/util/RandomNumberGenerator.java @@ -0,0 +1,14 @@ +package racingcar.util; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RandomNumberGenerator { + private static final int MIN = 0; + private static final int MAX = 9; + private static final int MOVE_THRESHOLD = 4; + + public boolean canMove() { + int randomValue = Randoms.pickNumberInRange(MIN, MAX); + return randomValue >= MOVE_THRESHOLD; + } +} \ No newline at end of file From f6acabd4c1ceace8deb266babfdd9d27e49a7589 Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Fri, 14 Feb 2025 23:42:59 +0900 Subject: [PATCH 5/6] =?UTF-8?q?chore:=20AppConfig=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DI 적용 및 의존성 주입 --- src/main/java/racingcar/Application.java | 9 ++++++-- src/main/java/racingcar/config/AppConfig.java | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/main/java/racingcar/config/AppConfig.java diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..87b312c 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,12 @@ package racingcar; +import racingcar.config.AppConfig; +import racingcar.controller.RacingGameController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + AppConfig appConfig = new AppConfig(); + RacingGameController controller = appConfig.getRacingGameController(); + controller.run(); } -} +} \ No newline at end of file diff --git a/src/main/java/racingcar/config/AppConfig.java b/src/main/java/racingcar/config/AppConfig.java new file mode 100644 index 0000000..59fbc35 --- /dev/null +++ b/src/main/java/racingcar/config/AppConfig.java @@ -0,0 +1,23 @@ +package racingcar.config; + +import racingcar.controller.RacingGameController; +import racingcar.service.RacingService; +import racingcar.util.RandomNumberGenerator; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class AppConfig { + private final RacingGameController racingGameController; + + public AppConfig() { + RandomNumberGenerator randomNumberGenerator = new RandomNumberGenerator(); + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + RacingService racingService = new RacingService(randomNumberGenerator, outputView); + this.racingGameController = new RacingGameController(inputView, racingService); + } + + public RacingGameController getRacingGameController() { + return racingGameController; + } +} \ No newline at end of file From 024bc9260388f23080c7d0c0d1c5cee12ddf6f2a Mon Sep 17 00:00:00 2001 From: kim nan seul <154604350+seulnan@users.noreply.github.com> Date: Fri, 14 Feb 2025 23:52:51 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor(validator):=20InputValidator=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 중복된 코드 및 가독성이 떨어지는 로직을 별도 메서드로 분리 - 자동차 이름이 영어 또는 숫자가 아닐 경우 예외 추가 - 시도 횟수가 숫자가 아닐 경우 예외 추가 - 유닛 테스트 및 통합 테스트 코드 추가 --- .../racingcar/validator/InputValidator.java | 31 +++++--- src/test/java/racingcar/ApplicationTest.java | 11 +++ .../java/racingcar/InputValidatorTest.java | 71 +++++++++++++++++++ 3 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 src/test/java/racingcar/InputValidatorTest.java diff --git a/src/main/java/racingcar/validator/InputValidator.java b/src/main/java/racingcar/validator/InputValidator.java index 931a7bd..0e7adc4 100644 --- a/src/main/java/racingcar/validator/InputValidator.java +++ b/src/main/java/racingcar/validator/InputValidator.java @@ -7,23 +7,36 @@ public static void validateCarNames(List carNames) { if (carNames.isEmpty()) { throw new IllegalArgumentException("[ERROR] 자동차 이름을 입력해야 합니다."); } - if (carNames.size() != carNames.stream().distinct().count()) { + if (hasDuplicateNames(carNames)) { throw new IllegalArgumentException("[ERROR] 자동차 이름은 중복될 수 없습니다."); } - - if (carNames.stream().anyMatch(name -> name.length() > 5)) { + if (hasInvalidLength(carNames)) { throw new IllegalArgumentException("[ERROR] 자동차 이름은 5자 이하만 가능합니다."); } + if (carNames.stream().anyMatch(name -> !name.matches("^[a-zA-Z0-9]*$"))) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 영어 또는 숫자만 가능합니다."); + } } public static void validateAttemptCount(String input) { - try { - int count = Integer.parseInt(input); - if (count <= 0) { - throw new IllegalArgumentException("[ERROR] 시도 횟수는 1 이상이어야 합니다."); - } - } catch (NumberFormatException e) { + if (!isNumeric(input)) { throw new IllegalArgumentException("[ERROR] 유효한 숫자를 입력해야 합니다."); } + int count = Integer.parseInt(input); + if (count <= 0) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 1 이상이어야 합니다."); + } + } + + private static boolean hasDuplicateNames(List carNames) { + return carNames.size() != carNames.stream().distinct().count(); + } + + private static boolean hasInvalidLength(List carNames) { + return carNames.stream().anyMatch(name -> name.length() > 5); + } + + private static boolean isNumeric(String input) { + return input.matches("\\d+"); } } \ No newline at end of file diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc3..06d1aed 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -2,6 +2,7 @@ import camp.nextstep.edu.missionutils.test.NsTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest; import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; @@ -31,6 +32,16 @@ class ApplicationTest extends NsTest { ); } + @Test + @DisplayName("시도 횟수가 숫자가 아닐 경우 예외 발생") + void 시도_횟수_숫자_예외() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", "abc")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 유효한 숫자를 입력해야 합니다.") + ); + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/racingcar/InputValidatorTest.java b/src/test/java/racingcar/InputValidatorTest.java new file mode 100644 index 0000000..cc7e6a9 --- /dev/null +++ b/src/test/java/racingcar/InputValidatorTest.java @@ -0,0 +1,71 @@ +package racingcar; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import racingcar.validator.InputValidator; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class InputValidatorTest { + + @Test + @DisplayName("자동차 이름이 비어있을 경우 예외 발생") + void validateCarNames_emptyList_throwsException() { + List emptyList = List.of(); + assertThatThrownBy(() -> InputValidator.validateCarNames(emptyList)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 자동차 이름을 입력해야 합니다."); + } + + @Test + @DisplayName("자동차 이름이 중복될 경우 예외 발생") + void validateCarNames_duplicateNames_throwsException() { + List carNames = Arrays.asList("pobi", "woni", "pobi"); + assertThatThrownBy(() -> InputValidator.validateCarNames(carNames)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 자동차 이름은 중복될 수 없습니다."); + } + + @Test + @DisplayName("자동차 이름이 5자를 초과할 경우 예외 발생") + void validateCarNames_tooLongNames_throwsException() { + List carNames = Arrays.asList("pobiii", "woni"); + assertThatThrownBy(() -> InputValidator.validateCarNames(carNames)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 자동차 이름은 5자 이하만 가능합니다."); + } + + @Test + @DisplayName("자동차 이름에 특수 문자가 포함될 경우 예외 발생") + void validateCarNames_invalidCharacters_throwsException() { + List carNames = Arrays.asList("pobi@", "woni123"); + assertThatThrownBy(() -> InputValidator.validateCarNames(carNames)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 자동차 이름은 영어 또는 숫자만 가능합니다."); + } + + @Test + @DisplayName("시도 횟수가 숫자가 아닐 경우 예외 발생") + void validateAttemptCount_nonNumeric_throwsException() { + assertThatThrownBy(() -> InputValidator.validateAttemptCount("abc")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 유효한 숫자를 입력해야 합니다."); + } + + @Test + @DisplayName("시도 횟수가 0 이하일 경우 예외 발생") + void validateAttemptCount_zeroOrNegative_throwsException() { + assertThatThrownBy(() -> InputValidator.validateAttemptCount("0")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 시도 횟수는 1 이상이어야 합니다."); + } + + @Test + @DisplayName("시도 횟수가 정상적인 경우 예외가 발생하지 않음") + void validateAttemptCount_validInput_noException() { + InputValidator.validateAttemptCount("5"); + } +} \ No newline at end of file