diff --git a/README.md b/README.md
index 8d7e8aee..29048985 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,76 @@
-# java-baseball-precourse
\ No newline at end of file
+# java-baseball-precourse
+
+
+# ⚾ 숫자 야구 게임
+
+## 🚀 프로젝트 개요
+1부터 9까지의 서로 다른 수로 이루어진 3자리의 수를 맞추는 콘솔 게임입니다.
+
+## 🎯 기능 목록
+
+### 1. 게임 시작 및 초기화
+- [ ] **컴퓨터의 수 생성 기능**
+ - 1에서 9까지의 서로 다른 임의의 수 3개를 선택한다.
+
+### 2. 사용자 입력
+- [ ] **숫자 입력 요청 기능**
+ - "숫자를 입력해주세요 : " 문구를 출력한다.
+ - 사용자의 입력을 받는다.
+- [ ] **재시작/종료 입력 요청 기능**
+ - 게임 종료 후 "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." 문구를 출력하고 입력을 받는다.
+
+### 3. 입력 유효성 검사 (예외 처리)
+- [ ] **사용자 입력 값 검증**
+ - 사용자가 입력한 값이 유효하지 않을 경우 `IllegalArgumentException`을 발생시키고 `[ERROR]`로 시작하는 에러 메시지를 출력한다.
+ - 에러 발생 후 게임을 종료하지 않고 다시 입력을 받을 수 있도록 처리한다.
+ - **검증 조건:**
+ - [ ] 숫자가 아닌 값이 포함되어 있는가?
+ - [ ] 3자리가 아닌가?
+ - [ ] 1~9 사이의 숫자가 아닌가? (0 포함 여부 확인)
+ - [ ] 중복된 숫자가 있는가?
+
+### 4. 게임 점수 계산 로직
+- [ ] **스트라이크/볼 판별 기능**
+ - 같은 수가 같은 자리에 있으면: **스트라이크**
+ - 같은 수가 다른 자리에 있으면: **볼**
+ - 같은 수가 전혀 없으면: **낫싱**
+
+### 5. 결과 출력
+- [ ] **라운드 결과 출력 기능**
+ - 계산된 스트라이크와 볼의 개수를 출력한다.
+ - 예시: `1볼`, `1스트라이크 1볼`, `3스트라이크`, `낫싱`
+- [ ] **게임 종료 문구 출력 기능**
+ - 3스트라이크일 경우 "3개의 숫자를 모두 맞히셨습니다! 게임 끝"을 출력한다.
+
+### 6. 게임 진행 루프
+- [ ] **게임 흐름 제어**
+ - 3스트라이크가 될 때까지 [입력 -> 계산 -> 출력] 과정을 반복한다.
+ - 게임 종료 후 재시작(1) 선택 시 게임을 처음부터 다시 시작한다.
+ - 종료(2) 선택 시 프로그램을 완전히 종료한다.
+
+---
+
+## 🔍 프로그래밍 요구사항1 - 제약사항
+1. 자바 코드 컨벤션을 지키면서 프로그래밍한다.
+•https://naver.github.io/hackday-conventions-java/
+
+2. indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
+• 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
+• 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
+
+3. 자바 8에 추가된 stream api를 사용하지 않고 구현해야 한다. 단, 람다는 사용 가능하다.
+4. else 예약어를 쓰지 않는다.
+•힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
+•else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도
+허용하지 않는다.
+5. 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
+•함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
+
+---
+
+## 🔍 프로그래밍 요구사항2 - 단위 테스트
+1. 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외
+•핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
+•힌트는 MVC 패턴 기반으로 구현한 후 View, Controller를 제외한 Model에 대한 단위 테스트를 추가하는 것에 집중한다.
+2. JUnit5와 AssertJ 사용법에 익숙하지 않은 개발자는 첨부한 "학습테스트를 통해 JUnit 학습하기.pdf" 문서를 참고해
+사용법을 학습한 후 JUnit5 기반 단위 테스트를 구현한다.
\ No newline at end of file
diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java
new file mode 100644
index 00000000..ca1ac951
--- /dev/null
+++ b/src/main/java/baseball/Application.java
@@ -0,0 +1,11 @@
+package baseball;
+
+import baseball.controller.BaseballController;
+
+public class Application {
+ public static void main(String[] args) {
+
+ BaseballController baseballController = new BaseballController();
+ baseballController.run();
+ }
+}
diff --git a/src/main/java/baseball/controller/BaseballController.java b/src/main/java/baseball/controller/BaseballController.java
new file mode 100644
index 00000000..7a3de394
--- /dev/null
+++ b/src/main/java/baseball/controller/BaseballController.java
@@ -0,0 +1,78 @@
+package baseball.controller;
+
+import baseball.domain.Computer;
+import baseball.domain.Referee;
+import baseball.domain.Result;
+import baseball.util.InputParser;
+import baseball.util.ValidationResult;
+import baseball.util.Validator;
+import baseball.view.InputView;
+import baseball.view.OutputView;
+
+import java.util.List;
+
+public class BaseballController {
+
+ private final Referee referee;
+ private final InputView inputView;
+ private final OutputView outputView;
+ private Computer computer;
+
+ public BaseballController() {
+ this.referee = new Referee();
+ this.inputView = new InputView();
+ this.outputView = new OutputView();
+ }
+
+ // 프로그램 실행
+ public void run() {
+ do {
+ play();
+ } while(isRestart());
+ }
+
+ // 게임 시작
+ private void play() {
+ computer = new Computer(); // 상대(컴퓨터)가 랜덤 숫자 생성
+
+ while (true) {
+ String input = getValidInput(); // 입력 받기
+ List inputNumbers = InputParser.parseToNumbers(input); // 입력을 List로 변환
+
+ Result result = referee.getResult(computer.getNums(), inputNumbers); // 심판에게 결과 받음
+ outputView.printResult(result); // 받은 결과 출력
+
+ // 3스트라이크 시 게임 승리
+ if (result.isWin()) {
+ outputView.printFinish();
+ break;
+ }
+ }
+ }
+
+ // 유효한 입력 받기
+ private String getValidInput() {
+ String input;
+ while (true) {
+ input = inputView.inputNumber();
+ ValidationResult validationResult = Validator.validateGameNumber(input);
+ if (validationResult.isValid()) {
+ return input;
+ }
+ outputView.printError(validationResult.getMessage());
+ }
+ }
+
+ // 재시작 입력 받기
+ private boolean isRestart() {
+ String input;
+ while (true) {
+ input = inputView.inputRestartNumber();
+ ValidationResult validationResult = Validator.validateRestartNumber(input);
+ if (validationResult.isValid()) {
+ return input.equals("1");
+ }
+ outputView.printError(validationResult.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/baseball/domain/Computer.java b/src/main/java/baseball/domain/Computer.java
new file mode 100644
index 00000000..719f2964
--- /dev/null
+++ b/src/main/java/baseball/domain/Computer.java
@@ -0,0 +1,30 @@
+package baseball.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class Computer {
+
+ public List getNums() {
+ return nums;
+ }
+
+ private final List nums;
+ private final Random random = new Random();
+
+ public Computer() {
+ this.nums = new ArrayList<>();
+ generateNumbers();
+ }
+
+ private void generateNumbers() {
+ while(nums.size() < 3) {
+ Integer randomNumber = random.nextInt(9) + 1;
+
+ if (!nums.contains(randomNumber)) {
+ nums.add(randomNumber);
+ }
+ }
+ }
+}
diff --git a/src/main/java/baseball/domain/Referee.java b/src/main/java/baseball/domain/Referee.java
new file mode 100644
index 00000000..e0f6c0b7
--- /dev/null
+++ b/src/main/java/baseball/domain/Referee.java
@@ -0,0 +1,36 @@
+package baseball.domain;
+
+import java.util.List;
+
+public class Referee {
+
+ // 해당 라운드 결과 산출
+ public Result getResult(List computer, List input) {
+ int strike = countStrike(computer, input);
+ int ball = countBall(computer, input);
+
+ return new Result(strike, ball);
+ }
+
+ // 스트라이크 갯수 세기
+ private int countStrike(List computer, List input) {
+ int strike = 0;
+
+ for (int i = 0; i < 3; i++) {
+ if (computer.get(i).equals(input.get(i))) strike ++;
+ }
+
+ return strike;
+ }
+
+ // 볼 갯수 세기
+ private int countBall(List computer, List input) {
+ int ball = 0;
+
+ for (int i = 0; i < 3; i++) {
+ if(!computer.get(i).equals(input.get(i)) && input.contains(computer.get(i))) ball ++;
+ }
+
+ return ball;
+ }
+}
diff --git a/src/main/java/baseball/domain/Result.java b/src/main/java/baseball/domain/Result.java
new file mode 100644
index 00000000..4cdf00e6
--- /dev/null
+++ b/src/main/java/baseball/domain/Result.java
@@ -0,0 +1,23 @@
+package baseball.domain;
+
+public class Result {
+ private final int strike;
+ private final int ball;
+
+ public Result(int strike, int ball) {
+ this.strike = strike;
+ this.ball = ball;
+ }
+
+ public int getStrike() {
+ return strike;
+ }
+
+ public int getBall() {
+ return ball;
+ }
+
+ public boolean isWin() {
+ return strike == 3 && ball == 0;
+ }
+}
diff --git a/src/main/java/baseball/util/InputParser.java b/src/main/java/baseball/util/InputParser.java
new file mode 100644
index 00000000..53199a0b
--- /dev/null
+++ b/src/main/java/baseball/util/InputParser.java
@@ -0,0 +1,19 @@
+package baseball.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InputParser {
+
+ private InputParser() {}
+
+ public static List parseToNumbers(String input) {
+ List numbers = new ArrayList<>();
+
+ for (char c : input.toCharArray()) {
+ numbers.add(Character.getNumericValue(c));
+ }
+
+ return numbers;
+ }
+}
diff --git a/src/main/java/baseball/util/ValidationResult.java b/src/main/java/baseball/util/ValidationResult.java
new file mode 100644
index 00000000..1fdd107c
--- /dev/null
+++ b/src/main/java/baseball/util/ValidationResult.java
@@ -0,0 +1,27 @@
+package baseball.util;
+
+public class ValidationResult {
+ private final boolean valid;
+ private final String message;
+
+ private ValidationResult(boolean valid, String message) {
+ this.valid = valid;
+ this.message = message;
+ }
+
+ public static ValidationResult ok() {
+ return new ValidationResult(true, "");
+ }
+
+ public static ValidationResult fail(String message) {
+ return new ValidationResult(false, message);
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/baseball/util/Validator.java b/src/main/java/baseball/util/Validator.java
new file mode 100644
index 00000000..c884ec75
--- /dev/null
+++ b/src/main/java/baseball/util/Validator.java
@@ -0,0 +1,62 @@
+package baseball.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class Validator {
+
+ private Validator() {}
+ private static final String ERROR_PREFIX = "[ERROR] ";
+ private static final String INVALID_INPUT = ERROR_PREFIX + "유효하지 않은 입력입니다.";
+ private static final String INVALID_RESTART_INPUT = ERROR_PREFIX + "1, 2 중 입력하세요.";
+
+ // 유효한 입력인지 검증
+ public static ValidationResult validateGameNumber(String input) {
+ if (validateIsNumber(input) && validateLength(input) && validateRange(input) && validateDuplicateNumber(input)) {
+ return ValidationResult.ok();
+ }
+
+ return ValidationResult.fail(INVALID_INPUT);
+ }
+
+ // 유효한 게임 재시작 입력인지 검증
+ public static ValidationResult validateRestartNumber(String input) {
+ if (input.equals("1") || input.equals("2")) {
+ return ValidationResult.ok();
+ }
+
+ return ValidationResult.fail(INVALID_RESTART_INPUT);
+ }
+
+ // 입력받은 숫자가 숫자가 아닌 값이 포함되어 있는지 검증
+ static boolean validateIsNumber(String input) {
+ if (!input.matches("^[0-9]*$")) return false;
+
+ return true;
+ }
+
+ // 입력받은 숫자가 3자리가 맞는지 검증
+ static boolean validateLength(String input) {
+ if (input.length() != 3) return false;
+
+ return true;
+ }
+
+ // 입력받은 숫자에 1~9 사이의 숫자가 존재하는 지 검증 (0을 포함하는 지)
+ static boolean validateRange(String input) {
+ if (input.contains("0")) return false;
+
+ return true;
+ }
+
+ // 입력받은 숫자에 중복된 숫자가 있는지 검증
+ static boolean validateDuplicateNumber(String input) {
+ Set set = new HashSet<>();
+ for (char num : input.toCharArray()) {
+ if(set.contains(num)) return false;
+ set.add(num);
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java
new file mode 100644
index 00000000..5f473e10
--- /dev/null
+++ b/src/main/java/baseball/view/InputView.java
@@ -0,0 +1,25 @@
+package baseball.view;
+
+import java.util.Scanner;
+
+public class InputView {
+ private final Scanner scanner;
+
+ public InputView() {
+ this.scanner = new Scanner(System.in);
+ }
+
+ // 숫자 입력
+ public String inputNumber() {
+ System.out.print("숫자를 입력해주세요 : ");
+
+ return scanner.nextLine().trim();
+ }
+
+ // 재시작을 위한 입력
+ public String inputRestartNumber() {
+ System.out.print("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요 : ");
+
+ return scanner.nextLine().trim();
+ }
+}
diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java
new file mode 100644
index 00000000..0a9baa75
--- /dev/null
+++ b/src/main/java/baseball/view/OutputView.java
@@ -0,0 +1,39 @@
+package baseball.view;
+
+import baseball.domain.Result;
+
+public class OutputView {
+
+ // 결과 출력
+ public void printResult(Result result) {
+ int strike = result.getStrike();
+ int ball = result.getBall();
+
+ if (strike == 0 && ball == 0) {
+ System.out.println("낫싱");
+ return;
+ }
+
+ System.out.println(buildResultString(strike, ball));
+ }
+
+ // 스트라이크와 볼 포함한 결과 String 빌드
+ public String buildResultString(int strike, int ball) {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ if(strike > 0) stringBuilder.append(strike).append("스트라이크 ");
+ if(ball > 0) stringBuilder.append(ball).append("볼");
+
+ return stringBuilder.toString().trim();
+ }
+
+ // 게임 종료 멘트 출력
+ public void printFinish() {
+ System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝");
+ }
+
+ // 에러 멘트 출력
+ public void printError(String message) {
+ System.out.println(message);
+ }
+}
diff --git a/src/test/java/baseball/domain/ComputerTest.java b/src/test/java/baseball/domain/ComputerTest.java
new file mode 100644
index 00000000..3b4c693a
--- /dev/null
+++ b/src/test/java/baseball/domain/ComputerTest.java
@@ -0,0 +1,64 @@
+package baseball.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ComputerTest {
+
+ @Test
+ @DisplayName("컴퓨터가 생성한 숫자의 개수는 정확히 3개여야 한다.")
+ void generateNumberSizeTest() {
+ // given
+ Computer computer = new Computer();
+
+ // when
+ List nums = computer.getNums();
+
+ // then
+ assertThat(nums).hasSize(3);
+ }
+
+ @Test
+ @DisplayName("컴퓨터가 생성한 숫자는 모두 1부터 9 사이의 수여야 한다.")
+ void generateNumberRangeTest() {
+ // given
+ Computer computer = new Computer();
+
+ // when
+ List nums = computer.getNums();
+
+ // then
+ assertThat(nums).allMatch(number -> number >= 1 && number <= 9);
+ }
+
+ @Test
+ @DisplayName("컴퓨터가 생성한 3개의 숫자는 서로 중복되지 않아야 한다.")
+ void generateNumberDuplicateTest() {
+ // given
+ Computer computer = new Computer();
+
+ // when
+ List nums = computer.getNums();
+
+ // then
+ assertThat(nums).doesNotHaveDuplicates();
+ }
+
+ // 여러 번 반복해서 안정성 검증위한 테스트
+ @RepeatedTest(100)
+ @DisplayName("반복 테스트: 100번 실행해도 항상 유효한 숫자가 생성되어야 한다.")
+ void generateNumberConsistencyTest() {
+ Computer computer = new Computer();
+ List nums = computer.getNums();
+
+ assertThat(nums)
+ .hasSize(3)
+ .doesNotHaveDuplicates()
+ .allMatch(number -> number >= 1 && number <= 9);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/baseball/domain/RefereeTest.java b/src/test/java/baseball/domain/RefereeTest.java
new file mode 100644
index 00000000..bb28b311
--- /dev/null
+++ b/src/test/java/baseball/domain/RefereeTest.java
@@ -0,0 +1,95 @@
+package baseball.domain;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RefereeTest {
+
+ private Referee referee;
+
+ @BeforeEach
+ void setUp() {
+ referee = new Referee();
+ }
+
+ @Test
+ @DisplayName("3스트라이크: 모든 숫자와 위치가 일치하는 경우")
+ void getResultTest3strike() {
+ // given
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(1, 2, 3);
+
+ // when
+ Result result = referee.getResult(computer, player);
+
+ // then
+ assertThat(result.getStrike()).isEqualTo(3);
+ assertThat(result.getBall()).isEqualTo(0);
+ }
+
+ @Test
+ @DisplayName("3볼: 숫자는 모두 같지만 위치가 다른 경우")
+ void getResultTest3ball() {
+ // given
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(2, 3, 1);
+
+ // when
+ Result result = referee.getResult(computer, player);
+
+ // then
+ assertThat(result.getStrike()).isEqualTo(0);
+ assertThat(result.getBall()).isEqualTo(3);
+ }
+
+ @Test
+ @DisplayName("낫싱: 일치하는 숫자가 하나도 없는 경우")
+ void getResultTestNothing() {
+ // given
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(4, 5, 6);
+
+ // when
+ Result result = referee.getResult(computer, player);
+
+ // then
+ assertThat(result.getStrike()).isEqualTo(0);
+ assertThat(result.getBall()).isEqualTo(0);
+ }
+
+ @Test
+ @DisplayName("1스트라이크 1볼: 위치가 같은 수 1개, 위치는 다르지만 포함된 수 1개")
+ void getResultTest1strike1ball() {
+ // given
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(1, 3, 4); // 1(스트라이크), 3(볼), 4(낫싱)
+
+ // when
+ Result result = referee.getResult(computer, player);
+
+ // then
+ assertThat(result.getStrike()).isEqualTo(1);
+ assertThat(result.getBall()).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("2볼: 스트라이크 없이 볼만 2개인 경우")
+ void getResultTest2ball() {
+ // given
+ List computer = Arrays.asList(1, 2, 3);
+ List player = Arrays.asList(2, 1, 6); // 2(볼), 1(볼), 6(낫싱)
+
+ // when
+ Result result = referee.getResult(computer, player);
+
+ // then
+ assertThat(result.getStrike()).isEqualTo(0);
+ assertThat(result.getBall()).isEqualTo(2);
+ }
+}
diff --git a/src/test/java/baseball/domain/ResultTest.java b/src/test/java/baseball/domain/ResultTest.java
new file mode 100644
index 00000000..ebc6577d
--- /dev/null
+++ b/src/test/java/baseball/domain/ResultTest.java
@@ -0,0 +1,37 @@
+package baseball.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ResultTest {
+
+ @Test
+ @DisplayName("strike/ball 값을 그대로 보관한다.")
+ void getterTest() {
+ Result result = new Result(2, 1);
+
+ assertThat(result.getStrike()).isEqualTo(2);
+ assertThat(result.getBall()).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("3스트라이크 0볼이면 승리다.")
+ void isWinTest() {
+ Result result = new Result(3, 0);
+
+ assertThat(result.isWin()).isTrue();
+ }
+
+ @ParameterizedTest
+ @CsvSource({"2,0", "1,1", "0,0"})
+ @DisplayName("승리 조건(3스트라이크 0볼)이 아니면 패배다.")
+ void isNotWinTest(int strike, int ball) {
+ Result result = new Result(strike, ball);
+
+ assertThat(result.isWin()).isFalse();
+ }
+}
diff --git a/src/test/java/baseball/util/InputParserTest.java b/src/test/java/baseball/util/InputParserTest.java
new file mode 100644
index 00000000..c16dd29f
--- /dev/null
+++ b/src/test/java/baseball/util/InputParserTest.java
@@ -0,0 +1,27 @@
+package baseball.util;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class InputParserTest {
+
+ @Test
+ @DisplayName("문자열 입력을 List로 변환한다.")
+ void parseToNumbersTest() {
+ List result = InputParser.parseToNumbers("123");
+
+ assertThat(result).containsExactly(1, 2, 3);
+ }
+
+ @Test
+ @DisplayName("빈 문자열은 빈 리스트로 변환한다.")
+ void parseToNumbersTestWhenEmpty() {
+ List result = InputParser.parseToNumbers("");
+
+ assertThat(result).isEmpty();
+ }
+}
diff --git a/src/test/java/baseball/util/ValidationResultTest.java b/src/test/java/baseball/util/ValidationResultTest.java
new file mode 100644
index 00000000..bc08ac0b
--- /dev/null
+++ b/src/test/java/baseball/util/ValidationResultTest.java
@@ -0,0 +1,27 @@
+package baseball.util;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ValidationResultTest {
+
+ @Test
+ @DisplayName("ok()는 valid=true, message=''를 반환한다.")
+ void isOkTeest() {
+ ValidationResult result = ValidationResult.ok();
+
+ assertThat(result.isValid()).isTrue();
+ assertThat(result.getMessage()).isEmpty();
+ }
+
+ @Test
+ @DisplayName("fail(message)는 valid=false, message를 보관한다.")
+ void isFailTest() {
+ ValidationResult result = ValidationResult.fail("[ERROR] 유효하지 않은 입력입니다.");
+
+ assertThat(result.isValid()).isFalse();
+ assertThat(result.getMessage()).isEqualTo("[ERROR] 유효하지 않은 입력입니다.");
+ }
+}
diff --git a/src/test/java/baseball/util/ValidatorTest.java b/src/test/java/baseball/util/ValidatorTest.java
new file mode 100644
index 00000000..d4a4e112
--- /dev/null
+++ b/src/test/java/baseball/util/ValidatorTest.java
@@ -0,0 +1,79 @@
+package baseball.util;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ValidatorTest {
+
+ private static final String INVALID_INPUT = "[ERROR] 유효하지 않은 입력입니다.";
+ private static final String INVALID_RESTART_INPUT = "[ERROR] 1, 2 중 입력하세요.";
+
+ @Test
+ @DisplayName("게임 숫자 검증 성공: 1~9 사이의 서로 다른 3자리 숫자")
+ void validateGameNumberTestSuccess() {
+ assertThat(Validator.validateGameNumber("123").isValid()).isTrue();
+ assertThat(Validator.validateGameNumber("987").isValid()).isTrue();
+ assertThat(Validator.validateGameNumber("519").isValid()).isTrue();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"abc", "1a3", "!!!", " "})
+ @DisplayName("게임 숫자 검증 실패: 숫자가 아닌 값 포함")
+ void validateGameNumberTestFailNotNumber(String input) {
+ ValidationResult result = Validator.validateGameNumber(input);
+
+ assertThat(result.isValid()).isFalse();
+ assertThat(result.getMessage()).isEqualTo(INVALID_INPUT);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"12", "1234", "", "1"})
+ @DisplayName("게임 숫자 검증 실패: 3자리가 아님")
+ void validateGameNumberTestFailLength(String input) {
+ ValidationResult result = Validator.validateGameNumber(input);
+
+ assertThat(result.isValid()).isFalse();
+ assertThat(result.getMessage()).isEqualTo(INVALID_INPUT);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"102", "012", "000"})
+ @DisplayName("게임 숫자 검증 실패: 0 포함")
+ void validateGameNumberTestFailRange(String input) {
+ ValidationResult result = Validator.validateGameNumber(input);
+
+ assertThat(result.isValid()).isFalse();
+ assertThat(result.getMessage()).isEqualTo(INVALID_INPUT);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"112", "121", "222", "999"})
+ @DisplayName("게임 숫자 검증 실패: 중복 숫자 포함")
+ void validateGameNumberTestFailDuplicate(String input) {
+ ValidationResult result = Validator.validateGameNumber(input);
+
+ assertThat(result.isValid()).isFalse();
+ assertThat(result.getMessage()).isEqualTo(INVALID_INPUT);
+ }
+
+ @Test
+ @DisplayName("재시작 숫자 검증 성공: 1 또는 2")
+ void validateRestartNumberTestSuccess() {
+ assertThat(Validator.validateRestartNumber("1").isValid()).isTrue();
+ assertThat(Validator.validateRestartNumber("2").isValid()).isTrue();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"0", "3", "a", "!", "12", ""})
+ @DisplayName("재시작 숫자 검증 실패: 1 또는 2 외의 값")
+ void validateRestartNumberTestFail(String input) {
+ ValidationResult result = Validator.validateRestartNumber(input);
+
+ assertThat(result.isValid()).isFalse();
+ assertThat(result.getMessage()).isEqualTo(INVALID_RESTART_INPUT);
+ }
+}