diff --git a/docs/README.md b/docs/README.md
index e69de29..ddf62b4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -0,0 +1,108 @@
+## Feature
+
+- [X] 컴퓨터는 1..9 범위에서 서로 다른 임의의 수 3개 선택
+- [X] 유저는 1..9 범위에서 서로 다른 숫자 3개를 입력한다
+ - [X] 숫자가 아닌 값을 입력한 경우 `IllegalArgumentException` 발생
+- [X] 심판은 `컴퓨터 <-> 유저의 3가지 숫자`를 토대로 숫자 야구 게임 결과를 도출한다
+ - 비교 후 `strikeCount & ballCount` 정보를 `Result`로 Wrapping한 후 결과를 발송한다
+- [X] 컴퓨터가 선택한 모든 숫자를 맞히면 게임은 클리어된다
+ - [X] 사용자는 재시작[1], 종료[2] 중 하나를 입력한다
+ - [X] 숫자가 아닌 값을 입력한 경우 `IllegalArgumentException` 발생
+
+
+
+
+## Model
+
+### `Baseballs`
+
+- 입력한 3개의 숫자들을 추상화시킨 `Baseballs`
+ - Baseballs에 속한 `List`은
+ - [X] 각 원소가 `1..9` 범위 사이여야 한다
+ - [X] 원소의 크기가 3이여야 한다
+ - [X] 중복된 원소가 없어야 한다
+
+
+
+### `Computer: 컴퓨터`
+
+- 1..9 범위에서 서로 다른 임의의 수 3개 선택
+ - Computer는 중복 숫자를 선택하지 않도록 구현
+- User 선택을 통한 결과 산출의 기준
+
+
+
+### `User: 사용자`
+
+- 숫자 3개 입력
+ - 숫자를 입력하지 않을 경우 (IllegalArgumentException)
+- 게임 재시작/종료와 관련된 커맨드 입력
+ - 숫자를 입력하지 않을 경우 (IllegalArgumentException)
+
+
+
+### `Referee: 심판`
+
+- 플레이어 점수 채점
+ - 숫자 겹침 + 동일 위치 = 스트라이크
+ - 숫자 겹침 + 다른 위치 = 볼
+ - 숫자 안겹침 = 낫싱
+
+
+
+### `Result: 채점 결과`
+
+- 스트라이크 & 볼에 대한 count field 보유
+
+
+
+### `GameStatus: 게임 상태`
+
+- 진행중인 게임에 대한 상태
+ - `GAME_RUN` -> 게임 실행
+ - `GAME_TERMINATE` -> 게임 종료
+
+
+
+### `GameProcessDecider: 재시작 or 종료 결정자`
+
+- 게임 클리어 후 종료 or 재시작에 대한 커맨더
+
+
+
+
+## Utils
+
+### `ExceptionConstants`
+
+- 전역 예외 메시지 통합 컴포넌트
+
+### `BaseballConstants`
+
+- Baseball에 대한 상수 전용 컴포넌트
+ - 숫자 범위
+ - Baseball 개수
+
+
+
+
+## View
+
+### `InputView`
+
+- 사용자 Input을 받기 위한 컴포넌트
+
+### `OutputView`
+
+- 게임 진행과 관련된 출력 컴포넌트
+
+
+
+
+## Controller
+
+### `GameController`
+
+- 게임 진행과 관련된 컨트롤러
+
+
diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java
index dd95a34..119c1e3 100644
--- a/src/main/java/baseball/Application.java
+++ b/src/main/java/baseball/Application.java
@@ -1,7 +1,9 @@
package baseball;
+import baseball.controller.GameController;
+
public class Application {
public static void main(String[] args) {
- // TODO: 프로그램 구현
+ new GameController().run();
}
}
diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java
new file mode 100644
index 0000000..b3f1db8
--- /dev/null
+++ b/src/main/java/baseball/controller/GameController.java
@@ -0,0 +1,67 @@
+package baseball.controller;
+
+import baseball.model.*;
+import baseball.view.InputView;
+import baseball.view.OutputView;
+
+import java.util.List;
+
+import static baseball.model.GameProcessDecider.GAME_RESTART;
+import static baseball.model.GameStatus.GAME_RUNNING;
+import static baseball.model.GameStatus.GAME_TERMINATE;
+
+public class GameController {
+ private static GameStatus gameStatus;
+ private Computer computer;
+ private User user;
+
+ public GameController() {
+ computer = new Computer();
+ gameStatus = GAME_RUNNING;
+ }
+
+ public void run() {
+ // 게임 시작
+ OutputView.printGameStart();
+
+ while (gameStatus.isGameNotTerminated()) {
+ // User - Baseball 입력
+ readUserBaseballInput();
+
+ // 게임 결과 확인
+ Result result = judgeGameByReferee();
+ OutputView.printGameResult(result);
+
+ // 게임 클리어 확인
+ checkGameClear(result);
+ }
+ }
+
+ private void readUserBaseballInput() {
+ List userBaseballs = InputView.readUserBaseballs();
+ user = new User(userBaseballs);
+ }
+
+ private Result judgeGameByReferee() {
+ return Referee.judge(computer.getBaseballs(), user.getBaseballs());
+ }
+
+ private void checkGameClear(final Result result) {
+ if (result.isGameClear()) {
+ OutputView.printGameClear();
+ determineGameRestartOrEnd();
+ }
+ }
+
+ private void determineGameRestartOrEnd() {
+ int userCommand = InputView.readUserRestartCommand();
+ GameProcessDecider decider = GameProcessDecider.getDecider(userCommand);
+
+ if (decider == GAME_RESTART) {
+ computer = new Computer();
+ gameStatus = GAME_RUNNING;
+ } else {
+ gameStatus = GAME_TERMINATE;
+ }
+ }
+}
diff --git a/src/main/java/baseball/model/Baseballs.java b/src/main/java/baseball/model/Baseballs.java
new file mode 100644
index 0000000..c0df58e
--- /dev/null
+++ b/src/main/java/baseball/model/Baseballs.java
@@ -0,0 +1,51 @@
+package baseball.model;
+
+import java.util.Collections;
+import java.util.List;
+
+import static baseball.utils.BaseballConstants.*;
+import static baseball.utils.ExceptionConstants.BaseballException.*;
+
+public class Baseballs {
+ private final List baseballs;
+
+ public Baseballs(final List baseballs) {
+ validateEachBaseballElementIsInRange(baseballs);
+ validateTotalBaseballSize(baseballs);
+ validateBaseballHasDuplicateNumber(baseballs);
+ this.baseballs = baseballs;
+ }
+
+ private void validateEachBaseballElementIsInRange(final List baseballs) {
+ if (hasOutOfRange(baseballs)) {
+ throw new IllegalArgumentException(BASEBALL_IS_NOT_IN_RANGE.message);
+ }
+ }
+
+ private boolean hasOutOfRange(final List baseballs) {
+ return baseballs.stream()
+ .anyMatch(baseball -> baseball < MIN_BASEBALL || baseball > MAX_BASEBALL);
+ }
+
+ private void validateTotalBaseballSize(final List baseballs) {
+ if (baseballs.size() != BASEBALL_SIZE) {
+ throw new IllegalArgumentException(BASEBALL_SIZE_IS_NOT_FULFILL.message);
+ }
+ }
+
+ private void validateBaseballHasDuplicateNumber(final List baseballs) {
+ if (hasDuplicateNumber(baseballs)) {
+ throw new IllegalArgumentException(BASEBALL_MUST_BE_UNIQUE.message);
+ }
+ }
+
+ private boolean hasDuplicateNumber(final List baseballs) {
+ return baseballs.stream()
+ .distinct()
+ .count() != BASEBALL_SIZE;
+ }
+
+ public List getBaseballs() {
+ return Collections.unmodifiableList(baseballs);
+ }
+}
diff --git a/src/main/java/baseball/model/Computer.java b/src/main/java/baseball/model/Computer.java
new file mode 100644
index 0000000..e0eaa74
--- /dev/null
+++ b/src/main/java/baseball/model/Computer.java
@@ -0,0 +1,48 @@
+package baseball.model;
+
+import camp.nextstep.edu.missionutils.Randoms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static baseball.utils.BaseballConstants.*;
+
+public class Computer {
+ private final Baseballs baseballs;
+
+ public Computer() {
+ this.baseballs = generateRandomBaseballs();
+ }
+
+ private Baseballs generateRandomBaseballs() {
+ List baseballs = new ArrayList<>();
+
+ while (isNotReachedLimitSize(baseballs)) {
+ int randomNumber = getRandomNumberInRange();
+ if (isRandomNumberAbsentInBaseballs(baseballs, randomNumber)) {
+ baseballs.add(randomNumber);
+ }
+ }
+
+ return new Baseballs(baseballs);
+ }
+
+ private boolean isNotReachedLimitSize(final List baseballs) {
+ return baseballs.size() < BASEBALL_SIZE;
+ }
+
+ private int getRandomNumberInRange() {
+ return Randoms.pickNumberInRange(MIN_BASEBALL, MAX_BASEBALL);
+ }
+
+ private boolean isRandomNumberAbsentInBaseballs(
+ final List baseballs,
+ final int randomNumber
+ ) {
+ return !baseballs.contains(randomNumber);
+ }
+
+ public List getBaseballs() {
+ return baseballs.getBaseballs();
+ }
+}
diff --git a/src/main/java/baseball/model/GameProcessDecider.java b/src/main/java/baseball/model/GameProcessDecider.java
new file mode 100644
index 0000000..fba54b4
--- /dev/null
+++ b/src/main/java/baseball/model/GameProcessDecider.java
@@ -0,0 +1,24 @@
+package baseball.model;
+
+import java.util.Arrays;
+
+import static baseball.utils.ExceptionConstants.GameProcessCommandException.INVALID_COMMAND;
+
+public enum GameProcessDecider {
+ GAME_RESTART(1),
+ GAME_END(2),
+ ;
+
+ private final int command;
+
+ GameProcessDecider(final int command) {
+ this.command = command;
+ }
+
+ public static GameProcessDecider getDecider(final int userCommand) {
+ return Arrays.stream(values())
+ .filter(restartDecider -> restartDecider.command == userCommand)
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(INVALID_COMMAND.message));
+ }
+}
diff --git a/src/main/java/baseball/model/GameStatus.java b/src/main/java/baseball/model/GameStatus.java
new file mode 100644
index 0000000..056b21d
--- /dev/null
+++ b/src/main/java/baseball/model/GameStatus.java
@@ -0,0 +1,11 @@
+package baseball.model;
+
+public enum GameStatus {
+ GAME_RUNNING,
+ GAME_TERMINATE,
+ ;
+
+ public boolean isGameNotTerminated() {
+ return this != GAME_TERMINATE;
+ }
+}
diff --git a/src/main/java/baseball/model/Referee.java b/src/main/java/baseball/model/Referee.java
new file mode 100644
index 0000000..318f02b
--- /dev/null
+++ b/src/main/java/baseball/model/Referee.java
@@ -0,0 +1,55 @@
+package baseball.model;
+
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static baseball.utils.BaseballConstants.BASEBALL_SIZE;
+
+public class Referee {
+ private Referee() {
+ }
+
+ public static Result judge(
+ final List computerBaseballs,
+ final List userBaseballs
+ ) {
+ final int strikeCount = calculateStrikeCount(computerBaseballs, userBaseballs);
+ final int ballCount = calculateBallCount(computerBaseballs, userBaseballs);
+
+ return new Result(strikeCount, ballCount);
+ }
+
+ private static int calculateStrikeCount(
+ final List computerBaseballs,
+ final List userBaseballs
+ ) {
+ return (int) IntStream.range(0, BASEBALL_SIZE)
+ .filter(index -> isExactlyMatchAtIndex(computerBaseballs, userBaseballs, index))
+ .count();
+ }
+
+ private static boolean isExactlyMatchAtIndex(
+ final List computerBaseballs,
+ final List userBaseballs,
+ final int index
+ ) {
+ return computerBaseballs.get(index).equals(userBaseballs.get(index));
+ }
+
+ private static int calculateBallCount(
+ final List computerBaseballs,
+ final List userBaseballs
+ ) {
+ return getContainedElementsCount(computerBaseballs, userBaseballs)
+ - calculateStrikeCount(computerBaseballs, userBaseballs);
+ }
+
+ private static int getContainedElementsCount(
+ final List computerBaseballs,
+ final List userBaseballs
+ ) {
+ return (int) userBaseballs.stream()
+ .filter(computerBaseballs::contains)
+ .count();
+ }
+}
diff --git a/src/main/java/baseball/model/Result.java b/src/main/java/baseball/model/Result.java
new file mode 100644
index 0000000..f0ba3ff
--- /dev/null
+++ b/src/main/java/baseball/model/Result.java
@@ -0,0 +1,25 @@
+package baseball.model;
+
+import static baseball.utils.BaseballConstants.BASEBALL_SIZE;
+
+public class Result {
+ private final int strikeCount;
+ private final int ballCount;
+
+ public Result(final int strikeCount, final int ballCount) {
+ this.strikeCount = strikeCount;
+ this.ballCount = ballCount;
+ }
+
+ public int getStrikeCount() {
+ return strikeCount;
+ }
+
+ public int getBallCount() {
+ return ballCount;
+ }
+
+ public boolean isGameClear() {
+ return strikeCount == BASEBALL_SIZE;
+ }
+}
diff --git a/src/main/java/baseball/model/User.java b/src/main/java/baseball/model/User.java
new file mode 100644
index 0000000..b6d8379
--- /dev/null
+++ b/src/main/java/baseball/model/User.java
@@ -0,0 +1,15 @@
+package baseball.model;
+
+import java.util.List;
+
+public class User {
+ private final Baseballs baseballs;
+
+ public User(final List baseballs) {
+ this.baseballs = new Baseballs(baseballs);
+ }
+
+ public List getBaseballs() {
+ return baseballs.getBaseballs();
+ }
+}
diff --git a/src/main/java/baseball/utils/BaseballConstants.java b/src/main/java/baseball/utils/BaseballConstants.java
new file mode 100644
index 0000000..be546a8
--- /dev/null
+++ b/src/main/java/baseball/utils/BaseballConstants.java
@@ -0,0 +1,7 @@
+package baseball.utils;
+
+public interface BaseballConstants {
+ int MIN_BASEBALL = 1;
+ int MAX_BASEBALL = 9;
+ int BASEBALL_SIZE = 3;
+}
diff --git a/src/main/java/baseball/utils/ExceptionConstants.java b/src/main/java/baseball/utils/ExceptionConstants.java
new file mode 100644
index 0000000..cc4985d
--- /dev/null
+++ b/src/main/java/baseball/utils/ExceptionConstants.java
@@ -0,0 +1,39 @@
+package baseball.utils;
+
+public interface ExceptionConstants {
+ enum BaseballException {
+ BASEBALL_IS_NOT_IN_RANGE("숫자는 1..9 범위만 허용합니다."),
+ BASEBALL_SIZE_IS_NOT_FULFILL("숫자 야구 게임을 위해서 숫자 3개가 필요합니다."),
+ BASEBALL_MUST_BE_UNIQUE("중복된 숫자는 허용하지 않습니다."),
+ ;
+
+ public final String message;
+
+ BaseballException(final String message) {
+ this.message = message;
+ }
+ }
+
+ enum GameProcessCommandException {
+ INVALID_COMMAND("재시작[1] / 종료[2] 중 하나를 입력해주세요."),
+ ;
+
+ public final String message;
+
+ GameProcessCommandException(final String message) {
+ this.message = message;
+ }
+ }
+
+ enum InputException {
+ INPUT_MUST_BE_NUMERIC("숫자를 입력해주세요."),
+ INPUT_MUST_NOT_CONTAINS_SPACE("공백없이 숫자를 입력해주세요."),
+ ;
+
+ public final String message;
+
+ InputException(final String message) {
+ this.message = message;
+ }
+ }
+}
diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java
new file mode 100644
index 0000000..388b084
--- /dev/null
+++ b/src/main/java/baseball/view/InputView.java
@@ -0,0 +1,55 @@
+package baseball.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static baseball.utils.ExceptionConstants.InputException.INPUT_MUST_BE_NUMERIC;
+import static baseball.utils.ExceptionConstants.InputException.INPUT_MUST_NOT_CONTAINS_SPACE;
+
+public class InputView {
+ public static List readUserBaseballs() {
+ System.out.print("숫자를 입력해주세요 : ");
+
+ String userInput = Console.readLine();
+ validateInputHasSpace(userInput);
+ validateInputIsNumeric(userInput);
+
+ return convertUserInputToIntegerList(userInput);
+ }
+
+ private static void validateInputHasSpace(final String userInput) {
+ if (hasSpace(userInput)) {
+ throw new IllegalArgumentException(INPUT_MUST_NOT_CONTAINS_SPACE.message);
+ }
+ }
+
+ private static boolean hasSpace(String userInput) {
+ return userInput.chars()
+ .anyMatch(Character::isWhitespace);
+ }
+
+ private static void validateInputIsNumeric(final String userInput) {
+ try {
+ Integer.parseInt(userInput);
+ } catch (NumberFormatException exception) {
+ throw new IllegalArgumentException(INPUT_MUST_BE_NUMERIC.message);
+ }
+ }
+
+ private static List convertUserInputToIntegerList(final String userInput) {
+ return Arrays.stream(userInput.split(""))
+ .map(Integer::parseInt)
+ .collect(Collectors.toList());
+ }
+
+ public static int readUserRestartCommand() {
+ String userInput = Console.readLine();
+ validateInputHasSpace(userInput);
+ validateInputIsNumeric(userInput);
+
+ return Integer.parseInt(userInput);
+ }
+}
diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java
new file mode 100644
index 0000000..22721d3
--- /dev/null
+++ b/src/main/java/baseball/view/OutputView.java
@@ -0,0 +1,59 @@
+package baseball.view;
+
+import baseball.model.Result;
+
+import static baseball.utils.BaseballConstants.BASEBALL_SIZE;
+
+public class OutputView {
+ private static final String ENTER = "\n";
+ private static final String SEPARATOR = " ";
+
+ private static final String NOTHING = "낫싱";
+ private static final String STRIKE = "스트라이크";
+ private static final String BALL = "볼";
+
+ public static void printGameStart() {
+ System.out.println("숫자 야구 게임을 시작합니다.");
+ }
+
+ public static void printGameResult(final Result result) {
+ System.out.println(refineGameResult(result));
+ }
+
+ private static String refineGameResult(final Result result) {
+ if (!hasStrikeCount(result) && !hasBallCount(result)) {
+ return NOTHING;
+ }
+
+ StringBuilder resultFormat = new StringBuilder();
+
+ if (hasBallCount(result)) {
+ resultFormat.append(result.getBallCount())
+ .append(BALL)
+ .append(SEPARATOR);
+ }
+
+ if (hasStrikeCount(result)) {
+ resultFormat.append(result.getStrikeCount())
+ .append(STRIKE);
+ }
+
+ return resultFormat.toString();
+ }
+
+ private static boolean hasBallCount(final Result result) {
+ return result.getBallCount() > 0;
+ }
+
+ private static boolean hasStrikeCount(final Result result) {
+ return result.getStrikeCount() > 0;
+ }
+
+ public static void printGameClear() {
+ System.out.printf(
+ "%d개의 숫자를 모두 맞히셨습니다! 게임 종료" + ENTER
+ + "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." + ENTER,
+ BASEBALL_SIZE
+ );
+ }
+}
diff --git a/src/test/java/baseball/ApplicationTest.java b/src/test/java/baseball/ApplicationTest.java
index 3fa29fa..dd218f6 100644
--- a/src/test/java/baseball/ApplicationTest.java
+++ b/src/test/java/baseball/ApplicationTest.java
@@ -13,13 +13,88 @@ class ApplicationTest extends NsTest {
void 게임종료_후_재시작() {
assertRandomNumberInRangeTest(
() -> {
- run("246", "135", "1", "597", "589", "2");
- assertThat(output()).contains("낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료");
+ run(
+ "246",
+ "135",
+ "1",
+ "597",
+ "589",
+ "2"
+ );
+ assertThat(output())
+ .contains(
+ "낫싱",
+ "3스트라이크",
+ "1볼 1스트라이크",
+ "3스트라이크",
+ "게임 종료"
+ );
},
1, 3, 5, 5, 8, 9
);
}
+ @Test
+ void 게임종료_후_재시작2() {
+ assertRandomNumberInRangeTest(
+ () -> {
+ run(
+ "124",
+ "154",
+ "127",
+ "123",
+ "2"
+ );
+ assertThat(output())
+ .contains(
+ "2스트라이크",
+ "1스트라이크",
+ "2스트라이크",
+ "3스트라이크",
+ "게임 종료"
+ );
+ },
+ 1, 2, 3
+ );
+ }
+
+ @Test
+ void 게임종료_후_재시작3() {
+ assertRandomNumberInRangeTest(
+ () -> {
+ run(
+ "123",
+ "128",
+ "813",
+ "782",
+ "287",
+ "987",
+ "1",
+ "316",
+ "312",
+ "213",
+ "123",
+ "2"
+ );
+ assertThat(output())
+ .contains(
+ "낫싱",
+ "1볼",
+ "1볼",
+ "1볼 1스트라이크",
+ "2스트라이크",
+ "3스트라이크",
+ "2볼",
+ "3볼",
+ "2볼 1스트라이크",
+ "3스트라이크",
+ "게임 종료"
+ );
+ },
+ 9, 8, 7, 1, 2, 3
+ );
+ }
+
@Test
void 예외_테스트() {
assertSimpleTest(() ->
diff --git a/src/test/java/baseball/model/BaseballsTest.java b/src/test/java/baseball/model/BaseballsTest.java
new file mode 100644
index 0000000..8282ed1
--- /dev/null
+++ b/src/test/java/baseball/model/BaseballsTest.java
@@ -0,0 +1,71 @@
+package baseball.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static baseball.utils.ExceptionConstants.BaseballException.*;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+class BaseballsTest {
+ @ParameterizedTest
+ @MethodSource("invalidRange")
+ @DisplayName("1..9 범위가 아닌 숫자가 존재하면 Baseballs를 생성할 수 없다")
+ void throwExceptionByBaseballIsNotInRange(List baseballs) {
+ assertThatThrownBy(() -> new Baseballs(baseballs))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(BASEBALL_IS_NOT_IN_RANGE.message);
+ }
+
+ private static Stream invalidRange() {
+ return Stream.of(
+ Arguments.of(List.of(0, 1, 9)),
+ Arguments.of(List.of(1, 9, 10))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidSize")
+ @DisplayName("숫자가 3개 미만이면 Baseballs를 생성할 수 없다")
+ void throwExceptionByBaseballSizeNotFulfill(List baseballs) {
+ assertThatThrownBy(() -> new Baseballs(baseballs))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(BASEBALL_SIZE_IS_NOT_FULFILL.message);
+ }
+
+ private static Stream invalidSize() {
+ return Stream.of(
+ Arguments.of(List.of()),
+ Arguments.of(List.of(1)),
+ Arguments.of(List.of(1, 2))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("duplicateNumber")
+ @DisplayName("중복된 숫자가 존재하면 Baseballs를 생성할 수 없다")
+ void throwExceptionByBaseballIsNotUnique(List baseballs) {
+ assertThatThrownBy(() -> new Baseballs(baseballs))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(BASEBALL_MUST_BE_UNIQUE.message);
+ }
+
+ private static Stream duplicateNumber() {
+ return Stream.of(
+ Arguments.of(List.of(1, 2, 2)),
+ Arguments.of(List.of(3, 3, 3))
+ );
+ }
+
+ @Test
+ @DisplayName("Baseballs를 생성한다")
+ void construct() {
+ assertDoesNotThrow(() -> new Baseballs(List.of(1, 2, 3)));
+ }
+}
diff --git a/src/test/java/baseball/model/ComputerTest.java b/src/test/java/baseball/model/ComputerTest.java
new file mode 100644
index 0000000..ea771bb
--- /dev/null
+++ b/src/test/java/baseball/model/ComputerTest.java
@@ -0,0 +1,27 @@
+package baseball.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+class ComputerTest {
+ @Test
+ @DisplayName("Baseballs 랜덤 제공을 통해서 Computer를 생성한다")
+ void construct() {
+ // when
+ final Computer computer = new Computer();
+
+ // then
+ assertAll(
+ () -> assertThat(computer.getBaseballs()).hasSize(3), // size
+ () -> assertThat(
+ computer.getBaseballs()
+ .stream()
+ .distinct()
+ .count()
+ ).isEqualTo(3) // has duplicate
+ );
+ }
+}
diff --git a/src/test/java/baseball/model/GameProcessDeciderTest.java b/src/test/java/baseball/model/GameProcessDeciderTest.java
new file mode 100644
index 0000000..1335be9
--- /dev/null
+++ b/src/test/java/baseball/model/GameProcessDeciderTest.java
@@ -0,0 +1,40 @@
+package baseball.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static baseball.model.GameProcessDecider.GAME_END;
+import static baseball.model.GameProcessDecider.GAME_RESTART;
+import static baseball.utils.ExceptionConstants.GameProcessCommandException.INVALID_COMMAND;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class GameProcessDeciderTest {
+ @Test
+ @DisplayName("재시작[1], 종료[2] 이외의 Command를 입력하면 예외가 발생한다")
+ void throwExceptionByInvalidCommand() {
+ assertThatThrownBy(() -> GameProcessDecider.getDecider(3))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(INVALID_COMMAND.message);
+ }
+
+ @Test
+ @DisplayName("게임 재시작[1] Decider를 선택한다")
+ void selectGameRestart() {
+ // when
+ GameProcessDecider decider = GameProcessDecider.getDecider(1);
+
+ // then
+ assertThat(decider).isEqualTo(GAME_RESTART);
+ }
+
+ @Test
+ @DisplayName("게임 종료[2] Decider를 선택한다")
+ void selectGameEnd() {
+ // when
+ GameProcessDecider decider = GameProcessDecider.getDecider(2);
+
+ // then
+ assertThat(decider).isEqualTo(GAME_END);
+ }
+}
diff --git a/src/test/java/baseball/model/GameStatusTest.java b/src/test/java/baseball/model/GameStatusTest.java
new file mode 100644
index 0000000..7bc593e
--- /dev/null
+++ b/src/test/java/baseball/model/GameStatusTest.java
@@ -0,0 +1,20 @@
+package baseball.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static baseball.model.GameStatus.GAME_RUNNING;
+import static baseball.model.GameStatus.GAME_TERMINATE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+class GameStatusTest {
+ @Test
+ @DisplayName("GameStatus가 TERMINATED가 아닌지 확인한다")
+ void isGameNotTerminated() {
+ assertAll(
+ () -> assertThat(GAME_RUNNING.isGameNotTerminated()).isTrue(),
+ () -> assertThat(GAME_TERMINATE.isGameNotTerminated()).isFalse()
+ );
+ }
+}
diff --git a/src/test/java/baseball/model/RefereeTest.java b/src/test/java/baseball/model/RefereeTest.java
new file mode 100644
index 0000000..49ac51c
--- /dev/null
+++ b/src/test/java/baseball/model/RefereeTest.java
@@ -0,0 +1,112 @@
+package baseball.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+class RefereeTest {
+ @ParameterizedTest
+ @MethodSource("dummyBaseballs")
+ @DisplayName("Referee가 Computer & User 간 숫자 야구 게임 결과(Result)를 도출한다")
+ void judge(
+ List computerBaseballs,
+ List userBaseballs,
+ int strikeCount,
+ int ballCount
+ ) {
+ // when
+ Result result = Referee.judge(computerBaseballs, userBaseballs);
+
+ // then
+ assertAll(
+ () -> assertThat(result.getStrikeCount()).isEqualTo(strikeCount),
+ () -> assertThat(result.getBallCount()).isEqualTo(ballCount)
+ );
+ }
+
+ private static Stream dummyBaseballs() {
+ return Stream.of(
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(4, 5, 6),
+ 0,
+ 0
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(4, 5, 1),
+ 0,
+ 1
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(2, 1, 5),
+ 0,
+ 2
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(3, 1, 2),
+ 0,
+ 3
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(1, 4, 5),
+ 1,
+ 0
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(1, 2, 5),
+ 2,
+ 0
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(1, 2, 3),
+ 3,
+ 0
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(1, 3, 5),
+ 1,
+ 1
+ ),
+ Arguments.of(
+ List.of(1, 2, 3),
+ List.of(1, 3, 2),
+ 1,
+ 2
+ )
+ );
+ }
+
+ @Test
+ @DisplayName("Game Clear(All Strike) 여부를 확인한다")
+ void isGameClear() {
+ // given
+ final List computerBaseballs = List.of(1, 2, 3);
+ final List userBaseballs1 = List.of(1, 2, 3);
+ final List userBaseballs2 = List.of(1, 2, 4);
+
+ // when
+ Result result1 = Referee.judge(computerBaseballs, userBaseballs1);
+ Result result2 = Referee.judge(computerBaseballs, userBaseballs2);
+
+ // then
+ assertAll(
+ () -> assertThat(result1.isGameClear()).isTrue(),
+ () -> assertThat(result2.isGameClear()).isFalse()
+ );
+ }
+}
diff --git a/src/test/java/baseball/model/UserTest.java b/src/test/java/baseball/model/UserTest.java
new file mode 100644
index 0000000..6e85b41
--- /dev/null
+++ b/src/test/java/baseball/model/UserTest.java
@@ -0,0 +1,61 @@
+package baseball.model;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static baseball.utils.ExceptionConstants.BaseballException.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class UserTest {
+ @Test
+ @DisplayName("입력한 원소중에 1..9 범위가 아닌 원소가 있으면 User는 Baseballs를 가질 수 없다")
+ void throwExceptionByBaseballIsNotInRange() {
+ // given
+ final List baseballs = List.of(1, 9, 10);
+
+ // when - then
+ assertThatThrownBy(() -> new User(baseballs))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(BASEBALL_IS_NOT_IN_RANGE.message);
+ }
+
+ @Test
+ @DisplayName("입력한 원소의 크기가 3이 아니면 User는 Baseballs를 가질 수 없다")
+ void throwExceptionByBaseballSizeNotFulfill() {
+ // given
+ final List baseballs = List.of(1, 2);
+
+ // when - then
+ assertThatThrownBy(() -> new User(baseballs))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(BASEBALL_SIZE_IS_NOT_FULFILL.message);
+ }
+
+ @Test
+ @DisplayName("입력한 원소중에 중복된 원소가 존재한다면 User는 Baseballs를 가질 수 없다")
+ void throwExceptionByBaseballIsNotUnique() {
+ // given
+ final List baseballs = List.of(1, 2, 2);
+
+ // when - then
+ assertThatThrownBy(() -> new User(baseballs))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(BASEBALL_MUST_BE_UNIQUE.message);
+ }
+
+ @Test
+ @DisplayName("User를 생성한다")
+ void construct() {
+ // given
+ final List baseballs = List.of(1, 2, 3);
+
+ // when
+ final User user = new User(baseballs);
+
+ // then
+ assertThat(user.getBaseballs()).containsExactlyElementsOf(baseballs);
+ }
+}