diff --git a/README.md b/README.md
index d0286c859f..d7160a30af 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,47 @@
-# java-racingcar-precourse
+# π μλμ°¨ κ²½μ£Ό π
+
+## κΈ°λ₯ λͺ
μΈμ
+
+> π κΈ°λ₯ λͺ
μΈμλ νλ‘κ·Έλ¨ μ€ν νλ¦μ λ°λΌ μμ±νλ€.
+> βοΈ κΈ°λ₯ ꡬνμ μ£Όμ κΈ°λ₯λΆν° λ¨Όμ ꡬννλ€.
+> β οΈ κ²½μ£Όλ κ²½μ μλκ° μμ΄μΌ νλ―λ‘ μ ν¨ν μ΄λ¦μ 2κ° μ΄μ μ
λ ₯ν΄μΌ κ²μμ μ§νν μ μλ€.
+
+### κ²½μ£Όν μλμ°¨λ€μ μ΄λ¦μ μ
λ ₯λ°λλ€.
+
+- [X] μλμ°¨ μ΄λ¦ μ
λ ₯μ μμ²νλ λ©μμ§λ₯Ό μΆλ ₯νλ€.
+- [X] μ
λ ₯λ λ¬Έμμ΄μ μΌν(,)λ₯Ό κΈ°μ€μΌλ‘ ꡬλΆν΄ μ΄λ¦μ μΆμΆνλ€.
+- [X] μλͺ»λ κ°μ μ
λ ₯ν κ²½μ° `IllegalArgumentException`μ λμ§λ€.
+ - [X] μ΄λ¦μ 2κ° λ―Έλ§μΌλ‘ μ
λ ₯ν κ²½μ°
+ - [X] μ
λ ₯λ μ΄λ¦μ΄ 1μ λ―Έλ§, 5μ μ΄κ³ΌμΌ κ²½μ°
+ - [X] μ΄λ¦μ΄ μ€λ³΅μΌ κ²½μ°
+
+### μ μ§ μλ νμλ₯Ό μ
λ ₯λ°λλ€.
+
+- [X] μλ νμ μ
λ ₯ μλ΄ λ©μμ§λ₯Ό μΆλ ₯νλ€.
+- [X] μ
λ ₯λ λ¬Έμμ΄μ μμ°μλ‘ λ³ννλ€.
+- [X] μλͺ»λ κ°μ μ
λ ₯ν κ²½μ° `IllegalArgumentException`μ λμ§λ€.
+ - [X] 0~9λ‘ μ΄λ£¨μ΄μ§ λ¬Έμμ΄μ΄ μλ κ²½μ°
+ - [X] 1 λ―Έλ§μ μλ₯Ό μ
λ ₯ν κ²½μ°
+
+### μλμ°¨ κ²½μ£Όλ₯Ό μ§ννλ€.
+
+- [X] μ€ν κ²°κ³Ό 문ꡬλ₯Ό μΆλ ₯νλ€.
+- [X] μ¬μ©μκ° μ
λ ₯ν μλ νμλ§νΌ λ°λ³΅νλ€.
+ - [X] λͺ¨λ μλμ°¨κ° μ μ§μ 1λ² μλνλ€.
+ - [X] 0λΆν° 9κΉμ§μ μ μ μ€ λ¬΄μμ κ°μ 1κ° λ½λλ€.
+ - [X] 무μμ κ°μ΄ 4 μ΄μμ΄λ©΄ μ μ§νλ€.
+ - [X] ν λΌμ΄λ μ€ν κ²°κ³Όλ₯Ό μΆλ ₯νλ€.
+ - [X] 1μ€μ μλμ°¨ 1κ°μ© μ΄λ¦κ³Ό μ΄λν νμλ₯Ό μΆλ ₯νλ€.
+
+### κ²μ μ’
λ£ ν μ°μΉμλ₯Ό μ°Ύλλ€.
+
+- [X] μλμ°¨λ€μ μ΄λ νμ μ€ μ΅λκ°μ μ°Ύλλ€.
+- [X] μ΄λ νμμ μ΅λκ°μ κ°μ§ μλμ°¨λ€μ μ΄λ¦μ 리ν΄νλ€.
+
+### μ°μΉμλ₯Ό μλ €μ€λ€.
+
+- [X] μ°μΉμ μλ΄ λ¬Έκ΅¬λ₯Ό μΆλ ₯νλ€.
+ - [X] μ°μΉμμ μ΄λ¦μ μΆλ ₯νλ€.
+ - [X] 곡λ μ°μΉμλ μΌν(,)λ₯Ό μ΄μ©νμ¬ κ΅¬λΆνλ€.
+
+### `IllegalArgumentException`μ΄ λ°μνλ©΄ μ ν리μΌμ΄μ
μ μ’
λ£νλ€.
\ No newline at end of file
diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java
index a17a52e724..ec50c9b9a6 100644
--- a/src/main/java/racingcar/Application.java
+++ b/src/main/java/racingcar/Application.java
@@ -1,7 +1,12 @@
package racingcar;
+import camp.nextstep.edu.missionutils.Console;
+import racingcar.config.AppConfig;
+
public class Application {
public static void main(String[] args) {
- // TODO: νλ‘κ·Έλ¨ κ΅¬ν
+ AppConfig appConfig = new AppConfig();
+ appConfig.run();
+ Console.close();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/View/InputValidator.java b/src/main/java/racingcar/View/InputValidator.java
new file mode 100644
index 0000000000..7e00f8269d
--- /dev/null
+++ b/src/main/java/racingcar/View/InputValidator.java
@@ -0,0 +1,12 @@
+package racingcar.View;
+
+import static racingcar.View.ViewConstants.TOTAL_ROUNDS_REGEX;
+import static racingcar.View.ViewConstants.CONTAINS_NON_DIGIT_ERROR_MESSAGE;
+
+public class InputValidator {
+ public void validateThatContainsOnlyDigits(String input) {
+ if (!input.matches(TOTAL_ROUNDS_REGEX)) {
+ throw new IllegalArgumentException(CONTAINS_NON_DIGIT_ERROR_MESSAGE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/View/InputView.java b/src/main/java/racingcar/View/InputView.java
new file mode 100644
index 0000000000..4bdc9a2412
--- /dev/null
+++ b/src/main/java/racingcar/View/InputView.java
@@ -0,0 +1,18 @@
+package racingcar.View;
+
+import static racingcar.View.ViewConstants.ENTER_CAR_NAMES_MESSAGE;
+import static racingcar.View.ViewConstants.ENTER_TOTAL_ROUNDS_MESSAGE;
+
+import camp.nextstep.edu.missionutils.Console;
+
+public class InputView {
+ public String requestCarNames() {
+ System.out.println(ENTER_CAR_NAMES_MESSAGE);
+ return Console.readLine();
+ }
+
+ public String requestTotalRounds() {
+ System.out.println(ENTER_TOTAL_ROUNDS_MESSAGE);
+ return Console.readLine();
+ }
+}
\ 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 0000000000..0cab5b69ca
--- /dev/null
+++ b/src/main/java/racingcar/View/OutputView.java
@@ -0,0 +1,23 @@
+package racingcar.View;
+
+import static racingcar.View.ViewConstants.NAME_DELIMITER;
+import static racingcar.View.ViewConstants.RACE_RESULT;
+import static racingcar.View.ViewConstants.WHITE_SPACE;
+import static racingcar.View.ViewConstants.WINNER_IS;
+
+import java.util.List;
+
+public class OutputView {
+ public void printResultPhrase() {
+ System.out.println(System.lineSeparator() + RACE_RESULT);
+ }
+
+ public void printRaceProgress(String roundResult) {
+ System.out.println(roundResult);
+ }
+
+ public void printWinners(List winnerNames) {
+ String winners = String.join(NAME_DELIMITER + WHITE_SPACE, winnerNames);
+ System.out.printf("%s%s", WINNER_IS, winners);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/View/ViewConstants.java b/src/main/java/racingcar/View/ViewConstants.java
new file mode 100644
index 0000000000..b63123f8c3
--- /dev/null
+++ b/src/main/java/racingcar/View/ViewConstants.java
@@ -0,0 +1,31 @@
+package racingcar.View;
+
+import static racingcar.model.RacingConstants.MAX_LENGTH_OF_CAR_NAME;
+import static racingcar.model.RacingConstants.MIN_LENGTH_OF_CAR_NAME;
+import static racingcar.model.RacingConstants.MIN_ROUNDS;
+import static racingcar.model.RacingConstants.REQUIRED_MIN_PLAYERS;
+
+public class ViewConstants {
+ public static final String ENTER_CAR_NAMES_MESSAGE = "κ²½μ£Όν μλμ°¨ μ΄λ¦μ μ
λ ₯νμΈμ.(μ΄λ¦μ μΌν(,) κΈ°μ€μΌλ‘ ꡬλΆ)";
+ public static final String ENTER_TOTAL_ROUNDS_MESSAGE = "μλν νμλ λͺ νμΈκ°μ?";
+
+ public static final String NAME_DELIMITER = ",";
+ public static final String TOTAL_ROUNDS_REGEX = "^[0-9]+$";
+
+ public static final String RACE_RESULT = "μ€ν κ²°κ³Ό";
+ public static final String RACE_DISPLAY_FORMAT = "%s : ";
+ public static final String MOVE_SYMBOL = "-";
+
+ public static final String WINNER_IS = "μ΅μ’
μ°μΉμ : ";
+ public static final String WHITE_SPACE = " ";
+
+ public static final String NOT_ENOUGH_PLAYERS_ERROR_MESSAGE
+ = String.format("μ΄λ¦μ %sκ° μ΄μ μ
λ ₯ν΄μΌ κ²μμ΄ μμλ©λλ€.", REQUIRED_MIN_PLAYERS);
+ public static final String NAME_LENGTH_ERROR_MESSAGE
+ = String.format("μ΄λ¦μ %sμ μ΄μ, %sμ μ΄νλ§ κ°λ₯ν©λλ€.", MIN_LENGTH_OF_CAR_NAME, MAX_LENGTH_OF_CAR_NAME);
+ public static final String DUPLICATE_NAME_ERROR_MESSAGE = "μ€λ³΅λ μ΄λ¦μ μ¬μ©ν μ μμ΅λλ€.";
+
+ public static final String CONTAINS_NON_DIGIT_ERROR_MESSAGE = "μλ νμλ μ«μλ§ μ
λ ₯ν΄ μ£ΌμΈμ.";
+ public static final String LESS_THAN_MIN_ROUNDS_ERROR_MESSAGE
+ = String.format("μλ νμλ %sν μ΄μμ΄μ΄μΌ ν©λλ€.", MIN_ROUNDS);
+}
diff --git a/src/main/java/racingcar/config/AppConfig.java b/src/main/java/racingcar/config/AppConfig.java
new file mode 100644
index 0000000000..ca573d1476
--- /dev/null
+++ b/src/main/java/racingcar/config/AppConfig.java
@@ -0,0 +1,55 @@
+package racingcar.config;
+
+import racingcar.View.InputValidator;
+import racingcar.model.CarRacing;
+import racingcar.controller.CarRacingController;
+import racingcar.model.Racers;
+import racingcar.model.RacingRule;
+import racingcar.View.InputView;
+import racingcar.View.OutputView;
+import racingcar.service.CarRacingService;
+
+public class AppConfig {
+ public InputView inputView() {
+ return new InputView();
+ }
+
+ public InputValidator inputValidator() {
+ return new InputValidator();
+ }
+
+ public OutputView outputView() {
+ return new OutputView();
+ }
+
+ public CarRacingController carRacingController() {
+ return new CarRacingController(inputView(), inputValidator(), outputView());
+ }
+
+ public CarRacingService carRacingService() {
+ return new CarRacingService();
+ }
+
+ public RacingRule racingRule() {
+ return carRacingService().setRacingRule();
+ }
+
+ public Racers racers() {
+ return carRacingService().registerRacers(carRacingController().extractCarNames());
+ }
+
+ public int totalRounds() {
+ return carRacingService().registerTotalRounds(carRacingController().convertToNumber());
+ }
+
+ public CarRacing carRacing() {
+ return new CarRacing(racingRule(), racers());
+ }
+
+ public void run() {
+ CarRacingController carRacingController = carRacingController();
+ CarRacing carRacing = carRacing();
+ carRacingController.deliverRaceProgress(carRacing, totalRounds());
+ carRacingController.deliverWinners(carRacing);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/controller/CarRacingController.java b/src/main/java/racingcar/controller/CarRacingController.java
new file mode 100644
index 0000000000..f16d2b6931
--- /dev/null
+++ b/src/main/java/racingcar/controller/CarRacingController.java
@@ -0,0 +1,57 @@
+package racingcar.controller;
+
+import static racingcar.View.ViewConstants.NAME_DELIMITER;
+
+import java.util.Arrays;
+import java.util.List;
+import racingcar.View.InputView;
+import racingcar.View.OutputView;
+import racingcar.View.InputValidator;
+import racingcar.model.CarRacing;
+
+public class CarRacingController {
+ private final InputView inputView;
+ private final InputValidator inputValidator;
+ private final OutputView outputView;
+
+ public CarRacingController(InputView inputView, InputValidator inputValidator, OutputView outputView) {
+ this.inputView = inputView;
+ this.inputValidator = inputValidator;
+ this.outputView = outputView;
+ }
+
+ public List extractCarNames() {
+ String input = inputView.requestCarNames();
+ return splitByDelimiter(input);
+ }
+
+ private List splitByDelimiter(String input) {
+ return Arrays.stream(input.split(NAME_DELIMITER)).toList();
+ }
+
+ public int convertToNumber() {
+ String input = inputView.requestTotalRounds();
+ inputValidator.validateThatContainsOnlyDigits(input);
+ return Integer.parseInt(input);
+ }
+
+ public void deliverRaceProgress(CarRacing carRacing, int totalRounds) {
+ outputView.printResultPhrase();
+ for (int i = 0; i < totalRounds; i++) {
+ List currentResults = carRacing.playARound();
+ outputView.printRaceProgress(convertToString(currentResults));
+ }
+ }
+
+ private String convertToString(List currentResults) {
+ StringBuilder output = new StringBuilder();
+ for (String result : currentResults) {
+ output.append(result).append(System.lineSeparator());
+ }
+ return output.toString();
+ }
+
+ public void deliverWinners(CarRacing carRacing) {
+ outputView.printWinners(carRacing.announceWinners());
+ }
+}
diff --git a/src/main/java/racingcar/model/Car.java b/src/main/java/racingcar/model/Car.java
new file mode 100644
index 0000000000..826976b030
--- /dev/null
+++ b/src/main/java/racingcar/model/Car.java
@@ -0,0 +1,48 @@
+package racingcar.model;
+
+import static racingcar.model.RacingConstants.MAX_LENGTH_OF_CAR_NAME;
+import static racingcar.View.ViewConstants.MOVE_SYMBOL;
+import static racingcar.View.ViewConstants.NAME_LENGTH_ERROR_MESSAGE;
+import static racingcar.View.ViewConstants.RACE_DISPLAY_FORMAT;
+
+public class Car implements Comparable {
+ private final String name;
+ private int moveCount;
+
+ public Car(String name, int moveCount) {
+ validateLengthOf(name);
+ this.name = name;
+ this.moveCount = moveCount;
+ }
+
+ private void validateLengthOf(String name) {
+ if (name == null || name.isBlank() || (name.length() > MAX_LENGTH_OF_CAR_NAME)) {
+ throw new IllegalArgumentException(NAME_LENGTH_ERROR_MESSAGE);
+ }
+ }
+
+ public int moveForwardIf(boolean possible) {
+ if (possible) {
+ moveCount++;
+ return moveCount;
+ }
+ return -1;
+ }
+
+ public boolean isSameMoveCount(Car other) {
+ return other.moveCount == this.moveCount;
+ }
+
+ @Override
+ public int compareTo(Car other) {
+ return this.moveCount - other.moveCount;
+ }
+
+ public String getStatus() {
+ return String.format(RACE_DISPLAY_FORMAT, name).concat(MOVE_SYMBOL.repeat(moveCount));
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/racingcar/model/CarRacing.java b/src/main/java/racingcar/model/CarRacing.java
new file mode 100644
index 0000000000..79f1847b2c
--- /dev/null
+++ b/src/main/java/racingcar/model/CarRacing.java
@@ -0,0 +1,22 @@
+package racingcar.model;
+
+import java.util.List;
+
+public class CarRacing {
+ private final RacingRule racingRule;
+ private final Racers racers;
+
+ public CarRacing(RacingRule racingRule, Racers racers) {
+ this.racingRule = racingRule;
+ this.racers = racers;
+ }
+
+ public List playARound() {
+ racers.tryToMoveWith(racingRule);
+ return racers.getCurrentResult();
+ }
+
+ public List announceWinners() {
+ return racers.getWinnerNames();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/model/Racers.java b/src/main/java/racingcar/model/Racers.java
new file mode 100644
index 0000000000..b364c022cc
--- /dev/null
+++ b/src/main/java/racingcar/model/Racers.java
@@ -0,0 +1,60 @@
+package racingcar.model;
+
+import static racingcar.View.ViewConstants.DUPLICATE_NAME_ERROR_MESSAGE;
+import static racingcar.View.ViewConstants.NOT_ENOUGH_PLAYERS_ERROR_MESSAGE;
+import static racingcar.model.RacingConstants.REQUIRED_MIN_PLAYERS;
+
+import java.util.List;
+
+public class Racers {
+ private final List cars;
+
+ public Racers(List cars) {
+ validateCountOf(cars);
+ validateNonDuplicate(cars);
+ this.cars = cars;
+ }
+
+ private void validateCountOf(List cars) {
+ if (cars.size() < REQUIRED_MIN_PLAYERS) {
+ throw new IllegalArgumentException(NOT_ENOUGH_PLAYERS_ERROR_MESSAGE);
+ }
+ }
+
+ private void validateNonDuplicate(List cars) {
+ long totalDistinctNames = cars.stream().map(Car::getName).distinct().count();
+ if (cars.size() != totalDistinctNames) {
+ throw new IllegalArgumentException(DUPLICATE_NAME_ERROR_MESSAGE);
+ }
+ }
+
+ public void tryToMoveWith(RacingRule racingRule) {
+ for (Car car : cars) {
+ car.moveForwardIf(racingRule.permitToMove());
+ }
+ }
+
+ public List getCurrentResult() {
+ return cars.stream()
+ .map(Car::getStatus)
+ .toList();
+ }
+
+ public List getWinnerNames() {
+ Car maxMoveCountCar = findMaxMoveCountCar();
+ return findSameMoveCountCar(maxMoveCountCar);
+ }
+
+ private List findSameMoveCountCar(Car maxMoveCountCar) {
+ return cars.stream()
+ .filter(maxMoveCountCar::isSameMoveCount)
+ .map(Car::getName)
+ .toList();
+ }
+
+ private Car findMaxMoveCountCar() {
+ return cars.stream()
+ .max(Car::compareTo)
+ .orElseThrow();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/model/RacingConstants.java b/src/main/java/racingcar/model/RacingConstants.java
new file mode 100644
index 0000000000..cc7257a9f4
--- /dev/null
+++ b/src/main/java/racingcar/model/RacingConstants.java
@@ -0,0 +1,14 @@
+package racingcar.model;
+
+public class RacingConstants {
+ public static final int REQUIRED_MIN_PLAYERS = 2;
+ public static final int MIN_ROUNDS = 1;
+
+ public static final int MIN_LENGTH_OF_CAR_NAME = 1;
+ public static final int MAX_LENGTH_OF_CAR_NAME = 5;
+ public static final int INITIAL_MOVE_COUNT = 0;
+
+ public static final int MIN_NUMBER_TO_MOVE = 4;
+ public static final int MIN_NUMBER_IN_RANGE = 0;
+ public static final int MAX_NUMBER_IN_RANGE = 9;
+}
\ No newline at end of file
diff --git a/src/main/java/racingcar/model/RacingRule.java b/src/main/java/racingcar/model/RacingRule.java
new file mode 100644
index 0000000000..0d178e9fd1
--- /dev/null
+++ b/src/main/java/racingcar/model/RacingRule.java
@@ -0,0 +1,27 @@
+package racingcar.model;
+
+import camp.nextstep.edu.missionutils.Randoms;
+
+public class RacingRule {
+ private final int minNumber;
+ private final int maxNumber;
+ private final int minNumberToMove;
+
+ public RacingRule(int minNumber, int maxNumber, int minNumberToMove) {
+ this.minNumber = minNumber;
+ this.maxNumber = maxNumber;
+ this.minNumberToMove = minNumberToMove;
+ }
+
+ public boolean permitToMove() {
+ return isMovable(pickRandomNumber());
+ }
+
+ private int pickRandomNumber() {
+ return Randoms.pickNumberInRange(minNumber, maxNumber);
+ }
+
+ private boolean isMovable(int number) {
+ return number >= minNumberToMove;
+ }
+}
diff --git a/src/main/java/racingcar/service/CarRacingService.java b/src/main/java/racingcar/service/CarRacingService.java
new file mode 100644
index 0000000000..331c5ba389
--- /dev/null
+++ b/src/main/java/racingcar/service/CarRacingService.java
@@ -0,0 +1,36 @@
+package racingcar.service;
+
+import static racingcar.View.ViewConstants.LESS_THAN_MIN_ROUNDS_ERROR_MESSAGE;
+import static racingcar.model.RacingConstants.INITIAL_MOVE_COUNT;
+import static racingcar.model.RacingConstants.MAX_NUMBER_IN_RANGE;
+import static racingcar.model.RacingConstants.MIN_NUMBER_IN_RANGE;
+import static racingcar.model.RacingConstants.MIN_NUMBER_TO_MOVE;
+import static racingcar.model.RacingConstants.MIN_ROUNDS;
+
+import java.util.List;
+import racingcar.model.Car;
+import racingcar.model.Racers;
+import racingcar.model.RacingRule;
+
+public class CarRacingService {
+ public RacingRule setRacingRule() {
+ return new RacingRule(MIN_NUMBER_IN_RANGE, MAX_NUMBER_IN_RANGE, MIN_NUMBER_TO_MOVE);
+ }
+
+ public Racers registerRacers(List carNames) {
+ return new Racers(carNames.stream()
+ .map(name -> new Car(name, INITIAL_MOVE_COUNT))
+ .toList());
+ }
+
+ public int registerTotalRounds(int number) {
+ validateMinRoundsOrOver(number);
+ return number;
+ }
+
+ private void validateMinRoundsOrOver(int number) {
+ if (number < MIN_ROUNDS) {
+ throw new IllegalArgumentException(LESS_THAN_MIN_ROUNDS_ERROR_MESSAGE);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/racingcar/model/CarTest.java b/src/test/java/racingcar/model/CarTest.java
new file mode 100644
index 0000000000..22d779f9c0
--- /dev/null
+++ b/src/test/java/racingcar/model/CarTest.java
@@ -0,0 +1,61 @@
+package racingcar.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static racingcar.model.RacingConstants.INITIAL_MOVE_COUNT;
+import static racingcar.model.RacingConstants.MAX_LENGTH_OF_CAR_NAME;
+import static racingcar.model.RacingConstants.MIN_LENGTH_OF_CAR_NAME;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class CarTest {
+ @DisplayName("μ΅μ κΈΈμ΄λ³΄λ€ μ§§μ μ΄λ¦μ μ λ¬λ°μΌλ©΄ μμΈ λ°μ")
+ @ParameterizedTest
+ @ValueSource(strings = {" "})
+ @NullAndEmptySource
+ void throwExceptionIfGetNameShorterThanMinLength(String name) {
+ assertThatThrownBy(() -> new Car(name, INITIAL_MOVE_COUNT))
+ .isExactlyInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining(String.valueOf(MIN_LENGTH_OF_CAR_NAME));
+ }
+
+ @DisplayName("μ΅λ κΈΈμ΄λ³΄λ€ κΈ΄ μ΄λ¦μ μ λ¬λ°μΌλ©΄ μμΈ λ°μ")
+ @ParameterizedTest
+ @ValueSource(strings = {"sarang", "sarangkim", "jaesungpark", "κ°λλ€λΌλ§λ°"})
+ void throwExceptionIfGetNameLongerThanMaxLength(String name) {
+ assertThatThrownBy(() -> new Car(name, INITIAL_MOVE_COUNT))
+ .isExactlyInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining(String.valueOf(MAX_LENGTH_OF_CAR_NAME));
+ }
+
+ @DisplayName("μ λ¬λ°μ μ΄λ¦μ κΈΈμ΄κ° μ ν¨νλ©΄ Car μΈμ€ν΄μ€ μμ±")
+ @ParameterizedTest
+ @ValueSource(strings = {"k", "go123", "pobi", "saera", "won.i", "μ°¨", "μμ", "μΉ΄ννν5"})
+ void createCar(String name) {
+ assertThat(new Car(name, INITIAL_MOVE_COUNT)).isExactlyInstanceOf(Car.class);
+ }
+
+ @DisplayName("trueλ₯Ό μ λ¬λ°μΌλ©΄ μμΉκ° λ°ν")
+ @Test
+ void returnPositionIfGetTrue() {
+ Car car = new Car("pobi", INITIAL_MOVE_COUNT);
+
+ int actual = car.moveForwardIf(true);
+
+ assertThat(actual).isEqualTo(INITIAL_MOVE_COUNT + 1);
+ }
+
+ @DisplayName("falseλ₯Ό μ λ¬λ°μΌλ©΄ -1 λ°ν")
+ @Test
+ void returnMinusOneIfGetFalse() {
+ Car car = new Car("pobi", INITIAL_MOVE_COUNT);
+
+ int actual = car.moveForwardIf(false);
+
+ assertThat(actual).isEqualTo(-1);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/racingcar/model/RacersTest.java b/src/test/java/racingcar/model/RacersTest.java
new file mode 100644
index 0000000000..afb04c4535
--- /dev/null
+++ b/src/test/java/racingcar/model/RacersTest.java
@@ -0,0 +1,68 @@
+package racingcar.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static racingcar.View.ViewConstants.DUPLICATE_NAME_ERROR_MESSAGE;
+import static racingcar.View.ViewConstants.NAME_DELIMITER;
+import static racingcar.model.RacingConstants.REQUIRED_MIN_PLAYERS;
+
+import java.util.Arrays;
+import java.util.List;
+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 racingcar.service.CarRacingService;
+
+class RacersTest {
+ private final CarRacingService carRacingService = new CarRacingService();
+ private List inputNames;
+
+ @DisplayName("μ
λ ₯λ μλμ°¨ μ΄λ¦μ΄ 2κ° λ―Έλ§μ΄λ©΄ μμΈ λ°μ")
+ @ParameterizedTest
+ @ValueSource(strings = {",", ",,,,,", "pobi", "ab.cd", "jo ko"})
+ void throwExceptionIfPlayersNotEnough(String input) {
+ inputNames = split(input);
+
+ assertThatThrownBy(() -> carRacingService.registerRacers(inputNames))
+ .isExactlyInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining(String.valueOf(REQUIRED_MIN_PLAYERS));
+ }
+
+ @DisplayName("μ
λ ₯λ μλμ°¨ μ΄λ¦μ μ€λ³΅μ΄ μμΌλ©΄ μμΈ λ°μ")
+ @ParameterizedTest
+ @ValueSource(strings = {"pobi,woni,jun,pobi", "κ°,κ°,κ°,κ°", "a,b,a,d,a,e,f", "0,0"})
+ void throwExceptionWhenDuplicateNameIsIn(String input) {
+ inputNames = split(input);
+
+ assertThatThrownBy(() -> carRacingService.registerRacers(inputNames))
+ .isExactlyInstanceOf(IllegalArgumentException.class)
+ .hasMessageMatching(DUPLICATE_NAME_ERROR_MESSAGE);
+ }
+
+ @DisplayName("μ
λ ₯λ μλμ°¨ μ΄λ¦μ΄ 2κ° μ΄μμ΄κ³ , μ€λ³΅μ΄ μμΌλ©΄ Racers μΈμ€ν΄μ€ μμ±")
+ @ParameterizedTest
+ @ValueSource(strings = {"pobi,jun", "pobi,jun,woni", "κ°,λ", "κ°λλ€λΌλ§,λ°μ¬μμμ°¨,μΉ΄ννν", "kim1,kim2,kim3,kim4,kim5"})
+ void createRacers(String input) {
+ inputNames = split(input);
+
+ assertThat(carRacingService.registerRacers(inputNames)).isExactlyInstanceOf(Racers.class);
+ }
+
+ private List split(String input) {
+ return Arrays.stream(input.split(NAME_DELIMITER)).toList();
+ }
+
+ @DisplayName("getWinnerNames() λ©μλκ° μ°μΉμλ€μ μ΄λ¦μ λ°ννλ€.")
+ @Test
+ void returnWinnerNames() {
+ Racers racers = new Racers(List.of(
+ new Car("pobi", Integer.MAX_VALUE), new Car("woni", 4), new Car("jun", Integer.MAX_VALUE)
+ ));
+
+ List actual = racers.getWinnerNames();
+ List expected = List.of("pobi", "jun");
+
+ assertThat(actual).isEqualTo(expected);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/racingcar/service/CarRacingServiceTest.java b/src/test/java/racingcar/service/CarRacingServiceTest.java
new file mode 100644
index 0000000000..efd4d17c8d
--- /dev/null
+++ b/src/test/java/racingcar/service/CarRacingServiceTest.java
@@ -0,0 +1,35 @@
+package racingcar.service;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static racingcar.View.ViewConstants.LESS_THAN_MIN_ROUNDS_ERROR_MESSAGE;
+import static racingcar.model.RacingConstants.MIN_ROUNDS;
+
+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;
+
+class CarRacingServiceTest {
+
+ @DisplayName("μ΅μ μλ νμλ³΄λ€ μμ κ°μ μ λ¬λ°μΌλ©΄ μμΈ λ°μ")
+ @Test
+ void throwExceptionIfNumberIsLessThanMinRounds() {
+ CarRacingService carRacingService = new CarRacingService();
+
+ int number = MIN_ROUNDS - 1;
+
+ assertThatThrownBy(() -> carRacingService.registerTotalRounds(number))
+ .isExactlyInstanceOf(IllegalArgumentException.class)
+ .hasMessageMatching(LESS_THAN_MIN_ROUNDS_ERROR_MESSAGE);
+ }
+
+ @DisplayName("μ΅μ μλ νμλ³΄λ€ ν¬κ±°λ κ°μ κ°μ μ λ¬λ°μΌλ©΄ κ·Έ κ°μ λ°ν")
+ @ParameterizedTest
+ @ValueSource(ints = {MIN_ROUNDS, MIN_ROUNDS + 1, 1, 2, 3, 10, 1000, Integer.MAX_VALUE})
+ void returnTotalRoundsIfNumberIsAboveMinRounds(int number) {
+ CarRacingService carRacingService = new CarRacingService();
+
+ assertThat(carRacingService.registerTotalRounds(number)).isEqualTo(number);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/racingcar/view/InputValidatorTest.java b/src/test/java/racingcar/view/InputValidatorTest.java
new file mode 100644
index 0000000000..af0a9c5d3d
--- /dev/null
+++ b/src/test/java/racingcar/view/InputValidatorTest.java
@@ -0,0 +1,26 @@
+package racingcar.view;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import racingcar.View.InputValidator;
+
+class InputValidatorTest {
+ @DisplayName("μ«μλ‘λ§ κ΅¬μ±λ λ¬Έμμ΄μ΄λ©΄ ν΅κ³Ό")
+ @ParameterizedTest
+ @ValueSource(strings = {"0", "1", "0123456789"})
+ void passWhenInputHasNumbersOnly(String input) {
+ assertThatCode(() -> new InputValidator().validateThatContainsOnlyDigits(input)).doesNotThrowAnyException();
+ }
+
+ @DisplayName("λ¬Έμμ΄μ μ«μκ° μλ λ¬Έμκ° μμΌλ©΄ μμΈ λ°μ")
+ @ParameterizedTest
+ @ValueSource(strings = {"", "0ν", "1,", ";2", "34λ²", "56ν μλ"})
+ void throwIllegalArgumentExceptionWhenInputHasNotNumbersOnly(String input) {
+ assertThatThrownBy(() -> new InputValidator().validateThatContainsOnlyDigits(input))
+ .isExactlyInstanceOf(IllegalArgumentException.class);
+ }
+}
\ No newline at end of file