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