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); + } +}