From ca559422994a3e211a6895e29d391daf9f25b9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Wed, 28 Jan 2026 16:27:43 +0900 Subject: [PATCH 1/9] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EB=B0=8F=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구현해야 할 기능 목록과 요구사항을 README.md에 정리하여 추가함 - 게임 시작 및 초기화 조건 정의 - 사용자 입력 및 유효성 검사(예외 처리) 항목 상세화 - 게임 점수 계산 로직 및 결과 출력 흐름 정리 - 제약사항과 단위 테스트에 대한 요구사항 추가 --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) 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 From 17bde3f3171c08fb5be81203f74de36eb460abbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Wed, 28 Jan 2026 17:22:40 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EC=BB=B4=ED=93=A8=ED=84=B0?= =?UTF-8?q?=EC=9D=98=20=EB=9E=9C=EB=8D=A4=20=EC=88=AB=EC=9E=90=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 게임 시작 시 컴퓨터가 1에서 9까지의 서로 다른 임의의 수 3개를 선택하는 기능 구현 - Computer 도메인 클래스 생성 - java.util.Random을 사용하여 난수 생성 로직 구현 - 중복되지 않는 3개의 숫자를 List에 저장 --- src/main/java/baseball/Application.java | 9 +++++++ src/main/java/baseball/domain/Computer.java | 26 +++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/baseball/Application.java create mode 100644 src/main/java/baseball/domain/Computer.java diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java new file mode 100644 index 00000000..569d781a --- /dev/null +++ b/src/main/java/baseball/Application.java @@ -0,0 +1,9 @@ +package baseball; + +import baseball.domain.Computer; + +public class Application { + + Computer computer = new Computer(); + +} diff --git a/src/main/java/baseball/domain/Computer.java b/src/main/java/baseball/domain/Computer.java new file mode 100644 index 00000000..f0ed6ded --- /dev/null +++ b/src/main/java/baseball/domain/Computer.java @@ -0,0 +1,26 @@ +package baseball.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class Computer { + + 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); + } + } + } +} From a3a7383e71c140c773bcbf32f45a44888173d7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Wed, 28 Jan 2026 18:16:44 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=9A=94=EC=B2=AD=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=8B=A0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자와 상호작용하여 값을 입력받는 InputView 클래스 구현 - 게임 진행을 위한 숫자 입력 요청 및 수신(inputNumber) - 게임 종료 후 재시작/종료 여부 입력 요청 및 수신(inputRestartNumber) - java.util.Scanner를 사용하여 콘솔 입력 처리 --- src/main/java/baseball/view/InputView.java | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/baseball/view/InputView.java diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 00000000..b082643b --- /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("숫자를 입력해주세요 : "); + + // TODO: 입력 유효성 검증 + return scanner.nextLine().trim(); + } + + public String inputRestartNumber() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요"); + + // TODO: 입력 유효성 검증 + return scanner.nextLine().trim(); + } +} From cf38a276a576901f23105df5434655250f49b2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Sat, 31 Jan 2026 14:48:14 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B0=92=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 입력 값에 대한 검증 로직을 담당하는 Validator 구현 - 숫자 여부, 길이(3자리), 범위(1~9), 중복 여부 검증 - 재시작/종료 입력 값(1, 2) 검증 - 유효하지 않은 입력 시 에러 메시지 발생 --- src/main/java/baseball/Application.java | 8 ++- .../controller/BaseballController.java | 60 +++++++++++++++++++ src/main/java/baseball/util/Validator.java | 58 ++++++++++++++++++ src/main/java/baseball/view/InputView.java | 4 +- 4 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/main/java/baseball/controller/BaseballController.java create mode 100644 src/main/java/baseball/util/Validator.java diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index 569d781a..ca1ac951 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,9 +1,11 @@ package baseball; -import baseball.domain.Computer; +import baseball.controller.BaseballController; public class Application { + public static void main(String[] args) { - Computer computer = new Computer(); - + 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..d75ac4d3 --- /dev/null +++ b/src/main/java/baseball/controller/BaseballController.java @@ -0,0 +1,60 @@ +package baseball.controller; + +import baseball.domain.Computer; +import baseball.util.Validator; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class BaseballController { + + private final InputView inputView; + private final OutputView outputView; + + public BaseballController() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + } + + private static Computer computer; + + // 프로그램 실행 + public void run() { + do { + play(); + } while(isRestart()); + } + + // 게임 시작 + private void play() { + computer = new Computer(); // 상대(컴퓨터)가 랜덤 숫자 생성 + + while (true) { + String input = getValidInput(); // 입력 받기 + + + // TODO : 스트라이크/볼 판별 + } + } + + // 유효한 입력 받기 + private String getValidInput() { + String input; + do { + input = inputView.inputNumber(); + } while(!Validator.isGameNumberPossible(input)); + + return input; + } + + private boolean isRestart() { + String input; + do { + input = inputView.inputRestartNumber(); + } while(!Validator.isRestartNumberPossible(input)); + + System.out.println("true"); + if(input.equals("1")) return true; + + return false; + } +} diff --git a/src/main/java/baseball/util/Validator.java b/src/main/java/baseball/util/Validator.java new file mode 100644 index 00000000..0da69521 --- /dev/null +++ b/src/main/java/baseball/util/Validator.java @@ -0,0 +1,58 @@ +package baseball.util; + +import java.util.HashSet; +import java.util.Set; + +public class Validator { + + private Validator() {} + private static final String ERROR_PREFIX = "[ERROR] "; + + // 유효한 입력인지 검증 + public static boolean isGameNumberPossible(String input) { + if (validateIsNumber(input) && validateLength(input) && validateRange(input) && validateDuplicateNumber(input)) return true; + + System.out.println(ERROR_PREFIX + "유효하지 않은 입력입니다."); + return false; + } + + // 유효한 게임 재시작 입력인지 검증 + public static boolean isRestartNumberPossible(String input) { + if (input.equals("1") || input.equals("2")) return true; + + System.out.println(ERROR_PREFIX + "1, 2 중 입력하세요."); + return false; + } + + // 입력받은 숫자가 숫자가 아닌 값이 포함되어 있는지 검증 + 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 index b082643b..8e9c7004 100644 --- a/src/main/java/baseball/view/InputView.java +++ b/src/main/java/baseball/view/InputView.java @@ -12,14 +12,12 @@ public InputView() { public String inputNumber() { System.out.print("숫자를 입력해주세요 : "); - // TODO: 입력 유효성 검증 return scanner.nextLine().trim(); } public String inputRestartNumber() { - System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요"); + System.out.print("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요 : "); - // TODO: 입력 유효성 검증 return scanner.nextLine().trim(); } } From 27650229ab032993f3771cc4eb6356630b961ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Sat, 31 Jan 2026 15:16:22 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=A0=90?= =?UTF-8?q?=EC=88=98=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 컴퓨터의 수와 사용자의 입력을 비교하여 스트라이크/볼을 판별하는 Referee 클래스 구현 - 같은 수가 같은 자리에 있는 경우 스트라이크 개수 계산 - 같은 수가 다른 자리에 있는 경우 볼 개수 계산 - 비교 결과를 통해 스트라이크와 볼의 개수를 Map 형태로 반환하는 기능 --- src/main/java/baseball/domain/Referee.java | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/baseball/domain/Referee.java diff --git a/src/main/java/baseball/domain/Referee.java b/src/main/java/baseball/domain/Referee.java new file mode 100644 index 00000000..17385fa1 --- /dev/null +++ b/src/main/java/baseball/domain/Referee.java @@ -0,0 +1,40 @@ +package baseball.domain; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Referee { + + // 해당 라운드 결과 산출 + public Map getResult(List computer, List input) { + + Map result = new HashMap<>(); + result.put("strike", countStrike(computer, input)); + result.put("ball", countBall(computer, input)); + + return result; + } + + // 스트라이크 갯수 세기 + 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; + } +} From 16ffa386cc32d13f868d9e3eff385dc7b077e3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Sat, 31 Jan 2026 15:17:23 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=20=EC=A7=84=ED=96=89=20=ED=9D=90=EB=A6=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 계산된 결과를 화면에 출력하고 게임의 시작부터 종료, 재시작까지의 흐름을 제어함 - OutputView: 볼/스트라이크 개수 및 낫싱, 게임 종료 문구 출력 기능 - GameController: 정답을 맞출 때까지 반복하는 라운드 진행 로직 - GameController: 게임 종료 후 사용자 입력(1, 2)에 따른 재시작/종료 분기 처리 --- .../controller/BaseballController.java | 29 +++++++++++++++++-- src/main/java/baseball/domain/Computer.java | 4 +++ src/main/java/baseball/view/OutputView.java | 29 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/main/java/baseball/view/OutputView.java diff --git a/src/main/java/baseball/controller/BaseballController.java b/src/main/java/baseball/controller/BaseballController.java index d75ac4d3..e8ec7def 100644 --- a/src/main/java/baseball/controller/BaseballController.java +++ b/src/main/java/baseball/controller/BaseballController.java @@ -1,16 +1,23 @@ package baseball.controller; import baseball.domain.Computer; +import baseball.domain.Referee; import baseball.util.Validator; import baseball.view.InputView; import baseball.view.OutputView; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + public class BaseballController { + private final Referee referee; private final InputView inputView; private final OutputView outputView; public BaseballController() { + this.referee = new Referee(); this.inputView = new InputView(); this.outputView = new OutputView(); } @@ -30,9 +37,16 @@ private void play() { while (true) { String input = getValidInput(); // 입력 받기 + List inputNumbers = convertInputToList(input); // 입력을 List로 변환 + Map result = referee.getResult(computer.getNums(), inputNumbers); // 심판에게 결과 받음 + outputView.printResult(result.get("strike"), result.get("ball")); // 받은 결과 출력 - // TODO : 스트라이크/볼 판별 + // 3스트라이크 시 게임 승리 + if(result.get("strike") == 3 && result.get("ball") == 0) { + outputView.printFinish(); + break; + } } } @@ -46,15 +60,26 @@ private String getValidInput() { return input; } + // 재시작 입력 받기 private boolean isRestart() { String input; do { input = inputView.inputRestartNumber(); } while(!Validator.isRestartNumberPossible(input)); - System.out.println("true"); if(input.equals("1")) return true; return false; } + + // 입력 String을 List로 변환 + private List convertInputToList(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/domain/Computer.java b/src/main/java/baseball/domain/Computer.java index f0ed6ded..719f2964 100644 --- a/src/main/java/baseball/domain/Computer.java +++ b/src/main/java/baseball/domain/Computer.java @@ -6,6 +6,10 @@ public class Computer { + public List getNums() { + return nums; + } + private final List nums; private final Random random = new Random(); diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 00000000..7becc7b5 --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,29 @@ +package baseball.view; + +public class OutputView { + + // 결과 출력 + public void printResult(int strike, int ball) { + 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개의 숫자를 모두 맞히셨습니다! 게임 끝"); + } +} From 5e5d8f634f32c9c0a05474fe21692b3dc759a1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Sat, 31 Jan 2026 15:18:41 +0900 Subject: [PATCH 7/9] =?UTF-8?q?docs:=20InputView=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B8=B0=EB=8A=A5=20=EC=84=A4=EB=AA=85=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InputView 클래스의 메서드에 대한 설명 주석 작성 - 숫자 입력 (inputNumber) 메서드 - 게임 재시작/종료 입력 (inputRestartNumber) 메서드 --- src/main/java/baseball/view/InputView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java index 8e9c7004..5f473e10 100644 --- a/src/main/java/baseball/view/InputView.java +++ b/src/main/java/baseball/view/InputView.java @@ -9,12 +9,14 @@ 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를 입력하세요 : "); From 91491956ec616b5b7a86a3300e0b9a2e13f38f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Sat, 7 Feb 2026 19:04:24 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20=EA=B2=80=EC=A6=9D/=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=ED=99=94=20=EB=B0=8F=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Validator 반환값을 boolean에서 ValidationResult로 변경 - Referee 결과 타입을 Map에서 Result로 변경 - BaseballController의 입력 파싱/검증 흐름 정리 - OutputView가 Result 기반 출력과 printError를 처리하도록 변경 --- .../controller/BaseballController.java | 49 ++++++++----------- src/main/java/baseball/domain/Referee.java | 12 ++--- src/main/java/baseball/domain/Result.java | 23 +++++++++ src/main/java/baseball/util/InputParser.java | 19 +++++++ .../java/baseball/util/ValidationResult.java | 27 ++++++++++ src/main/java/baseball/util/Validator.java | 20 +++++--- src/main/java/baseball/view/OutputView.java | 14 +++++- 7 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 src/main/java/baseball/domain/Result.java create mode 100644 src/main/java/baseball/util/InputParser.java create mode 100644 src/main/java/baseball/util/ValidationResult.java diff --git a/src/main/java/baseball/controller/BaseballController.java b/src/main/java/baseball/controller/BaseballController.java index e8ec7def..7a3de394 100644 --- a/src/main/java/baseball/controller/BaseballController.java +++ b/src/main/java/baseball/controller/BaseballController.java @@ -2,19 +2,21 @@ 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.ArrayList; import java.util.List; -import java.util.Map; public class BaseballController { private final Referee referee; private final InputView inputView; private final OutputView outputView; + private Computer computer; public BaseballController() { this.referee = new Referee(); @@ -22,8 +24,6 @@ public BaseballController() { this.outputView = new OutputView(); } - private static Computer computer; - // 프로그램 실행 public void run() { do { @@ -37,13 +37,13 @@ private void play() { while (true) { String input = getValidInput(); // 입력 받기 - List inputNumbers = convertInputToList(input); // 입력을 List로 변환 + List inputNumbers = InputParser.parseToNumbers(input); // 입력을 List로 변환 - Map result = referee.getResult(computer.getNums(), inputNumbers); // 심판에게 결과 받음 - outputView.printResult(result.get("strike"), result.get("ball")); // 받은 결과 출력 + Result result = referee.getResult(computer.getNums(), inputNumbers); // 심판에게 결과 받음 + outputView.printResult(result); // 받은 결과 출력 // 3스트라이크 시 게임 승리 - if(result.get("strike") == 3 && result.get("ball") == 0) { + if (result.isWin()) { outputView.printFinish(); break; } @@ -53,33 +53,26 @@ private void play() { // 유효한 입력 받기 private String getValidInput() { String input; - do { + while (true) { input = inputView.inputNumber(); - } while(!Validator.isGameNumberPossible(input)); - - return input; + ValidationResult validationResult = Validator.validateGameNumber(input); + if (validationResult.isValid()) { + return input; + } + outputView.printError(validationResult.getMessage()); + } } // 재시작 입력 받기 private boolean isRestart() { String input; - do { + while (true) { input = inputView.inputRestartNumber(); - } while(!Validator.isRestartNumberPossible(input)); - - if(input.equals("1")) return true; - - return false; - } - - // 입력 String을 List로 변환 - private List convertInputToList(String input) { - List numbers = new ArrayList<>(); - - for (char c : input.toCharArray()) { - numbers.add(Character.getNumericValue(c)); + ValidationResult validationResult = Validator.validateRestartNumber(input); + if (validationResult.isValid()) { + return input.equals("1"); + } + outputView.printError(validationResult.getMessage()); } - - return numbers; } } diff --git a/src/main/java/baseball/domain/Referee.java b/src/main/java/baseball/domain/Referee.java index 17385fa1..e0f6c0b7 100644 --- a/src/main/java/baseball/domain/Referee.java +++ b/src/main/java/baseball/domain/Referee.java @@ -1,19 +1,15 @@ package baseball.domain; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class Referee { // 해당 라운드 결과 산출 - public Map getResult(List computer, List input) { + public Result getResult(List computer, List input) { + int strike = countStrike(computer, input); + int ball = countBall(computer, input); - Map result = new HashMap<>(); - result.put("strike", countStrike(computer, input)); - result.put("ball", countBall(computer, input)); - - return result; + return new Result(strike, 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 index 0da69521..c884ec75 100644 --- a/src/main/java/baseball/util/Validator.java +++ b/src/main/java/baseball/util/Validator.java @@ -7,21 +7,25 @@ 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 boolean isGameNumberPossible(String input) { - if (validateIsNumber(input) && validateLength(input) && validateRange(input) && validateDuplicateNumber(input)) return true; + public static ValidationResult validateGameNumber(String input) { + if (validateIsNumber(input) && validateLength(input) && validateRange(input) && validateDuplicateNumber(input)) { + return ValidationResult.ok(); + } - System.out.println(ERROR_PREFIX + "유효하지 않은 입력입니다."); - return false; + return ValidationResult.fail(INVALID_INPUT); } // 유효한 게임 재시작 입력인지 검증 - public static boolean isRestartNumberPossible(String input) { - if (input.equals("1") || input.equals("2")) return true; + public static ValidationResult validateRestartNumber(String input) { + if (input.equals("1") || input.equals("2")) { + return ValidationResult.ok(); + } - System.out.println(ERROR_PREFIX + "1, 2 중 입력하세요."); - return false; + return ValidationResult.fail(INVALID_RESTART_INPUT); } // 입력받은 숫자가 숫자가 아닌 값이 포함되어 있는지 검증 diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java index 7becc7b5..0a9baa75 100644 --- a/src/main/java/baseball/view/OutputView.java +++ b/src/main/java/baseball/view/OutputView.java @@ -1,10 +1,15 @@ package baseball.view; +import baseball.domain.Result; + public class OutputView { // 결과 출력 - public void printResult(int strike, int ball) { - if(strike == 0 && ball == 0) { + public void printResult(Result result) { + int strike = result.getStrike(); + int ball = result.getBall(); + + if (strike == 0 && ball == 0) { System.out.println("낫싱"); return; } @@ -26,4 +31,9 @@ public String buildResultString(int strike, int ball) { public void printFinish() { System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); } + + // 에러 멘트 출력 + public void printError(String message) { + System.out.println(message); + } } From fb388c52416c05cd063595334f2db2567eab378a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=B0=BD=EB=AF=BC?= Date: Sat, 7 Feb 2026 19:53:44 +0900 Subject: [PATCH 9/9] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Computer 숫자 생성 규칙(개수, 범위, 중복, 반복) 테스트 - Referee 판정(스트라이크, 볼, 낫싱) 시나리오 테스트 - Result 승리, 패배 시나리오 테스트 - InputParser 문자열 -> 리스트 파싱 테스트 - Validator 입력 검증, 에러 메시지 테스트 - ValidationResult ok, fail 테스트 --- .../java/baseball/domain/ComputerTest.java | 64 +++++++++++++ .../java/baseball/domain/RefereeTest.java | 95 +++++++++++++++++++ src/test/java/baseball/domain/ResultTest.java | 37 ++++++++ .../java/baseball/util/InputParserTest.java | 27 ++++++ .../baseball/util/ValidationResultTest.java | 27 ++++++ .../java/baseball/util/ValidatorTest.java | 79 +++++++++++++++ 6 files changed, 329 insertions(+) create mode 100644 src/test/java/baseball/domain/ComputerTest.java create mode 100644 src/test/java/baseball/domain/RefereeTest.java create mode 100644 src/test/java/baseball/domain/ResultTest.java create mode 100644 src/test/java/baseball/util/InputParserTest.java create mode 100644 src/test/java/baseball/util/ValidationResultTest.java create mode 100644 src/test/java/baseball/util/ValidatorTest.java 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); + } +}