diff --git a/README.md b/README.md index d0286c8..1b77aa0 100644 --- a/README.md +++ b/README.md @@ -1 +1,103 @@ -# java-racingcar-precourse +# java-racingCar-precourse + + +> 콩 번째로 하는 우테코의 콩 번째 미션 \ +> 콩 번째로 하는 우테코의 콩 번째 미션 + +
+ 과제 세부 내용 + +## 과제 내용 +초간단 자동차 경주 게임을 구현한다. + +- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. +- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. + +### 입출력 +- 입력 + - 경주 할 자동차 이름 + - 시도할 횟수 +- 출력 + - 각 차수별 실행 결과 + - 우승자 안내 문구 + +ex) + +``` +경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) +pobi,woni,jun +시도할 회수는 몇회인가요? +5 + +실행 결과 +pobi : - +woni : +jun : - + +pobi : -- +woni : - +jun : -- + +pobi : --- +woni : -- +jun : --- + +pobi : ---- +woni : --- +jun : ---- + +pobi : ----- +woni : ---- +jun : ----- + +최종 우승자 : pobi, jun +``` + +
+ +## 코드 흐름 +- 사용자의 이름 및 시도 횟수를 입력받는다. 이름의 유효성을 검증한다. +- 랜덤으로 자동차의 전진 횟수를 더한다. +- 최종 우승자를 출력한다. + +```mermaid +sequenceDiagram + participant View + participant Controller + participant Model + + Controller->>View: 입력 대기 + View->>Controller: 이름, 시도횟수 반환 + Controller->>Model: 이름, 시도횟수 전달 + Model->>Controller: 게임 생성 + loop 시도횟수만큼 반복 + Controller->>Model: 실행 대기 + Model->>Controller: 실행 결과 반환 + Controller->>View: 실행 결과 전달 + View->>View: 결과 출력 + View->>Controller: - + end + Controller->>Model: 우승자 요청 + Model->>Controller: 우승자 반환 + Controller->>View: 우승자 전달 + View->>View: 우승자 출력 + View->>Controller: - + Controller->>Controller: 프로그램 종료 + +``` + +## 구현 기능 목록 +- 입출력 + - [ ] 사용자 이름 입력 + - [ ] 시도 횟수 입력 + - [ ] 차수별 실행 결과 출력 + - [ ] 최종 우승자 출력 +- 자동차 전진 + - [ ] 여러 자동차의 상태 관리 + - [ ] 랜덤 추출 기능 \ No newline at end of file diff --git a/src/main/java/racingcar/AppConfig.java b/src/main/java/racingcar/AppConfig.java new file mode 100644 index 0000000..d49c0f7 --- /dev/null +++ b/src/main/java/racingcar/AppConfig.java @@ -0,0 +1,40 @@ +package racingcar; + +import racingcar.controller.RacingcarController; +import racingcar.service.CarControlService; +import racingcar.service.RandomService; +import racingcar.view.InputView; +import racingcar.view.OutputView; +import racingcar.view.provider.InputProvider; +import racingcar.view.provider.WoowaInputProvider; + +public class AppConfig { + public InputProvider inputProvider() { + return new WoowaInputProvider(); + } + + public InputView inputView() { + return new InputView(inputProvider()); + } + + public OutputView outputView() { + return new OutputView(); + } + + public CarControlService carControlService() { + return new CarControlService(); + } + + public RandomService randomService() { + return new RandomService(); + } + + public RacingcarController racingcarController() { + return new RacingcarController( + inputView(), + outputView(), + carControlService(), + randomService() + ); + } +} diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e..51d3407 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,16 @@ package racingcar; +import racingcar.controller.RacingcarController; + public class Application { + static AppConfig appConfig; + static RacingcarController racingcarController; + public static void main(String[] args) { // TODO: 프로그램 구현 + appConfig = new AppConfig(); + racingcarController = appConfig.racingcarController(); + + racingcarController.run(); } } diff --git a/src/main/java/racingcar/controller/RacingcarController.java b/src/main/java/racingcar/controller/RacingcarController.java new file mode 100644 index 0000000..17e6b67 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingcarController.java @@ -0,0 +1,55 @@ +package racingcar.controller; + +import java.util.List; +import java.util.stream.IntStream; +import racingcar.model.dto.CarMovementDto; +import racingcar.service.CarControlService; +import racingcar.service.RandomService; +import racingcar.utils.Validator; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class RacingcarController { + InputView inputView; + OutputView outputView; + CarControlService carControlService; + RandomService randomService; + + private int trials; + + public RacingcarController(InputView inputView, OutputView outputView, + CarControlService carControlService, RandomService randomService) { + this.inputView = inputView; + this.outputView = outputView; + this.carControlService = carControlService; + this.randomService = randomService; + } + + public void run() { + initialize(); + execute(); + getResult(); + } + + private void initialize() { + String names = inputView.getCarName(); + trials = inputView.getTrialNumber(); + Validator.naturalNumberCheck(trials); + carControlService.initialize(names); + outputView.newLine(); + } + + private void execute() { + outputView.printResultGuide(); + IntStream.range(0, trials).forEach((t) -> { + List progress = carControlService.playTurn(randomService::willCarMove); + outputView.printPlayerResult(progress); + outputView.newLine(); + }); + } + + private void getResult() { + List winner = carControlService.getWinner(); + outputView.printWinner(winner); + } +} diff --git a/src/main/java/racingcar/model/Car.java b/src/main/java/racingcar/model/Car.java new file mode 100644 index 0000000..79eab81 --- /dev/null +++ b/src/main/java/racingcar/model/Car.java @@ -0,0 +1,21 @@ +package racingcar.model; + +import racingcar.model.dto.CarMovementDto; + +public class Car { + private final String name; + private int position; + + public Car(String name) { + this.name = name; + position = 0; + } + + public CarMovementDto getData() { + return new CarMovementDto(name, position); + } + + public void move() { + position++; + } +} diff --git a/src/main/java/racingcar/model/Race.java b/src/main/java/racingcar/model/Race.java new file mode 100644 index 0000000..ee6b08e --- /dev/null +++ b/src/main/java/racingcar/model/Race.java @@ -0,0 +1,51 @@ +package racingcar.model; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Supplier; +import racingcar.model.dto.CarMovementDto; + +public class Race { + private int turn; + private final List cars; + + private Race() { + cars = new ArrayList<>(); + } + + public static Race init(List cars) { + Race race = new Race(); + race.cars.addAll(cars.stream().map(Car::new).toList()); + race.turn = 0; + return race; + } + + public void advance(Supplier movementFunction) { + cars.forEach((car) -> { + if (movementFunction.get()) { + car.move(); + } + }); + turn++; + } + + public List getResult() { + return cars.stream().map((Car::getData)).toList(); + } + + public List getWinner() { + int maxValue = cars.stream() + .map(car -> car.getData().movement()) + .max(Comparator.naturalOrder()).orElse(0); + + return cars.stream() + .filter(car -> car.getData().movement() == maxValue) + .map(car -> car.getData().name()) + .toList(); + } + + public int getTurn() { + return turn; + } +} diff --git a/src/main/java/racingcar/model/dto/CarMovementDto.java b/src/main/java/racingcar/model/dto/CarMovementDto.java new file mode 100644 index 0000000..31c8c18 --- /dev/null +++ b/src/main/java/racingcar/model/dto/CarMovementDto.java @@ -0,0 +1,16 @@ +package racingcar.model.dto; + +public record CarMovementDto(String name, int movement) { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CarMovementDto that = (CarMovementDto) o; + return movement == that.movement && name.equals(that.name); + } +} diff --git a/src/main/java/racingcar/service/CarControlService.java b/src/main/java/racingcar/service/CarControlService.java new file mode 100644 index 0000000..f665b0e --- /dev/null +++ b/src/main/java/racingcar/service/CarControlService.java @@ -0,0 +1,40 @@ +package racingcar.service; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import racingcar.model.Race; +import racingcar.model.dto.CarMovementDto; +import racingcar.utils.Validator; + +public class CarControlService { + private Race race; + + private static final int MIN_CHAR_LENGTH = 1; + private static final int MAX_CHAR_LENGTH = 5; + private static final String COMMA = ","; + + public void initialize(String names) { + List nameList = parseNames(names); + validateName(nameList); + this.race = Race.init(nameList); + } + + private List parseNames(String names) { + return Arrays.stream(names.trim().split(COMMA)).toList(); + } + + public List playTurn(Supplier movementFunction) { + race.advance(movementFunction); + return race.getResult(); + } + + public List getWinner() { + return race.getWinner(); + } + + private void validateName(List nameList) { + nameList.forEach((name) -> Validator.stringRangeCheck(name, MIN_CHAR_LENGTH, MAX_CHAR_LENGTH)); + Validator.duplicationCheck(nameList); + } +} diff --git a/src/main/java/racingcar/service/RandomService.java b/src/main/java/racingcar/service/RandomService.java new file mode 100644 index 0000000..ec2c613 --- /dev/null +++ b/src/main/java/racingcar/service/RandomService.java @@ -0,0 +1,13 @@ +package racingcar.service; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RandomService { + private static final int LOWER_BOUND = 0; + private static final int UPPER_BOUND = 9; + private static final int THRESHOLD = 4; + + public boolean willCarMove() { + return Randoms.pickNumberInRange(LOWER_BOUND, UPPER_BOUND) >= THRESHOLD; + } +} diff --git a/src/main/java/racingcar/utils/MessageConstants.java b/src/main/java/racingcar/utils/MessageConstants.java new file mode 100644 index 0000000..a81989e --- /dev/null +++ b/src/main/java/racingcar/utils/MessageConstants.java @@ -0,0 +1,19 @@ +package racingcar.utils; + +public enum MessageConstants { + INPUT_CAR_NAME_GUIDE("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"), + INPUT_TRIAL_NUMBER_GUIDE("시도할 회수는 몇회인가요?"), + OUTPUT_EXECUTE_RESULT_GUIDE("실행 결과"), + OUTPUT_FINAL_WINNER_GUIDE("최종 우승자 : "), + OUTPUT_CAR_MOVEMENT("%s : "); + + private final String message; + + MessageConstants(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/racingcar/utils/Validator.java b/src/main/java/racingcar/utils/Validator.java new file mode 100644 index 0000000..eacbc0a --- /dev/null +++ b/src/main/java/racingcar/utils/Validator.java @@ -0,0 +1,26 @@ +package racingcar.utils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Validator { + public static void stringRangeCheck(String input, int min, int max) { + if (input.length() <= min || input.length() > max) { + throw new IllegalArgumentException("[Error] Invalid character length."); + } + } + + public static void duplicationCheck(List itemList) { + Set uniqueNames = new HashSet<>(itemList); + if (uniqueNames.size() != itemList.size()) { + throw new IllegalArgumentException("[Error] Duplicated."); + } + } + + public static void naturalNumberCheck(int num) { + if (num <= 0) { + throw new IllegalArgumentException("[Error] Invalid number."); + } + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000..6aecbb5 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,30 @@ +package racingcar.view; + +import racingcar.utils.MessageConstants; +import racingcar.view.provider.InputProvider; + +public class InputView { + private final InputProvider inputProvider; + + public InputView(InputProvider inputProvider) { + this.inputProvider = inputProvider; + } + + public String getCarName() { + try { + System.out.println(MessageConstants.INPUT_CAR_NAME_GUIDE.getMessage()); + return inputProvider.readLine(); + } catch (Exception exception) { + throw new IllegalArgumentException("[Error] 입출력 과정에서 예외가 발생했습니다. "); + } + } + + public int getTrialNumber() { + try { + System.out.println(MessageConstants.INPUT_TRIAL_NUMBER_GUIDE.getMessage()); + return Integer.parseInt(inputProvider.readLine()); + } catch (Exception exception) { + throw new IllegalArgumentException("[Error] 입출력 과정에서 예외가 발생했습니다. "); + } + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000..353df7f --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,33 @@ +package racingcar.view; + +import java.util.List; +import racingcar.model.dto.CarMovementDto; +import racingcar.utils.MessageConstants; + +public class OutputView { + private final String SEPARATOR = ", "; + private final String MOVEMENT_DELIMITER = "-"; + + public void printResultGuide() { + System.out.println(MessageConstants.OUTPUT_EXECUTE_RESULT_GUIDE.getMessage()); + } + + public void printPlayerResult(List playerResult) { + playerResult.forEach((result) -> + System.out.println(String.format( + MessageConstants.OUTPUT_CAR_MOVEMENT.getMessage(), result.name() + ) + MOVEMENT_DELIMITER.repeat(result.movement())) + ); + } + + public void printWinner(List names) { + System.out.println( + MessageConstants.OUTPUT_FINAL_WINNER_GUIDE.getMessage() + + String.join(SEPARATOR, names) + ); + } + + public void newLine() { + System.out.println(); + } +} diff --git a/src/main/java/racingcar/view/provider/InputProvider.java b/src/main/java/racingcar/view/provider/InputProvider.java new file mode 100644 index 0000000..539c856 --- /dev/null +++ b/src/main/java/racingcar/view/provider/InputProvider.java @@ -0,0 +1,7 @@ +package racingcar.view.provider; + +import java.io.IOException; + +public interface InputProvider { + String readLine() throws IOException; +} diff --git a/src/main/java/racingcar/view/provider/WoowaInputProvider.java b/src/main/java/racingcar/view/provider/WoowaInputProvider.java new file mode 100644 index 0000000..d1e1270 --- /dev/null +++ b/src/main/java/racingcar/view/provider/WoowaInputProvider.java @@ -0,0 +1,10 @@ +package racingcar.view.provider; + +import camp.nextstep.edu.missionutils.Console; + +public class WoowaInputProvider implements InputProvider { + @Override + public String readLine() { + return Console.readLine(); + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc3..f2c2495 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -1,12 +1,17 @@ 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 static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; @@ -15,22 +20,204 @@ 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().trim()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); + }, + MOVING_FORWARD, STOP ); } @Test void 예외_테스트() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("pobi,javaji", "1")) - .isInstanceOf(IllegalArgumentException.class) + assertThatThrownBy(() -> runException("pobi,javaji", "1")) + .isInstanceOf(IllegalArgumentException.class) ); } + @Nested + @DisplayName("Level 1") + class Level1 { + @Test + @DisplayName("기본 기능 테스트") + void basicTest() { + Integer[] numbers = Stream.concat( + Stream.of(MOVING_FORWARD, STOP, STOP), + Collections.nCopies(12, MOVING_FORWARD).stream() + ).toArray(Integer[]::new); + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,jun", "5"); + assertThat(output().trim()).contains( + "pobi : -----", "woni : ----", "jun : ----", + "최종 우승자 : pobi" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + + ); + } + + @Test + @DisplayName("랜덤 범위 테스트") + void randomRangeTest() { + Integer[] numbers = {0, 9, 1, 8, 2, 7, 3, 6, 4, 5}; + assertRandomNumberInRangeTest( + () -> { + run("loser,winer", "5"); + assertThat(output().trim()).contains( + "loser : -", "winer : -----" + ); + assertThat(output().trim()).doesNotContain( + "loser : --", "winer : ------" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + ); + } + + @Test + @DisplayName("이름 길이 범위 테스트") + void longNameTest() { + assertSimpleTest(() -> { + assertThatThrownBy(() -> runException("java,script", "1")) + .isInstanceOf(IllegalArgumentException.class); + }); + } + } + + @Nested + @DisplayName("Level 2") + class Level2 { + @Test + @DisplayName("단일 우승자 테스트") + void singleWinnerTest() { + Integer[] numbers = {MOVING_FORWARD, STOP, STOP}; + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,jun", "1"); + assertThat(output().trim()).contains( + "최종 우승자 : pobi" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + + ); + } + + @Test + @DisplayName("공동 우승자(2명) 테스트") + void doubleWinnerTest() { + Integer[] numbers = {MOVING_FORWARD, MOVING_FORWARD, STOP}; + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,jun", "1"); + assertThat(output().trim()).contains( + "최종 우승자 : pobi, woni" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + + ); + } + + @Test + @DisplayName("공동 우승자(3명) 테스트") + void severalWinnerTest() { + Integer[] numbers = {MOVING_FORWARD, MOVING_FORWARD, MOVING_FORWARD}; + assertRandomNumberInRangeTest( + () -> { + run("pobi,woni,jun", "1"); + assertThat(output().trim()).contains( + "최종 우승자 : pobi, woni, jun" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + + ); + } + } + + @Nested + @DisplayName("Level 3") + class Level3 { + @Test + @DisplayName("많은 시도횟수 테스트") + void veryBigTrialsTest() { + Integer[] numbers = Collections.nCopies(200, MOVING_FORWARD).toArray(Integer[]::new); + + assertRandomNumberInRangeTest( + () -> { + run("hogun,park", "100"); + assertThat(output().trim()).contains( + "hogun : ----------------------------------------------------------------------------------------------------"); + assertThat(output().trim()).contains( + "park : ----------------------------------------------------------------------------------------------------"); + assertThat(output().trim()).doesNotContain( + "park : -----------------------------------------------------------------------------------------------------"); + assertThat(output().trim()).doesNotContain( + "park : -----------------------------------------------------------------------------------------------------"); + + assertThat(output().trim()).contains( + "최종 우승자 : hogun, park" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + ); + } + + @Test + @DisplayName("공백 이름 테스트") + void blankBetweenNameTest() { + Integer[] numbers = {MOVING_FORWARD, STOP}; + assertRandomNumberInRangeTest( + () -> { + run("ja va,ji gi", "1"); + assertThat(output().trim()).contains( + "최종 우승자 : ja va" + ); + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + + ); + } + + @Test + @DisplayName("빈 입력 테스트 테스트") + void emptyAnswerTest() { + assertThatThrownBy(() -> run("", "1")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("빈 이름 테스트") + void emptyNameTest() { + Integer[] numbers = Stream.concat( + Stream.of(STOP, MOVING_FORWARD, STOP), + Collections.nCopies(12, MOVING_FORWARD).stream() + ).toArray(Integer[]::new); + assertRandomNumberInRangeTest( + () -> { + try { + run("gamja,,yy", "1"); + assertThat(output().trim()).contains("최종 우승자 : yy"); + } catch (Exception exception) { + assertThat(exception).isInstanceOf(IllegalArgumentException.class); + } + }, numbers[0], Arrays.copyOfRange(numbers, 1, numbers.length) + + ); + } + + @Test + @DisplayName("음수 시도 횟수 테스트") + void minusTrialTest() { + assertThatThrownBy(() -> run("pobi,jun", "-1")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("이름 중복 테스트") + void duplicatedPlayerTest() { + assertThatThrownBy(() -> run("pobi,woni,pobi", "1")) + .isInstanceOf(IllegalArgumentException.class); + } + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/racingcar/MockInputProvider.java b/src/test/java/racingcar/MockInputProvider.java new file mode 100644 index 0000000..9605d02 --- /dev/null +++ b/src/test/java/racingcar/MockInputProvider.java @@ -0,0 +1,21 @@ +package racingcar; + +import java.io.IOException; +import racingcar.view.provider.InputProvider; + +public class MockInputProvider implements InputProvider { + private final String[] inputs; + private int index = 0; + + public MockInputProvider(String... inputs) { + this.inputs = inputs; + } + + @Override + public String readLine() throws IOException { + if (index < inputs.length) { + return inputs[index++]; + } + throw new IOException(); + } +} diff --git a/src/test/java/racingcar/MockRandomProvider.java b/src/test/java/racingcar/MockRandomProvider.java new file mode 100644 index 0000000..4c77af1 --- /dev/null +++ b/src/test/java/racingcar/MockRandomProvider.java @@ -0,0 +1,17 @@ +package racingcar; + +public class MockRandomProvider { + private final boolean[] randomStack; + private int index = 0; + + public MockRandomProvider(boolean... randomStack) { + this.randomStack = randomStack; + } + + public boolean mockingRandom() { + if (index <= randomStack.length) { + return randomStack[index++]; + } + throw new IllegalStateException(); + } +} diff --git a/src/test/java/racingcar/ModelTest.java b/src/test/java/racingcar/ModelTest.java new file mode 100644 index 0000000..0bc759b --- /dev/null +++ b/src/test/java/racingcar/ModelTest.java @@ -0,0 +1,63 @@ +package racingcar; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import racingcar.model.Race; +import racingcar.model.dto.CarMovementDto; + +public class ModelTest { + private MockRandomProvider mockRandomProvider; + + @BeforeEach + void setUp() { + mockRandomProvider = new MockRandomProvider( + true, false, true, + true, true, true, + true, true, true, + true, true, true, + true, true, true + ); + } + + @Nested + @DisplayName("경주 테스트") + class RaceTest { + @Test + @DisplayName("초기화 테스트") + public void initialTest() { + Race race = Race.init(List.of("pobi", "woni", "jun")); + assertEquals(0, race.getTurn()); + assertEquals( + List.of( + new CarMovementDto("pobi", 0), + new CarMovementDto("woni", 0), + new CarMovementDto("jun", 0) + ), + race.getResult() + ); + } + + @Test + @DisplayName("이동 테스트") + public void movementTest() { + Race race = Race.init(List.of("pobi", "woni", "jun")); + IntStream.range(0, 5).forEach(i -> race.advance(mockRandomProvider::mockingRandom)); + assertEquals(5, race.getTurn()); + assertEquals( + List.of( + new CarMovementDto("pobi", 5), + new CarMovementDto("woni", 4), + new CarMovementDto("jun", 5) + ), + race.getResult() + ); + assertEquals(List.of("pobi", "jun"), race.getWinner()); + } + } +} diff --git a/src/test/java/racingcar/ViewTest.java b/src/test/java/racingcar/ViewTest.java new file mode 100644 index 0000000..877a356 --- /dev/null +++ b/src/test/java/racingcar/ViewTest.java @@ -0,0 +1,90 @@ +package racingcar; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import racingcar.model.dto.CarMovementDto; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class ViewTest extends NsTest { + private InputView inputView; + private OutputView outputView; + + @BeforeEach + void setUp() { + inputView = new InputView(new MockInputProvider("pobi,woni,jun", "5")); + outputView = new OutputView(); + } + + @Nested + @DisplayName("입력 테스트") + class InputTest { + @Test + @DisplayName("차 이름 테스트") + void carNameInputTest() { + String carNames = inputView.getCarName(); + assertEquals("pobi,woni,jun", carNames); + } + + @Test + @DisplayName("시도 횟수 테스트") + void trialInputTest() { + inputView.getCarName(); + int trialNumber = inputView.getTrialNumber(); + assertEquals(5, trialNumber); + } + } + + @Nested + @DisplayName("출력 테스트") + class OutputTest { + @Test + @DisplayName("결과 메시지 출력 테스트") + void printResultGuideTest() { + assertSimpleTest(() -> { + outputView.printResultGuide(); + assertThat(output()).contains("실행 결과"); + }); + } + + @Test + @DisplayName("경주 진행 출력 테스트") + void printPlayerResultTest() { + assertSimpleTest(() -> { + outputView.printPlayerResult( + List.of( + new CarMovementDto("pobi", 4), + new CarMovementDto("woni", 0), + new CarMovementDto("jun", 1) + ) + ); + assertThat(output()).isEqualTo("pobi : ----\nwoni : \njun : -"); + }); + } + + @ParameterizedTest + @DisplayName("우승자 출력 테스트") + @CsvSource({"pobi", "pobi, jun", "pobi, woni, jun"}) + void printWinnerTest(String winners) { + assertSimpleTest(() -> { + List winnerList = List.of(winners.split(", ")); + outputView.printWinner(winnerList); + assertThat(output()).isEqualTo("최종 우승자 : " + winners); + }); + } + } + + @Override + public void runMain() { + } +}