diff --git a/build.gradle b/build.gradle index 45d61697f..edc026cd8 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ dependencies { java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(11) } } diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..fe5d80267 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,143 @@ +# [미션 - 자동차 경주 게임] 명세서 + +## 목차 + +1. [기능 목록](#-기능-목록) +2. [상수값 목록](#-상수값-목록) +3. [단위 테스트 목록](#-단위테스트-목록) +4. [클래스 설계](#-클래스-설계) +5. [리팩토링 검토 목록](#%EF%B8%8F-리팩토링-검토-목록) + +## 🚀 기능 목록 + +### 로직 분리에 따른 예외 발생 기준 + +- 예외 발생 시 적합한 표준예외를 사용한다. +- 기본 UI 검증 오류는 `view`에서, 도메인 기능에서 발생하는 오류는 `service`에서 예외를 발생시킨다. + +### 도메인 구성 요소 + +- 자동차 + - 이름 + - 위치 +- 게임에 참여한 모든 자동차 목록 +- 자동차가 이동하기 위한 값 생성기 +- 시도 회수 +- 게임 결과 + - 우승한 자동차 이름 모음 (1~n명) + +### 도메인 로직을 위한 기능 + +1. + - [x] 경주할 모든 자동차의 이름을 저장하는 기능 + - [x] `예외발생` : 경주할 자동차가 1대 + - [x] `예외발생` : 이름의 길이가 5자 초과 + - [x] `예외발생` : 중복 이름 존재 +2. + - [x] 랜덤 숫자를 생성하는 기능 +3. + - [x] 자동차가 전달받은 숫자에 따라 이동(전진하거나 전진하지않음)하는 기능 + - [x] `예외발생` : 범위 밖 숫자 +4. + - [x] 자동차 간 위치를 비교해 가장 값이 큰, 최소 1명 최대 n명의 우승자를 구하는 기능 +5. + - [x] 전달받은 회수 만큼 자동차 이동을 실행시키는 기능 + +### UI 로직을 위한 기능 + +1. + - [x] 경주할 모든 자동차 이름을 형식에 맞게 입력받는 기능 + - [x] `예외발생` : 빈 문자열 + - [x] `예외발생` : 입력값 형식에 맞지 않음 +2. + - [x] 시도할 회수를 입력받는 기능 + - [x] `예외발생` : 자연수가 아닌 값 +3. + - [x] 차수 별 실행 결과 출력하는 기능 +4. + - [x] 우승자 안내 출력하는 기능 +5. + - [x] 이름 형식을 쉼표 구분 <-> 리스트로 파싱하는 기능 +6. + - [x] 에러 메시지 출력하는 기능 +7. + - [x] 잘못된 값 입력 시 그 부분부터 재입력하는 기능 + +## 🗄 상수값 목록 + +### 도메인 로직 정보 + +- [x] 이동 숫자 0 ~ 9, 전진 조건 4 +- [x] 이름 길이 상한 제한 5 +- [x] 자동차 개수 하한 제한 2 +- [x] 에러 메시지 + +### UI 로직 정보 + +- [x] 구분자 쉼표 +- [x] 입력 안내 메시지 +- [x] 실행 결과 출력 형식 +- [x] 우승자 안내 문구 출력 형식 +- [x] 에러 메시지 +- [x] 에러메시지 출력 형식 + +## ✅ 단위테스트 목록 + +### 도메인 로직 단위테스트 + +- [x] 자동차 등록 + - [x] 예외 테스트 +- [x] 자동차 이동 + - [x] 전진 + - [x] 정지 + - [x] 예외 테스트 +- [x] 우승자 계산 + - [x] 성공 테스트 (공동 우승 확인) + +## 🖋 클래스 설계 + +``` +└── racingcar + ├── Application.java + ├── controller + │ ├── RacingController.java + │ └── util + │ └── ExceptionHandler.java + ├── dto + │ ├── CarDTO.java + │ └── CarStatusDTO.java + ├── model + │ ├── NumberGenerator.java + │ ├── RacingGame.java + │ ├── RandomNumberGenerator.java + │ ├── constants + │ │ ├── ErrorMessage.java + │ │ └── GameRule.java + │ └── domain + │ ├── Car.java + │ └── RacingCars.java + └── view + ├── InputView.java + ├── OutputView.java + ├── constants + │ ├── ErrorMessage.java + │ ├── Format.java + │ ├── InputMessage.java + │ └── OutputMessage.java + └── util + ├── FormatParser.java + └── NumberParser.java +``` + +## ♻️ 리팩토링 검토 목록 + +### 코드 스타일 및 요구사항 + +- [x] 인덴트 2 이하 검토 +- [x] 메소드, 클래스 분리 검토 +- [x] 메소드 길이 10라인 이하 검토 +- [x] 파라미터 개수 3개 이하 검토 +- [x] 변수, 메소드 선언 순서 정리 (메소드 정렬 기준 1순위: 메소드 간 논리적 연관성, 2순위: public-private) +- [x] Java 코드 컨벤션 가이드 준수 검토 +- [x] 요구사항에 주어진 클래스 적용 규칙 검토 +- [x] TODO 주석 모두 해결 후 삭제 \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index b9ed0456a..3db9dee21 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,10 @@ package racingcar; +import racingcar.controller.RacingController; + public class Application { public static void main(String[] args) { - // TODO 구현 진행 + RacingController racingController = new RacingController(); + racingController.run(); } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java deleted file mode 100644 index ab3df9492..000000000 --- a/src/main/java/racingcar/Car.java +++ /dev/null @@ -1,12 +0,0 @@ -package racingcar; - -public class Car { - private final String name; - private int position = 0; - - public Car(String name) { - this.name = name; - } - - // 추가 기능 구현 -} diff --git a/src/main/java/racingcar/controller/RacingController.java b/src/main/java/racingcar/controller/RacingController.java new file mode 100644 index 000000000..eb7c1ac78 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingController.java @@ -0,0 +1,46 @@ +package racingcar.controller; + +import java.util.List; +import racingcar.controller.util.ExceptionHandler; +import racingcar.dto.CarStatusDTO; +import racingcar.model.RacingGame; +import racingcar.model.RandomNumberGenerator; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class RacingController { + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + private final RacingGame racingGame = new RacingGame(new RandomNumberGenerator()); + + public void run() { + try { + repeatUntilGetLegalAnswer(this::enrollCarToRace); + repeatUntilGetLegalAnswer(this::moveCarsByCount); + showWinners(); + } catch (Exception exception) { + exception.printStackTrace(); + outputView.printErrorMessage(exception.getMessage()); + } + } + + private void repeatUntilGetLegalAnswer(Runnable runnable) { + ExceptionHandler.retryForIllegalArgument(runnable, outputView::printErrorMessage); + } + + private void enrollCarToRace() { + List carNames = inputView.inputCarNames(); + racingGame.enrollCars(carNames); + } + + private void moveCarsByCount() { + int moveCount = inputView.inputMoveCount(); + List carStatuses = racingGame.repeatMovingCars(moveCount); + outputView.printGameResult(carStatuses); + } + + private void showWinners() { + List winnerNames = racingGame.findWinners(); + outputView.printWinners(winnerNames); + } +} diff --git a/src/main/java/racingcar/controller/util/ExceptionHandler.java b/src/main/java/racingcar/controller/util/ExceptionHandler.java new file mode 100644 index 000000000..9e9a0092d --- /dev/null +++ b/src/main/java/racingcar/controller/util/ExceptionHandler.java @@ -0,0 +1,19 @@ +package racingcar.controller.util; + +import java.util.function.Consumer; + +public class ExceptionHandler { + private ExceptionHandler() { + } + + public static void retryForIllegalArgument(Runnable runnable, Consumer exceptionMessageHandling) { + while (true) { + try { + runnable.run(); + return; + } catch (IllegalArgumentException exception) { + exceptionMessageHandling.accept(exception.getMessage()); + } + } + } +} diff --git a/src/main/java/racingcar/dto/CarDTO.java b/src/main/java/racingcar/dto/CarDTO.java new file mode 100644 index 000000000..02ededbbf --- /dev/null +++ b/src/main/java/racingcar/dto/CarDTO.java @@ -0,0 +1,27 @@ +package racingcar.dto; + +public class CarDTO { + private final String name; + private final int position; + + public CarDTO(String name, int position) { + this.name = name; + this.position = position; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + @Override + public String toString() { + return "CarDTO{" + + "name='" + name + '\'' + + ", position=" + position + + '}'; + } +} diff --git a/src/main/java/racingcar/dto/CarStatusDTO.java b/src/main/java/racingcar/dto/CarStatusDTO.java new file mode 100644 index 000000000..2dbc7720b --- /dev/null +++ b/src/main/java/racingcar/dto/CarStatusDTO.java @@ -0,0 +1,15 @@ +package racingcar.dto; + +import java.util.List; + +public class CarStatusDTO { + private final List cars; + + public CarStatusDTO(List cars) { + this.cars = cars; + } + + public List getCars() { + return cars; + } +} diff --git a/src/main/java/racingcar/model/NumberGenerator.java b/src/main/java/racingcar/model/NumberGenerator.java new file mode 100644 index 000000000..817bb2485 --- /dev/null +++ b/src/main/java/racingcar/model/NumberGenerator.java @@ -0,0 +1,6 @@ +package racingcar.model; + +@FunctionalInterface +public interface NumberGenerator { + int make(); +} diff --git a/src/main/java/racingcar/model/RacingGame.java b/src/main/java/racingcar/model/RacingGame.java new file mode 100644 index 000000000..8d60d9447 --- /dev/null +++ b/src/main/java/racingcar/model/RacingGame.java @@ -0,0 +1,42 @@ +package racingcar.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import racingcar.dto.CarDTO; +import racingcar.dto.CarStatusDTO; +import racingcar.model.domain.Car; +import racingcar.model.domain.RacingCars; + +public class RacingGame { + private final RacingCars racingCars = new RacingCars(); + private final NumberGenerator numberGenerator; + + public RacingGame(NumberGenerator numberGenerator) { + this.numberGenerator = numberGenerator; + } + + public void enrollCars(List carNames) { + racingCars.addCars(carNames); + } + + public List repeatMovingCars(int moveCount) { + List carStatuses = new ArrayList<>(); + for (int count = 0; count < moveCount; count++) { + carStatuses.add(new CarStatusDTO(moveCars())); + } + return carStatuses; + } + + private List moveCars() { + racingCars.race(numberGenerator); + List cars = racingCars.cars(); + return cars.stream() + .map(Car::to) + .collect(Collectors.toList()); + } + + public List findWinners() { + return racingCars.findWinnerNames(); + } +} diff --git a/src/main/java/racingcar/model/RandomNumberGenerator.java b/src/main/java/racingcar/model/RandomNumberGenerator.java new file mode 100644 index 000000000..14ba6381b --- /dev/null +++ b/src/main/java/racingcar/model/RandomNumberGenerator.java @@ -0,0 +1,13 @@ +package racingcar.model; + +import static racingcar.model.constants.GameRule.NUMBER_RANGE_END; +import static racingcar.model.constants.GameRule.NUMBER_RANGE_START; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RandomNumberGenerator implements NumberGenerator { + @Override + public int make() { + return Randoms.pickNumberInRange(NUMBER_RANGE_START, NUMBER_RANGE_END); + } +} diff --git a/src/main/java/racingcar/model/constants/ErrorMessage.java b/src/main/java/racingcar/model/constants/ErrorMessage.java new file mode 100644 index 000000000..81b39b4e4 --- /dev/null +++ b/src/main/java/racingcar/model/constants/ErrorMessage.java @@ -0,0 +1,11 @@ +package racingcar.model.constants; + +public class ErrorMessage { + public static final String CARS_LACK_OF_COUNT = "경주할 자동차는 최소 2대 필요합니다."; + public static final String CAR_NAME_LENGTH_OVER = "자동차 이름은 최대 5글자까지만 가능합니다."; + public static final String CARS_DUPLICATED_NAME = "중복된 자동차 이름을 등록할 수 없습니다."; + public static final String MOVING_NUMBER_OUT_OF_BOUNDS = "자동차 이동을 위한 값이 범위 밖 수입니다."; + + private ErrorMessage() { + } +} diff --git a/src/main/java/racingcar/model/constants/GameRule.java b/src/main/java/racingcar/model/constants/GameRule.java new file mode 100644 index 000000000..785b7bae9 --- /dev/null +++ b/src/main/java/racingcar/model/constants/GameRule.java @@ -0,0 +1,13 @@ +package racingcar.model.constants; + +public class GameRule { + public static final int NAME_LENGTH_UPPER_LIMIT = 5; + public static final int CARS_COUNT_LOWER_LIMIT = 2; + public static final int CAR_FORWARD_LOWER_LIMIT = 4; + public static final int NUMBER_RANGE_START = 0; + public static final int NUMBER_RANGE_END = 9; + + + private GameRule() { + } +} diff --git a/src/main/java/racingcar/model/domain/Car.java b/src/main/java/racingcar/model/domain/Car.java new file mode 100644 index 000000000..ca276fed9 --- /dev/null +++ b/src/main/java/racingcar/model/domain/Car.java @@ -0,0 +1,47 @@ +package racingcar.model.domain; + +import racingcar.dto.CarDTO; +import racingcar.model.constants.ErrorMessage; +import racingcar.model.constants.GameRule; + +public class Car implements Comparable { + private final String name; + private int position = 0; + + public Car(String name) { + validateName(name); + this.name = name; + } + + private void validateName(String name) { + if (name.length() > GameRule.NAME_LENGTH_UPPER_LIMIT) { + throw new IllegalArgumentException(ErrorMessage.CAR_NAME_LENGTH_OVER); + } + } + + public void move(int number) { + validateNumber(number); + if (number >= GameRule.CAR_FORWARD_LOWER_LIMIT) { + position++; + } + } + + private void validateNumber(int number) { + if (number < GameRule.NUMBER_RANGE_START || number > GameRule.NUMBER_RANGE_END) { + throw new IllegalArgumentException(ErrorMessage.MOVING_NUMBER_OUT_OF_BOUNDS); + } + } + + public String getName() { + return name; + } + + public CarDTO to() { + return new CarDTO(name, position); + } + + @Override + public int compareTo(Car o) { + return o.position - this.position; + } +} diff --git a/src/main/java/racingcar/model/domain/RacingCars.java b/src/main/java/racingcar/model/domain/RacingCars.java new file mode 100644 index 000000000..ef6fdc945 --- /dev/null +++ b/src/main/java/racingcar/model/domain/RacingCars.java @@ -0,0 +1,51 @@ +package racingcar.model.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import racingcar.model.NumberGenerator; +import racingcar.model.constants.ErrorMessage; +import racingcar.model.constants.GameRule; + +public class RacingCars { + private final List cars = new ArrayList<>(); + + private void validateCarNames(List carNames) { + if (carNames.size() < GameRule.CARS_COUNT_LOWER_LIMIT) { + throw new IllegalArgumentException(ErrorMessage.CARS_LACK_OF_COUNT); + } + if (carNames.size() != new HashSet<>(carNames).size()) { + throw new IllegalArgumentException(ErrorMessage.CARS_DUPLICATED_NAME); + } + } + + public void addCars(List carNames) { + validateCarNames(carNames); + carNames.forEach(name -> cars.add(new Car(name))); + } + + public void race(NumberGenerator numberGenerator) { + cars.forEach(car -> car.move(numberGenerator.make())); + } + + public List cars() { + return Collections.unmodifiableList(cars); + } + + public List findWinnerNames() { + List winnerNames = new ArrayList<>(); + for (Car car : cars) { + if (findAnyHeadCar().compareTo(car) == 0) { + winnerNames.add(car.getName()); + } + } + return winnerNames; + } + + private Car findAnyHeadCar() { + List carsToSort = new ArrayList<>(cars); + Collections.sort(carsToSort); + return carsToSort.get(0); + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 000000000..4beb28fc3 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,24 @@ +package racingcar.view; + +import static racingcar.view.constants.Format.INPUT_NAMES_DELIMITER; + +import camp.nextstep.edu.missionutils.Console; +import java.util.List; +import racingcar.view.constants.InputMessage; +import racingcar.view.util.FormatParser; +import racingcar.view.util.NumberParser; + +public class InputView { + public List inputCarNames() { + System.out.println(InputMessage.CAR_NAMES); + String line = Console.readLine(); + return FormatParser.split(line, INPUT_NAMES_DELIMITER); + } + + public int inputMoveCount() { + System.out.println(InputMessage.MOVE_COUNT); + String line = Console.readLine(); + System.out.println(); + return NumberParser.parseDigit(line); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 000000000..fa7bfec51 --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,34 @@ +package racingcar.view; + +import java.util.List; +import racingcar.dto.CarDTO; +import racingcar.dto.CarStatusDTO; +import racingcar.view.constants.Format; +import racingcar.view.constants.OutputMessage; +import racingcar.view.util.FormatParser; + +public class OutputView { + public void printGameResult(List carStatuses) { + System.out.println(OutputMessage.GAME_RESULT); + carStatuses.forEach(this::printCarStatuses); + } + + private void printCarStatuses(CarStatusDTO carStatusDTO) { + List cars = carStatusDTO.getCars(); + cars.forEach(this::printCar); + System.out.println(); + } + + private void printCar(CarDTO carDTO) { + System.out.printf(Format.OUTPUT_CAR_STATUS_FORMAT, carDTO.getName(), + FormatParser.make(carDTO.getPosition(), Format.OUTPUT_CAR_POSITION_UNIT)); + } + + public void printWinners(List winnerNames) { + System.out.printf(Format.OUTPUT_WINNERS_FORMAT, FormatParser.join(winnerNames, Format.OUTPUT_NAMES_DELIMITER)); + } + + public void printErrorMessage(String message) { + System.out.printf(Format.OUTPUT_ERROR_FORMAT, message); + } +} diff --git a/src/main/java/racingcar/view/constants/ErrorMessage.java b/src/main/java/racingcar/view/constants/ErrorMessage.java new file mode 100644 index 000000000..50f6badd8 --- /dev/null +++ b/src/main/java/racingcar/view/constants/ErrorMessage.java @@ -0,0 +1,11 @@ +package racingcar.view.constants; + +public class ErrorMessage { + public static final String FORMAT_EMPTY = "형식 내의 값 중 빈 문자열이 있습니다."; + public static final String FORMAT_INVALID = "입력값의 형식이 잘못되었습니다."; + public static final String NOT_A_DIGIT = "입력값이 자연수가 아닙니다."; + public static final String NOT_A_NUMBER = "입력값이 숫자가 아닙니다."; + + private ErrorMessage() { + } +} diff --git a/src/main/java/racingcar/view/constants/Format.java b/src/main/java/racingcar/view/constants/Format.java new file mode 100644 index 000000000..3810c1ec9 --- /dev/null +++ b/src/main/java/racingcar/view/constants/Format.java @@ -0,0 +1,13 @@ +package racingcar.view.constants; + +public class Format { + public static final String INPUT_NAMES_DELIMITER = ","; + public static final String OUTPUT_NAMES_DELIMITER = ", "; + public static final String OUTPUT_CAR_STATUS_FORMAT = "%s : %s" + System.lineSeparator(); + public static final String OUTPUT_CAR_POSITION_UNIT = "-"; + public static final String OUTPUT_WINNERS_FORMAT = "최종 우승자 : %s" + System.lineSeparator(); + public static final String OUTPUT_ERROR_FORMAT = "[ERROR] %s" + System.lineSeparator(); + + private Format() { + } +} diff --git a/src/main/java/racingcar/view/constants/InputMessage.java b/src/main/java/racingcar/view/constants/InputMessage.java new file mode 100644 index 000000000..506c82782 --- /dev/null +++ b/src/main/java/racingcar/view/constants/InputMessage.java @@ -0,0 +1,9 @@ +package racingcar.view.constants; + +public class InputMessage { + public static final String CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + public static final String MOVE_COUNT = "시도할 회수는 몇회인가요?"; + + private InputMessage() { + } +} diff --git a/src/main/java/racingcar/view/constants/OutputMessage.java b/src/main/java/racingcar/view/constants/OutputMessage.java new file mode 100644 index 000000000..34b8660c3 --- /dev/null +++ b/src/main/java/racingcar/view/constants/OutputMessage.java @@ -0,0 +1,8 @@ +package racingcar.view.constants; + +public class OutputMessage { + public static final String GAME_RESULT = "실행 결과"; + + private OutputMessage() { + } +} diff --git a/src/main/java/racingcar/view/util/FormatParser.java b/src/main/java/racingcar/view/util/FormatParser.java new file mode 100644 index 000000000..e11d8a81b --- /dev/null +++ b/src/main/java/racingcar/view/util/FormatParser.java @@ -0,0 +1,55 @@ +package racingcar.view.util; + +import static racingcar.view.constants.Format.INPUT_NAMES_DELIMITER; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import racingcar.view.constants.ErrorMessage; + +public class FormatParser { + private FormatParser() { + } + + public static List split(String line, String delimiter) { + validateWrongFormat(line); + String[] values = line.split(delimiter); + List parsedValue = Arrays.stream(values) + .map(String::trim) + .collect(Collectors.toList()); + validateEmpty(parsedValue); + return parsedValue; + } + + private static void validateWrongFormat(String line) { + if (hasWrongFormat(line)) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_INVALID); + } + } + + private static boolean hasWrongFormat(String line) { + if (line.isEmpty()) { + return true; + } + return line.startsWith(INPUT_NAMES_DELIMITER) || line.endsWith(INPUT_NAMES_DELIMITER); + } + + private static void validateEmpty(List values) { + if (hasEmptyValue(values)) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_EMPTY); + } + } + + private static boolean hasEmptyValue(List values) { + return values.stream() + .anyMatch(String::isEmpty); + } + + public static String join(List values, String delimiter) { + return String.join(delimiter, values); + } + + public static String make(int count, String unit) { + return unit.repeat(count); + } +} diff --git a/src/main/java/racingcar/view/util/NumberParser.java b/src/main/java/racingcar/view/util/NumberParser.java new file mode 100644 index 000000000..3ae9bc410 --- /dev/null +++ b/src/main/java/racingcar/view/util/NumberParser.java @@ -0,0 +1,24 @@ +package racingcar.view.util; + +import racingcar.view.constants.ErrorMessage; + +public class NumberParser { + private NumberParser() { + } + + public static int parseDigit(String value) { + int number = parseInteger(value); + if (number <= 0) { + throw new IllegalArgumentException(ErrorMessage.NOT_A_DIGIT); + } + return number; + } + + public static int parseInteger(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.NOT_A_NUMBER); + } + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 8f6f70a21..6332501aa 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -1,12 +1,12 @@ package racingcar; -import camp.nextstep.edu.missionutils.test.NsTest; -import org.junit.jupiter.api.Test; - import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest; import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; import static org.assertj.core.api.Assertions.assertThat; +import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.Test; + class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; private static final int STOP = 3; @@ -16,21 +16,21 @@ class ApplicationTest extends NsTest { @Test void 전진_정지() { assertRandomNumberInRangeTest( - () -> { - run("pobi,woni", "1"); - assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); - }, - MOVING_FORWARD, STOP + () -> { + run("pobi,woni", "1"); + assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); + }, + MOVING_FORWARD, STOP ); } @Test void 이름에_대한_예외_처리() { assertSimpleTest( - () -> { - runException("pobi,javaji"); - assertThat(output()).contains(ERROR_MESSAGE); - } + () -> { + runException("pobi,javaji"); + assertThat(output()).contains(ERROR_MESSAGE); + } ); } diff --git a/src/test/java/racingcar/model/RacingGameTest.java b/src/test/java/racingcar/model/RacingGameTest.java new file mode 100644 index 000000000..d17e85b19 --- /dev/null +++ b/src/test/java/racingcar/model/RacingGameTest.java @@ -0,0 +1,31 @@ +package racingcar.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import org.junit.jupiter.api.Test; + +class RacingGameTest { + @Test + void 우승자_계산_성공() { + RacingGame racingGame = new RacingGame(new TestNumberGenerator()); + racingGame.enrollCars(List.of("pobi", "jason", "java", "joon")); + racingGame.repeatMovingCars(1); + + assertThat(racingGame.findWinners()).containsOnly("java", "joon"); + } + + private static class TestNumberGenerator implements NumberGenerator { + private final Queue numbers = new LinkedList<>(List.of(1, 3, 4, 4)); + + @Override + public int make() { + if (numbers.isEmpty()) { + return -1; + } + return numbers.poll(); + } + } +} \ No newline at end of file diff --git a/src/test/java/racingcar/model/domain/CarTest.java b/src/test/java/racingcar/model/domain/CarTest.java new file mode 100644 index 000000000..513483a72 --- /dev/null +++ b/src/test/java/racingcar/model/domain/CarTest.java @@ -0,0 +1,35 @@ +package racingcar.model.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class CarTest { + @Test + void 이동_전진() { + Car car = new Car("테스트"); + car.move(4); + + assertThat(car.to().getPosition()).isEqualTo(1); + } + + @Test + void 이동_정지() { + Car car = new Car("테스트"); + car.move(3); + + assertThat(car.to().getPosition()).isEqualTo(0); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 10}) + void 이동_범위_밖_수_예외발생(int number) { + Car car = new Car("테스트"); + + assertThatThrownBy(() -> car.move(number)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/racingcar/model/domain/RacingCarsTest.java b/src/test/java/racingcar/model/domain/RacingCarsTest.java new file mode 100644 index 000000000..c9bf538a0 --- /dev/null +++ b/src/test/java/racingcar/model/domain/RacingCarsTest.java @@ -0,0 +1,32 @@ +package racingcar.model.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class RacingCarsTest { + @Test + void 자동차_등록_개수_예외발생() { + RacingCars racingCars = new RacingCars(); + + assertThatThrownBy(() -> racingCars.addCars(List.of("자동차명"))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 자동차_등록_이름길이_예외발생() { + RacingCars racingCars = new RacingCars(); + + assertThatThrownBy(() -> racingCars.addCars(List.of("자동차명여섯"))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 자동차_등록_중복이름_예외발생() { + RacingCars racingCars = new RacingCars(); + + assertThatThrownBy(() -> racingCars.addCars(List.of("자동차명", "자동차명"))) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file