diff --git a/README.md b/README.md
index 8d7e8aee..b9fdb5a8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,60 @@
-# java-baseball-precourse
\ No newline at end of file
+# java-baseball-precourse
+
+# ⚾ 숫자 야구 게임
+
+## 기능 구현 목록
+
+### 1. 게임 시작 및 초기화
+
+- [ ] **컴퓨터의 수 생성 기능**: 1에서 9까지 서로 다른 임의의 수 3개를 선택한다.
+
+### 2. 사용자 입력
+
+- [ ] **숫자 입력 기능**: 플레이어에게 서로 다른 3자리의 숫자를 입력받는다.
+- [ ] **재시작/종료 입력 기능**: 게임 종료 후 재시작(1) 또는 종료(2)를 구분하는 값을 입력받는다.
+
+### 3. 입력 값 유효성 검사 (예외 처리)
+
+- [ ] **숫자 입력 유효성 검사**: 사용자가 입력한 값이 유효한지 검증한다.
+ - [ ] 3자리 숫자인가?
+ - [ ] 1~9 사이의 숫자로만 이루어져 있는가?
+ - [ ] 중복된 숫자가 없는가?
+- [ ] **재시작 입력 유효성 검사**: 입력 값이 1 또는 2인지 검증한다.
+- [ ] **에러 처리**: 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시키고, `[ERROR]`로 시작하는 에러 메시지를 출력한다.
+ - 에러 발생 후 게임을 종료하지 않고 다시 입력을 받도록 처리한다.
+
+### 4. 게임 로직 (판정)
+
+- [ ] **볼/스트라이크 판정 기능**: 컴퓨터의 수와 플레이어의 수를 비교하여 결과를 계산한다.
+ - 같은 수가 같은 자리에 있으면 스트라이크
+ - 같은 수가 다른 자리에 있으면 볼
+ - 같은 수가 전혀 없으면 낫싱
+
+### 5. 결과 출력
+
+- [ ] **게임 결과 출력 기능**: 판정된 결과를 요구사항에 맞는 형식으로 출력한다.
+ - 예: `1스트라이크 1볼`, `3스트라이크`, `낫싱`
+- [ ] **게임 종료 문구 출력 기능**: 3개의 숫자를 모두 맞혔을 때 종료 문구를 출력한다.
+ - 예: `3개의 숫자를 모두 맞히셨습니다! 게임 끝`
+
+### 6. 게임 흐름 제어
+
+- [ ] **게임 루프 구현**: 정답을 맞출 때까지 입력 -> 판정 -> 출력을 반복한다.
+- [ ] **전체 게임 제어**: 게임 종료 후 사용자의 선택(1, 2)에 따라 게임을 재시작하거나 완전히 종료한다.
+
+---
+
+## 🔍 기능적 요구사항 (참고)
+
+기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
+
+- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
+ - [예] 상대방(컴퓨터)의 수가 425일 때
+ - 123을 제시한 경우 : 1스트라이크
+ - 456을 제시한 경우 : 1스트라이크 1볼
+ - 789를 제시한 경우 : 낫싱
+- 위 숫자 야구게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한
+ 결과를 출력한다.
+- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
+- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
+- 사용자가 잘못된 값을 입력할 경우 `[ERROR]`로 시작하는 에러 메시지를 출력하고 게임을 계속 진행할 수 있어야 한다.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 20a92c9e..75684882 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,6 @@
plugins {
id 'java'
+ id 'checkstyle'
}
group = 'camp.nextstep.edu'
@@ -11,6 +12,14 @@ java {
}
}
+// Checkstyle config
+checkstyle {
+ maxWarnings = 0 // 규칙이 어긋나는 코드가 하나라도 있을 경우 빌드 fail을 내고 싶다면 이 선언을 추가한다.
+ configFile = file("${rootDir}/naver-checkstyle-rules.xml")
+ configProperties = ["suppressionFile": "${rootDir}/naver-checkstyle-suppressions.xml"]
+ toolVersion = "8.24" // checkstyle 버전 8.24 이상 선언
+}
+
repositories {
mavenCentral()
}
@@ -23,3 +32,6 @@ dependencies {
test {
useJUnitPlatform()
}
+
+
+
diff --git a/naver-checkstyle-rules.xml b/naver-checkstyle-rules.xml
new file mode 100644
index 00000000..dafbb4d1
--- /dev/null
+++ b/naver-checkstyle-rules.xml
@@ -0,0 +1,433 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java
new file mode 100644
index 00000000..d1c0ad85
--- /dev/null
+++ b/src/main/java/baseball/Application.java
@@ -0,0 +1,10 @@
+package baseball;
+
+import baseball.controller.GameController;
+
+public class Application {
+ public static void main(String[] args) {
+ GameController gameController = new GameController();
+ 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 00000000..6d856ef0
--- /dev/null
+++ b/src/main/java/baseball/controller/GameController.java
@@ -0,0 +1,67 @@
+package baseball.controller;
+
+import java.util.List;
+
+import baseball.model.Computer;
+import baseball.model.GameResult;
+import baseball.model.Player;
+import baseball.model.Referee;
+import baseball.model.RestartCommand;
+import baseball.view.InputView;
+import baseball.view.OutputView;
+
+public class GameController {
+
+ private final InputView inputView;
+ private final Referee referee;
+ private final Player player;
+ private final Computer computer;
+
+ public GameController() {
+ this.inputView = new InputView();
+ this.referee = new Referee();
+ this.player = new Player();
+ this.computer = new Computer();
+ }
+
+ public void run() {
+ RestartCommand restartCommand;
+ do {
+ playGame();
+ restartCommand = getPlayerRestartCommand();
+ } while (RestartCommand.RESTART.equals(restartCommand));
+ }
+
+ private void playGame() {
+ computer.setRandomNumbers();
+
+ GameResult gameResult;
+ do {
+ getPlayerValidInput();
+ gameResult = referee.compare(computer.getNumbers(), player.getNumbers());
+ OutputView.printGameResult(gameResult);
+ } while (!gameResult.isThreeStrikes());
+ }
+
+ private void getPlayerValidInput() {
+ while (true) {
+ try {
+ List numbers = inputView.readPlayerNumbers();
+ player.setNumbers(numbers);
+ break;
+ } catch (IllegalArgumentException e) {
+ System.out.println("[ERROR] " + e.getMessage());
+ }
+ }
+ }
+
+ private RestartCommand getPlayerRestartCommand() {
+ while (true) {
+ try {
+ return inputView.readRestartCommand();
+ } catch (IllegalArgumentException e) {
+ System.out.println("[ERROR] " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/main/java/baseball/model/Computer.java b/src/main/java/baseball/model/Computer.java
new file mode 100644
index 00000000..e374ebae
--- /dev/null
+++ b/src/main/java/baseball/model/Computer.java
@@ -0,0 +1,37 @@
+package baseball.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+public class Computer {
+ private List numbers;
+
+ public Computer() {
+ this.setRandomNumbers();
+ }
+
+ public void setRandomNumbers() {
+ List generatedNumbers = new ArrayList<>();
+ Random random = new Random();
+
+ while (generatedNumbers.size() < 3) {
+ int randomNumber = random.nextInt(9) + 1;
+
+ if (!generatedNumbers.contains(randomNumber)) {
+ generatedNumbers.add(randomNumber);
+ }
+ }
+
+ numbers = List.copyOf(generatedNumbers);
+ }
+
+ public List getNumbers() {
+ if (numbers == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.unmodifiableList(numbers);
+ }
+}
diff --git a/src/main/java/baseball/model/GameResult.java b/src/main/java/baseball/model/GameResult.java
new file mode 100644
index 00000000..35718c7a
--- /dev/null
+++ b/src/main/java/baseball/model/GameResult.java
@@ -0,0 +1,27 @@
+package baseball.model;
+
+public class GameResult {
+ private int strikes;
+ private int balls;
+
+ public GameResult(int strikes, int balls) {
+ this.strikes = strikes;
+ this.balls = balls;
+ }
+
+ public boolean isThreeStrikes() {
+ return strikes == 3;
+ }
+
+ public boolean isNothing() {
+ return strikes == 0 && balls == 0;
+ }
+
+ public int getStrikes() {
+ return this.strikes;
+ }
+
+ public int getBalls() {
+ return this.balls;
+ }
+}
diff --git a/src/main/java/baseball/model/Player.java b/src/main/java/baseball/model/Player.java
new file mode 100644
index 00000000..d008dece
--- /dev/null
+++ b/src/main/java/baseball/model/Player.java
@@ -0,0 +1,15 @@
+package baseball.model;
+
+import java.util.List;
+
+public class Player {
+ private List numbers;
+
+ public void setNumbers(List numbers) {
+ this.numbers = numbers;
+ }
+
+ public List getNumbers() {
+ return numbers;
+ }
+}
diff --git a/src/main/java/baseball/model/Referee.java b/src/main/java/baseball/model/Referee.java
new file mode 100644
index 00000000..21a6970a
--- /dev/null
+++ b/src/main/java/baseball/model/Referee.java
@@ -0,0 +1,41 @@
+package baseball.model;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Referee {
+ public GameResult compare(List computerNumbers, List playerNumbers) {
+ int strikeCnt = calculateStrikeCnt(computerNumbers, playerNumbers);
+ int strikeAndBallCnt = calculateStrikeAndBallCnt(computerNumbers, playerNumbers);
+ int ballCnt = strikeAndBallCnt - strikeCnt;
+
+ return new GameResult(strikeCnt, ballCnt);
+ }
+
+ private int calculateStrikeCnt(List computerNumbers, List playerNumbers) {
+ int strikeCnt = 0;
+
+ for (int i = 0; i < playerNumbers.size(); ++i) {
+ if (computerNumbers.get(i).equals(playerNumbers.get(i))) {
+ strikeCnt++;
+ }
+ }
+
+ return strikeCnt;
+ }
+
+ private int calculateStrikeAndBallCnt(List computerNumbers, List playerNumbers) {
+ int strikeAndBallCnt = 0;
+
+ Set computerNumberSet = new HashSet<>(computerNumbers);
+
+ for (Integer number : playerNumbers) {
+ if (computerNumberSet.contains(number)) {
+ strikeAndBallCnt++;
+ }
+ }
+
+ return strikeAndBallCnt;
+ }
+}
diff --git a/src/main/java/baseball/model/RestartCommand.java b/src/main/java/baseball/model/RestartCommand.java
new file mode 100644
index 00000000..a558d63f
--- /dev/null
+++ b/src/main/java/baseball/model/RestartCommand.java
@@ -0,0 +1,23 @@
+package baseball.model;
+
+public enum RestartCommand {
+ RESTART("1"),
+ EXIT("2");
+
+ private final String command;
+
+ RestartCommand(String command) {
+ this.command = command;
+ }
+
+ public static RestartCommand of(String input) {
+ if ("1".equals(input)) {
+ return RestartCommand.RESTART;
+ }
+ if ("2".equals(input)) {
+ return RestartCommand.EXIT;
+ }
+
+ throw new IllegalArgumentException("유효하지 않은 실행 명렵입니다. (유효값: 1 or 2)");
+ }
+}
diff --git a/src/main/java/baseball/util/InputValidator.java b/src/main/java/baseball/util/InputValidator.java
new file mode 100644
index 00000000..a1c7d994
--- /dev/null
+++ b/src/main/java/baseball/util/InputValidator.java
@@ -0,0 +1,25 @@
+package baseball.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class InputValidator {
+
+ private static final int NUMBER_LENGTH = 3;
+
+ public void validatePlayerNumbers(String input) {
+ if (input == null || input.length() != NUMBER_LENGTH) {
+ throw new IllegalArgumentException("3자리의 서로 다른 숫자를 입력해야 합니다.");
+ }
+
+ Set uniqueChars = new HashSet<>();
+ for (char c : input.toCharArray()) {
+ if (c < '1' || c > '9') {
+ throw new IllegalArgumentException("1에서 9까지의 숫자로만 입력해야 합니다.");
+ }
+ if (!uniqueChars.add(c)) {
+ throw new IllegalArgumentException("중복되지 않은 숫자를 입력해야 합니다.");
+ }
+ }
+ }
+}
diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java
new file mode 100644
index 00000000..3924c7cb
--- /dev/null
+++ b/src/main/java/baseball/view/InputView.java
@@ -0,0 +1,36 @@
+package baseball.view;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import baseball.model.RestartCommand;
+import baseball.util.InputValidator;
+
+public class InputView {
+
+ private final InputValidator validator;
+ private final Scanner scanner;
+
+ public InputView() {
+ this.scanner = new Scanner(System.in);
+ this.validator = new InputValidator();
+ }
+
+ public List readPlayerNumbers() {
+ System.out.print("숫자를 입력해주세요 : ");
+ String input = scanner.nextLine();
+ validator.validatePlayerNumbers(input);
+
+ List numbers = new ArrayList<>();
+ for (char c : input.toCharArray()) {
+ numbers.add(c - '0');
+ }
+ return numbers;
+ }
+
+ public RestartCommand readRestartCommand() {
+ String input = scanner.nextLine();
+ return RestartCommand.of(input);
+ }
+}
diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java
new file mode 100644
index 00000000..072f4ccd
--- /dev/null
+++ b/src/main/java/baseball/view/OutputView.java
@@ -0,0 +1,50 @@
+package baseball.view;
+
+import baseball.model.GameResult;
+
+public class OutputView {
+ private static final String STRIKE_SUFFIX = "스트라이크";
+ private static final String BALL_SUFFIX = "볼";
+ private static final String NOTHING_MESSAGE = "낫싱";
+
+ public static void printGameResult(GameResult result) {
+ if (isGameFinished(result)) {
+ printEndMessage();
+ return;
+ }
+
+ if (isNothingResult(result)) {
+ System.out.println(NOTHING_MESSAGE);
+ return;
+ }
+
+ printNormalResult(result);
+ }
+
+ private static boolean isGameFinished(GameResult result) {
+ return result.isThreeStrikes();
+ }
+
+ private static boolean isNothingResult(GameResult result) {
+ return result.isNothing();
+ }
+
+ private static void printNormalResult(GameResult result) {
+ printStrikeAndBallMessage(result.getStrikes(), result.getBalls());
+ }
+
+ private static void printStrikeAndBallMessage(int strikes, int balls) {
+ if (strikes > 0) {
+ System.out.print(strikes + STRIKE_SUFFIX + " ");
+ }
+ if (balls > 0) {
+ System.out.print(balls + BALL_SUFFIX);
+ }
+ System.out.println();
+ }
+
+ public static void printEndMessage() {
+ System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝");
+ System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
+ }
+}
diff --git a/src/test/java/baseball/model/ComputerTest.java b/src/test/java/baseball/model/ComputerTest.java
new file mode 100644
index 00000000..1e6008e9
--- /dev/null
+++ b/src/test/java/baseball/model/ComputerTest.java
@@ -0,0 +1,53 @@
+package baseball.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class ComputerTest {
+
+ @Test
+ @DisplayName("컴퓨터는 3개의 난수를 갖는다")
+ void generatesThreeDistinctNumbersBetweenOneAndNine() {
+ Computer computer = new Computer();
+ computer.setRandomNumbers();
+
+ List numbers = computer.getNumbers();
+
+ assertThat(numbers).hasSize(3);
+
+ Set unique = new HashSet<>();
+ for (Integer number : numbers) {
+ assertThat(number).isBetween(1, 9);
+ unique.add(number);
+ }
+ assertThat(unique).hasSize(3);
+ }
+
+ @Test
+ @DisplayName("생성 직후에도 3개의 숫자를 반환한다")
+ void constructorInitializesNumbers() {
+ Computer computer = new Computer();
+
+ List numbers = computer.getNumbers();
+
+ assertThat(numbers).hasSize(3);
+ }
+
+ @Test
+ @DisplayName("반환된 숫자 리스트는 수정할 수 없다")
+ void getNumbersReturnsUnmodifiableList() {
+ Computer computer = new Computer();
+
+ List numbers = computer.getNumbers();
+
+ assertThatThrownBy(() -> numbers.add(1))
+ .isInstanceOf(UnsupportedOperationException.class);
+ }
+}
diff --git a/src/test/java/baseball/model/GameResultTest.java b/src/test/java/baseball/model/GameResultTest.java
new file mode 100644
index 00000000..2e630325
--- /dev/null
+++ b/src/test/java/baseball/model/GameResultTest.java
@@ -0,0 +1,33 @@
+package baseball.model;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class GameResultTest {
+
+ @Test
+ @DisplayName("getter가 스트라이크와 볼 값을 반환한다")
+ void gettersReturnValues() {
+ GameResult result = new GameResult(2, 1);
+
+ assertThat(result.getStrikes()).isEqualTo(2);
+ assertThat(result.getBalls()).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("isThreeStrikes는 스트라이크가 3일 때만 true이다")
+ void isThreeStrikesIsTrueOnlyWhenStrikesAreThree() {
+ assertThat(new GameResult(3, 0).isThreeStrikes()).isTrue();
+ assertThat(new GameResult(2, 1).isThreeStrikes()).isFalse();
+ }
+
+ @Test
+ @DisplayName("isNothing은 스트라이크와 볼이 모두 0일 때만 true이다")
+ void isNothingIsTrueOnlyWhenNoMatches() {
+ assertThat(new GameResult(0, 0).isNothing()).isTrue();
+ assertThat(new GameResult(1, 0).isNothing()).isFalse();
+ assertThat(new GameResult(0, 1).isNothing()).isFalse();
+ }
+}
diff --git a/src/test/java/baseball/model/RefereeTest.java b/src/test/java/baseball/model/RefereeTest.java
new file mode 100644
index 00000000..aad06c18
--- /dev/null
+++ b/src/test/java/baseball/model/RefereeTest.java
@@ -0,0 +1,87 @@
+package baseball.model;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class RefereeTest {
+
+ private final Referee referee = new Referee();
+
+ @Test
+ @DisplayName("스트라이크와 볼을 정확히 계산한다")
+ void compareCalculatesStrikesAndBalls() {
+ List computer = Arrays.asList(4, 2, 5);
+ List player = Arrays.asList(4, 5, 6);
+
+ GameResult result = referee.compare(computer, player);
+
+ assertThat(result.getStrikes()).isEqualTo(1);
+ assertThat(result.getBalls()).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("일치하는 숫자가 없으면 낫싱이다")
+ void compareReturnsNothingWhenNoMatches() {
+ List computer = Arrays.asList(4, 2, 5);
+ List player = Arrays.asList(1, 7, 8);
+
+ GameResult result = referee.compare(computer, player);
+
+ assertThat(result.getStrikes()).isEqualTo(0);
+ assertThat(result.getBalls()).isEqualTo(0);
+ }
+
+ @Test
+ @DisplayName("모두 일치하면 3스트라이크이다")
+ void compareReturnsThreeStrikesWhenAllMatch() {
+ List computer = Arrays.asList(4, 2, 5);
+ List player = Arrays.asList(4, 2, 5);
+
+ GameResult result = referee.compare(computer, player);
+
+ assertThat(result.getStrikes()).isEqualTo(3);
+ assertThat(result.getBalls()).isEqualTo(0);
+ assertThat(result.isThreeStrikes()).isTrue();
+ }
+
+ @Test
+ @DisplayName("볼만 있는 경우를 계산한다")
+ void compareReturnsOnlyBallsWhenAllDigitsMatchDifferentPositions() {
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(3, 1, 2);
+
+ GameResult result = referee.compare(computer, player);
+
+ assertThat(result.getStrikes()).isEqualTo(0);
+ assertThat(result.getBalls()).isEqualTo(3);
+ }
+
+ @Test
+ @DisplayName("스트라이크만 있는 경우를 계산한다")
+ void compareReturnsOnlyStrikesWhenPositionsMatch() {
+ List computer = Arrays.asList(7, 8, 9);
+ List player = Arrays.asList(7, 8, 9);
+
+ GameResult result = referee.compare(computer, player);
+
+ assertThat(result.getStrikes()).isEqualTo(3);
+ assertThat(result.getBalls()).isEqualTo(0);
+ }
+
+ @Test
+ @DisplayName("스트라이크 1개, 볼 0개를 계산한다")
+ void compareReturnsSingleStrikeNoBalls() {
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(1, 4, 5);
+
+ GameResult result = referee.compare(computer, player);
+
+ assertThat(result.getStrikes()).isEqualTo(1);
+ assertThat(result.getBalls()).isEqualTo(0);
+ }
+}
diff --git a/src/test/java/baseball/model/RestartCommandTest.java b/src/test/java/baseball/model/RestartCommandTest.java
new file mode 100644
index 00000000..4d03e1b8
--- /dev/null
+++ b/src/test/java/baseball/model/RestartCommandTest.java
@@ -0,0 +1,35 @@
+package baseball.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class RestartCommandTest {
+
+ @Test
+ @DisplayName("입력값 1은 RESTART를 반환한다")
+ void ofReturnsRestartWhenInputIsOne() {
+ assertThat(RestartCommand.of("1")).isEqualTo(RestartCommand.RESTART);
+ }
+
+ @Test
+ @DisplayName("입력값 2는 EXIT를 반환한다")
+ void ofReturnsExitWhenInputIsTwo() {
+ assertThat(RestartCommand.of("2")).isEqualTo(RestartCommand.EXIT);
+ }
+
+ @Test
+ @DisplayName("유효하지 않은 입력은 예외를 발생시킨다")
+ void ofThrowsWhenInputIsInvalid() {
+ assertThatThrownBy(() -> RestartCommand.of("0"))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> RestartCommand.of("3"))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> RestartCommand.of(""))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> RestartCommand.of(" 1"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+}
diff --git a/src/test/java/baseball/util/InputValidatorTest.java b/src/test/java/baseball/util/InputValidatorTest.java
new file mode 100644
index 00000000..7cb5fe30
--- /dev/null
+++ b/src/test/java/baseball/util/InputValidatorTest.java
@@ -0,0 +1,54 @@
+package baseball.util;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class InputValidatorTest {
+
+ private final InputValidator validator = new InputValidator();
+
+ @Test
+ @DisplayName("정상 입력은 예외를 발생시키지 않는다")
+ void validInputDoesNotThrow() {
+ validator.validatePlayerNumbers("123");
+ validator.validatePlayerNumbers("987");
+ }
+
+ @Test
+ @DisplayName("입력 길이가 3이 아니면 예외를 발생시킨다")
+ void throwsWhenLengthIsNotThree() {
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("12"))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("1234"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ @DisplayName("입력이 null이면 예외를 발생시킨다")
+ void throwsWhenInputIsNull() {
+ assertThatThrownBy(() -> validator.validatePlayerNumbers(null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ @DisplayName("숫자가 아니거나 0이 포함되면 예외를 발생시킨다")
+ void throwsWhenContainsNonDigitOrZero() {
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("10a"))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("023"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ @DisplayName("중복된 숫자가 있으면 예외를 발생시킨다")
+ void throwsWhenHasDuplicateDigits() {
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("112"))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("121"))
+ .isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> validator.validatePlayerNumbers("211"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+}