From f1590cd9250e765ee146cbcfabc7e8dca9c52d6d Mon Sep 17 00:00:00 2001 From: believeme Date: Thu, 30 Oct 2025 23:41:46 +0900 Subject: [PATCH 01/21] =?UTF-8?q?=EB=A6=AC=EB=93=9C=EB=AF=B8=20=EC=B4=88?= =?UTF-8?q?=EC=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 401 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 400 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5fa2560b46..c319296c7d 100644 --- a/README.md +++ b/README.md @@ -1 +1,400 @@ -# java-lotto-precourse +# 🎰 둜또 κ²Œμž„ (java-lotto-precourse) + +## πŸ“– ν”„λ‘œμ νŠΈ μ†Œκ°œ + +> μš°μ•„ν•œν…Œν¬μ½”μŠ€ ν”„λ¦¬μ½”μŠ€ 3μ£Όμ°¨ λ―Έμ…˜μž…λ‹ˆλ‹€. +> +> 둜또 ꡬ맀뢀터 당첨 ν™•μΈκΉŒμ§€ 전체 ν”„λ‘œμ„ΈμŠ€λ₯Ό κ΅¬ν˜„ν•œ μ½˜μ†” 기반 둜또 κ²Œμž„μž…λ‹ˆλ‹€. + +### μ£Όμš” κΈ°λŠ₯ +- πŸ’° ꡬ맀 κΈˆμ•‘ μž…λ ₯ 및 둜또 μžλ™ λ°œν–‰ +- 🎲 1~45 λ²”μœ„μ˜ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” 번호 6개 생성 +- 🎯 당첨 번호 및 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ +- πŸ† 당첨 톡계 및 수읡λ₯  계산 +- βœ… μž…λ ₯κ°’ 검증 및 μ˜ˆμ™Έ 처리 + +--- + +## πŸš€ μ‹€ν–‰ 방법 + +### μš”κ΅¬μ‚¬ν•­ +- Java 21 +- Gradle + +### μ‹€ν–‰ +```bash +./gradlew run +``` + +### μž…μΆœλ ₯ μ˜ˆμ‹œ + +**μž…λ ₯:** +``` +κ΅¬μž…κΈˆμ•‘μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”. +8000 + +당첨 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”. +1,2,3,4,5,6 + +λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”. +7 +``` + +**좜λ ₯:** +``` +8개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] + +당첨 톡계 +--- +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +총 수읡λ₯ μ€ 62.5%μž…λ‹ˆλ‹€. +``` + +--- + +## πŸ› οΈ 기술 μŠ€νƒ + +- **Language:** Java 21 +- **Build Tool:** Gradle +- **Testing:** JUnit 5, AssertJ +- **Library:** camp.nextstep.edu.missionutils + +--- + +## πŸ“¦ νŒ¨ν‚€μ§€ ꡬ쑰 +``` +src/main/java/lotto/ +β”œβ”€β”€ Application.java # ν”„λ‘œκ·Έλž¨ μ§„μž…μ  +β”œβ”€β”€ domain/ # 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 +β”‚ β”œβ”€β”€ Lotto.java # 둜또 번호 (6개) +β”‚ β”œβ”€β”€ LottoTickets.java # μ—¬λŸ¬ 둜또 관리 +β”‚ β”œβ”€β”€ WinningLotto.java # 당첨 번호 + λ³΄λ„ˆμŠ€ 번호 +β”‚ β”œβ”€β”€ Rank.java # 당첨 λ“±μˆ˜ (Enum) +β”‚ └── LottoResult.java # 당첨 κ²°κ³Ό 및 수읡λ₯  +β”œβ”€β”€ service/ # κ²Œμž„ μ§„ν–‰ 쑰율 +β”‚ └── LottoGameService.java +β”œβ”€β”€ view/ # μž…μΆœλ ₯ 처리 +β”‚ β”œβ”€β”€ InputView.java # μ‚¬μš©μž μž…λ ₯ +β”‚ └── OutputView.java # κ²°κ³Ό 좜λ ₯ +β”œβ”€β”€ validator/ # μž…λ ₯ 검증 +β”‚ └── InputValidator.java +└── util/ # μœ ν‹Έλ¦¬ν‹° + └── LottoGenerator.java # 둜또 번호 생성 +``` + +--- + +## βš™οΈ κΈ°λŠ₯ κ΅¬ν˜„ λͺ©λ‘ + +### 1. 도메인 객체 κ΅¬ν˜„ + +#### 1.1 Lotto 객체 κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] 6개의 둜또 번호λ₯Ό μ €μž₯ν•œλ‹€ +- [ ] 둜또 λ²ˆν˜ΈλŠ” 1~45 λ²”μœ„μ˜ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” μˆ«μžμ΄λ‹€ +- [ ] 둜또 번호λ₯Ό μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•˜μ—¬ λ°˜ν™˜ν•œλ‹€ +- [ ] μ£Όμ–΄μ§„ 번호 λ¦¬μŠ€νŠΈμ™€ μΌμΉ˜ν•˜λŠ” 개수λ₯Ό λ°˜ν™˜ν•œλ‹€ +- [ ] νŠΉμ • 번호 포함 μ—¬λΆ€λ₯Ό ν™•μΈν•œλ‹€ + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] 둜또 λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 μ˜ˆμ™Έ λ°œμƒ +- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ + +**컀밋:** `feat(domain): Lotto 객체 κΈ°λ³Έ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 1.2 Rank μ—΄κ±°ν˜• κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] 일치 κ°œμˆ˜μ™€ λ³΄λ„ˆμŠ€ 일치 μ—¬λΆ€λ‘œ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ +- [ ] 각 λ“±μˆ˜λ³„ μƒκΈˆμ„ λ°˜ν™˜ν•œλ‹€ +- [ ] 5λ“±: 3개 일치 / 5,000원 +- [ ] 4λ“±: 4개 일치 / 50,000원 +- [ ] 3λ“±: 5개 일치 / 1,500,000원 +- [ ] 2λ“±: 5개 + λ³΄λ„ˆμŠ€ 일치 / 30,000,000원 +- [ ] 1λ“±: 6개 일치 / 2,000,000,000원 +- [ ] λ‹Ήμ²¨λ˜μ§€ μ•ŠμœΌλ©΄ NONE λ°˜ν™˜ + +**컀밋:** `feat(domain): Rank μ—΄κ±°ν˜• κ΅¬ν˜„` + +#### 1.3 WinningLotto 객체 κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] 당첨 번호 6κ°œμ™€ λ³΄λ„ˆμŠ€ 번호 1개λ₯Ό μ €μž₯ν•œλ‹€ +- [ ] λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ + +**컀밋:** `feat(domain): WinningLotto 객체 κ΅¬ν˜„` + +#### 1.4 LottoTickets 객체 κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] μ—¬λŸ¬ 개의 둜또λ₯Ό κ΄€λ¦¬ν•œλ‹€ +- [ ] κ΅¬λ§€ν•œ 둜또 개수λ₯Ό λ°˜ν™˜ν•œλ‹€ +- [ ] λͺ¨λ“  둜또λ₯Ό λ°˜ν™˜ν•œλ‹€ + +**컀밋:** `feat(domain): LottoTickets 객체 κ΅¬ν˜„` + +#### 1.5 LottoResult 객체 κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] λ“±μˆ˜λ³„ 당첨 개수λ₯Ό μ €μž₯ν•œλ‹€ +- [ ] 총 μˆ˜μ΅κΈˆμ„ κ³„μ‚°ν•œλ‹€ +- [ ] 수읡λ₯ μ„ κ³„μ‚°ν•œλ‹€ (μ†Œμˆ˜μ  λ‘˜μ§Έ 자리 반올림) +- [ ] λ“±μˆ˜λ³„ 당첨 개수λ₯Ό λ°˜ν™˜ν•œλ‹€ + +**컀밋:** `feat(domain): LottoResult 객체 κ΅¬ν˜„` + +--- + +### 2. μž…λ ₯ 검증 κ΅¬ν˜„ + +#### 2.1 ꡬ맀 κΈˆμ•‘ 검증 + +**정상 μΌ€μ΄μŠ€:** +- [ ] 1,000원 μ΄μƒμ˜ κΈˆμ•‘μ΄λ©΄ 톡과 +- [ ] 1,000원 λ‹¨μœ„λ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€λ©΄ 톡과 + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] μˆ«μžκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 1,000원 미만이면 μ˜ˆμ™Έ λ°œμƒ +- [ ] 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μ΄λ©΄ μ˜ˆμ™Έ λ°œμƒ + +**컀밋:** `feat(validator): ꡬ맀 κΈˆμ•‘ 검증 κ΅¬ν˜„` + +#### 2.2 당첨 번호 검증 + +**정상 μΌ€μ΄μŠ€:** +- [ ] μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ 6개의 번호면 톡과 +- [ ] 각 λ²ˆν˜Έκ°€ 1~45 λ²”μœ„ 내에 있으면 톡과 +- [ ] μ€‘λ³΅λ˜μ§€ μ•ŠμœΌλ©΄ 톡과 + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 μ˜ˆμ™Έ λ°œμƒ +- [ ] μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λ˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μ΄λ©΄ μ˜ˆμ™Έ λ°œμƒ + +**컀밋:** `feat(validator): 당첨 번호 검증 κ΅¬ν˜„` + +#### 2.3 λ³΄λ„ˆμŠ€ 번호 검증 + +**정상 μΌ€μ΄μŠ€:** +- [ ] 1~45 λ²”μœ„μ˜ 숫자면 톡과 +- [ ] 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜μ§€ μ•ŠμœΌλ©΄ 톡과 + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] μˆ«μžκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μ΄λ©΄ μ˜ˆμ™Έ λ°œμƒ + +**컀밋:** `feat(validator): λ³΄λ„ˆμŠ€ 번호 검증 κ΅¬ν˜„` + +--- + +### 3. 둜또 번호 생성 + +#### 3.1 μžλ™ 둜또 번호 생성 + +**정상 μΌ€μ΄μŠ€:** +- [ ] 1~45 λ²”μœ„μ—μ„œ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” 6개의 숫자λ₯Ό μƒμ„±ν•œλ‹€ +- [ ] `Randoms.pickUniqueNumbersInRange()` μ‚¬μš© +- [ ] ꡬ맀 κΈˆμ•‘μ— λ§žλŠ” 개수만큼 둜또λ₯Ό μƒμ„±ν•œλ‹€ + +**컀밋:** `feat(util): 둜또 번호 μžλ™ 생성 κ΅¬ν˜„` + +--- + +### 4. μž…μΆœλ ₯ κΈ°λŠ₯ + +#### 4.1 ꡬ맀 κΈˆμ•‘ μž…λ ₯ +- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯ +- [ ] λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ λ³€ν™˜ + +**컀밋:** `feat(view): ꡬ맀 κΈˆμ•‘ μž…λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 4.2 당첨 번호 μž…λ ₯ +- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯ +- [ ] μ‰Όν‘œ κΈ°μ€€μœΌλ‘œ 번호 뢄리 +- [ ] 각 번호의 곡백 제거 및 μ •μˆ˜ λ³€ν™˜ + +**컀밋:** `feat(view): 당첨 번호 μž…λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 4.3 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ +- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯ +- [ ] λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ λ³€ν™˜ + +**컀밋:** `feat(view): λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 4.4 κ΅¬λ§€ν•œ 둜또 좜λ ₯ +- [ ] "n개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€." 좜λ ₯ +- [ ] 각 둜또λ₯Ό μ˜€λ¦„μ°¨μˆœ μ •λ ¬ν•˜μ—¬ 좜λ ₯ +- [ ] ν˜•μ‹: [번호1, 번호2, 번호3, 번호4, 번호5, 번호6] + +**컀밋:** `feat(view): ꡬ맀 둜또 좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 4.5 당첨 톡계 좜λ ₯ +- [ ] "당첨 톡계" 헀더 좜λ ₯ +- [ ] ꡬ뢄선 "---" 좜λ ₯ +- [ ] 각 λ“±μˆ˜λ³„ 당첨 개수 좜λ ₯ +- [ ] ν˜•μ‹: "n개 일치 (κΈˆμ•‘μ›) - m개" +- [ ] 2λ“±: "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (κΈˆμ•‘μ›) - m개" + +**컀밋:** `feat(view): 당첨 톡계 좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 4.6 수읡λ₯  좜λ ₯ +- [ ] "총 수읡λ₯ μ€ n%μž…λ‹ˆλ‹€." ν˜•μ‹μœΌλ‘œ 좜λ ₯ +- [ ] μ†Œμˆ˜μ  λ‘˜μ§Έ μžλ¦¬μ—μ„œ 반올림 + +**컀밋:** `feat(view): 수읡λ₯  좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +#### 4.7 μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ +- [ ] "[ERROR]"둜 μ‹œμž‘ν•˜λŠ” μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ +- [ ] μ—λŸ¬ λ°œμƒ ν›„ ν•΄λ‹Ή μž…λ ₯λΆ€ν„° μž¬μ‹œλ„ + +**컀밋:** `feat(view): μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` + +--- + +### 5. κ²Œμž„ μ§„ν–‰ μ„œλΉ„μŠ€ + +#### 5.1 LottoGameService κ΅¬ν˜„ +- [ ] ꡬ맀 κΈˆμ•‘ μž…λ ₯ 및 검증 +- [ ] 둜또 μžλ™ λ°œν–‰ +- [ ] κ΅¬λ§€ν•œ 둜또 좜λ ₯ +- [ ] 당첨 번호 μž…λ ₯ 및 검증 +- [ ] λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ 및 검증 +- [ ] 당첨 κ²°κ³Ό 계산 +- [ ] 당첨 톡계 및 수읡λ₯  좜λ ₯ +- [ ] μ˜ˆμ™Έ λ°œμƒ μ‹œ ν•΄λ‹Ή 단계뢀터 μž¬μ‹œλ„ + +**컀밋:** `feat(service): LottoGameService κ²Œμž„ μ§„ν–‰ 둜직 κ΅¬ν˜„` + +--- + +### 6. 메인 μ‹€ν–‰ + +#### 6.1 Application μ§„μž…μ  +- [ ] LottoGameService μ‹€ν–‰ +- [ ] μ˜ˆμ™Έ λ°œμƒ μ‹œ μ μ ˆν•œ 처리 + +**컀밋:** `feat(app): Application 메인 μ‹€ν–‰ κ΅¬ν˜„` + +--- + +## πŸ—οΈ μ•„ν‚€ν…μ²˜ + +- **λ ˆμ΄μ–΄λ“œ μ•„ν‚€ν…μ²˜** (Layered Architecture) 기반 +- **Package by Layer** 방식 +- **Inside-Out 개발** (Domain β†’ Validator β†’ Util β†’ View β†’ Service β†’ Application) + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Application β”‚ (μ§„μž…μ ) +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Service β”‚ (쑰율 + μž¬μ‹œλ„ 둜직) +β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Domain β”‚ β”‚ View β”‚ β”‚ Util β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–² β–² + β”‚ β”‚ +β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β” +β”‚ Validator β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## βœ… ν”„λ‘œκ·Έλž˜λ° μš”κ΅¬μ‚¬ν•­ μ€€μˆ˜ + +- [ ] JDK 21μ—μ„œ μ‹€ν–‰ κ°€λŠ₯ +- [ ] indent depth 2 μ΄ν•˜ +- [ ] 3ν•­ μ—°μ‚°μž λ―Έμ‚¬μš© +- [ ] else μ˜ˆμ•½μ–΄ λ―Έμ‚¬μš© +- [ ] ν•¨μˆ˜ 길이 15라인 μ΄ν•˜ +- [ ] ν•¨μˆ˜κ°€ ν•œ κ°€μ§€ 일만 μˆ˜ν–‰ +- [ ] Java Enum ν™œμš© +- [ ] JUnit 5, AssertJ ν…ŒμŠ€νŠΈ μž‘μ„± +- [ ] `Randoms.pickUniqueNumbersInRange()` μ‚¬μš© +- [ ] `Console.readLine()` μ‚¬μš© +- [ ] 제곡된 Lotto 클래슀 ν™œμš© +- [ ] UI 둜직 μ œμ™Έ λ‹¨μœ„ ν…ŒμŠ€νŠΈ μž‘μ„± + +--- + +## πŸ§ͺ ν…ŒμŠ€νŠΈ + +### ν…ŒμŠ€νŠΈ μ‹€ν–‰ +```bash +./gradlew test +``` + +### ν…ŒμŠ€νŠΈ ꡬ쑰 +``` +src/test/java/lotto/ +β”œβ”€β”€ domain/ +β”‚ β”œβ”€β”€ LottoTest.java +β”‚ β”œβ”€β”€ RankTest.java +β”‚ β”œβ”€β”€ WinningLottoTest.java +β”‚ β”œβ”€β”€ LottoTicketsTest.java +β”‚ └── LottoResultTest.java +β”œβ”€β”€ validator/ +β”‚ └── InputValidatorTest.java +β”œβ”€β”€ util/ +β”‚ └── LottoGeneratorTest.java +└── service/ + └── LottoGameServiceTest.java +``` + +--- + +## πŸ’­ 회고 + +### 이번 μ£Όμ°¨ ν•™μŠ΅ λͺ©ν‘œ +- [ ] Java Enum을 ν™œμš©ν•œ λ“±μˆ˜ 관리 +- [ ] 일급 μ»¬λ ‰μ…˜ νŒ¨ν„΄ 적용 +- [ ] μ˜ˆμ™Έ λ°œμƒ μ‹œ μž¬μž…λ ₯ 둜직 κ΅¬ν˜„ +- [ ] 2μ£Όμ°¨ 곡톡 ν”Όλ“œλ°± 반영 + +### 2μ£Όμ°¨ ν”Όλ“œλ°± 반영 사항 +- [ ] ν•¨μˆ˜ 뢄리λ₯Ό ν†΅ν•œ indent depth 관리 +- [ ] Early return νŒ¨ν„΄μœΌλ‘œ else 제거 +- [ ] 맀직 λ„˜λ²„ μƒμˆ˜ν™” +- [ ] 도메인 둜직과 UI 둜직 뢄리 + +--- + +## πŸ“š ν•™μŠ΅ λ‚΄μš© + +- Java Enum ν™œμš© +- 일급 μ»¬λ ‰μ…˜ (First-Class Collection) +- μ˜ˆμ™Έ 처리 및 μž¬μž…λ ₯ 둜직 +- 객체 κ°„ ν˜‘λ ₯ 섀계 +- λ‹¨μœ„ ν…ŒμŠ€νŠΈ μž‘μ„± +- 수읡λ₯  계산 및 ν¬λ§·νŒ… \ No newline at end of file From 51878c28d010afbdf95c927b6c7b395bdacb7acf Mon Sep 17 00:00:00 2001 From: believeme Date: Sat, 1 Nov 2025 11:00:17 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat(readme):=20README=20=EC=9E=AC?= =?UTF-8?q?=EC=BB=B4=ED=86=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. parsor νŒ¨ν‚€μ§€ 뢄리 2. μ˜ˆμ™Έμ²˜λ¦¬ μ •μ˜ --- README.md | 347 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 256 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index c319296c7d..91ffab6bb2 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,20 @@ - 🎲 1~45 λ²”μœ„μ˜ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” 번호 6개 생성 - 🎯 당첨 번호 및 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ - πŸ† 당첨 톡계 및 수읡λ₯  계산 -- βœ… μž…λ ₯κ°’ 검증 및 μ˜ˆμ™Έ 처리 +- βœ… μž…λ ₯κ°’ 검증 및 μ˜ˆμ™Έ 처리 (μž¬μ‹œλ„ 둜직) --- -## πŸš€ μ‹€ν–‰ 방법 +## πŸ› οΈ 기술 μŠ€νƒ -### μš”κ΅¬μ‚¬ν•­ -- Java 21 -- Gradle +- **Language:** Java 21 +- **Build Tool:** Gradle +- **Testing:** JUnit 5, AssertJ +- **Library:** camp.nextstep.edu.missionutils -### μ‹€ν–‰ -```bash -./gradlew run -``` +--- -### μž…μΆœλ ₯ μ˜ˆμ‹œ +## πŸš€ μž…μΆœλ ₯ μ˜ˆμ‹œ **μž…λ ₯:** ``` @@ -64,16 +62,16 @@ --- -## πŸ› οΈ 기술 μŠ€νƒ - -- **Language:** Java 21 -- **Build Tool:** Gradle -- **Testing:** JUnit 5, AssertJ -- **Library:** camp.nextstep.edu.missionutils - ---- - ## πŸ“¦ νŒ¨ν‚€μ§€ ꡬ쑰 +**λ ˆμ΄μ–΄λ³„ μ±…μž„:** +- **Application**: ν”„λ‘œκ·Έλž¨ μ‹œμž‘μ  +- **Domain**: 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 +- **Service**: 전체 κ²Œμž„ 흐름 쑰율 및 μ˜ˆμ™Έ μž¬μ‹œλ„ 처리 +- **View**: μž…μΆœλ ₯ λ‹΄λ‹Ή (`Console.readLine()`, `System.out.println()`) +- **Parser**: λ¬Έμžμ—΄ β†’ 객체 λ³€ν™˜ +- **Validator**: λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™ 검증 +- **Util**: 둜또 번호 생성 λ“± μœ ν‹Έλ¦¬ν‹° + ``` src/main/java/lotto/ β”œβ”€β”€ Application.java # ν”„λ‘œκ·Έλž¨ μ§„μž…μ  @@ -86,8 +84,12 @@ src/main/java/lotto/ β”œβ”€β”€ service/ # κ²Œμž„ μ§„ν–‰ 쑰율 β”‚ └── LottoGameService.java β”œβ”€β”€ view/ # μž…μΆœλ ₯ 처리 -β”‚ β”œβ”€β”€ InputView.java # μ‚¬μš©μž μž…λ ₯ +β”‚ β”œβ”€β”€ InputView.java # μ‚¬μš©μž μž…λ ₯ (Console.readLine()) β”‚ └── OutputView.java # κ²°κ³Ό 좜λ ₯ +β”œβ”€β”€ parser/ # λ¬Έμžμ—΄ νŒŒμ‹± +β”‚ β”œβ”€β”€ PurchaseAmountParser.java +β”‚ β”œβ”€β”€ WinningNumbersParser.java +β”‚ └── BonusNumberParser.java β”œβ”€β”€ validator/ # μž…λ ₯ 검증 β”‚ └── InputValidator.java └── util/ # μœ ν‹Έλ¦¬ν‹° @@ -110,9 +112,14 @@ src/main/java/lotto/ - [ ] νŠΉμ • 번호 포함 μ—¬λΆ€λ₯Ό ν™•μΈν•œλ‹€ **μ˜ˆμ™Έ μΌ€μ΄μŠ€:** -- [ ] 둜또 λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 μ˜ˆμ™Έ λ°œμƒ -- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 둜또 λ²ˆν˜Έκ°€ null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] 둜또 λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€.` +- [ ] μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€.` +- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` **컀밋:** `feat(domain): Lotto 객체 κΈ°λ³Έ κΈ°λŠ₯ κ΅¬ν˜„` @@ -135,10 +142,16 @@ src/main/java/lotto/ **정상 μΌ€μ΄μŠ€:** - [ ] 당첨 번호 6κ°œμ™€ λ³΄λ„ˆμŠ€ 번호 1개λ₯Ό μ €μž₯ν•œλ‹€ - [ ] λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ +- [ ] 일치 개수λ₯Ό κ³„μ‚°ν•œλ‹€ +- [ ] λ³΄λ„ˆμŠ€ 번호 일치 μ—¬λΆ€λ₯Ό ν™•μΈν•œλ‹€ **μ˜ˆμ™Έ μΌ€μ΄μŠ€:** -- [ ] λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 당첨 λ²ˆν˜Έκ°€ null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€.` +- [ ] λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` **컀밋:** `feat(domain): WinningLotto 객체 κ΅¬ν˜„` @@ -148,6 +161,13 @@ src/main/java/lotto/ - [ ] μ—¬λŸ¬ 개의 둜또λ₯Ό κ΄€λ¦¬ν•œλ‹€ - [ ] κ΅¬λ§€ν•œ 둜또 개수λ₯Ό λ°˜ν™˜ν•œλ‹€ - [ ] λͺ¨λ“  둜또λ₯Ό λ°˜ν™˜ν•œλ‹€ +- [ ] 당첨 λ²ˆν˜Έμ™€ λΉ„κ΅ν•˜μ—¬ 각 둜또의 λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] 둜또 λ¦¬μŠ€νŠΈκ°€ null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 λͺ©λ‘μ΄ μ—†μŠ΅λ‹ˆλ‹€.` +- [ ] 둜또 λ¦¬μŠ€νŠΈκ°€ λΉ„μ–΄μžˆμœΌλ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] μ΅œμ†Œ 1개 μ΄μƒμ˜ 둜또λ₯Ό ꡬ맀해야 ν•©λ‹ˆλ‹€.` **컀밋:** `feat(domain): LottoTickets 객체 κ΅¬ν˜„` @@ -159,153 +179,285 @@ src/main/java/lotto/ - [ ] 수읡λ₯ μ„ κ³„μ‚°ν•œλ‹€ (μ†Œμˆ˜μ  λ‘˜μ§Έ 자리 반올림) - [ ] λ“±μˆ˜λ³„ 당첨 개수λ₯Ό λ°˜ν™˜ν•œλ‹€ +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] ꡬ맀 κΈˆμ•‘μ΄ 0 μ΄ν•˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] ꡬ맀 κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€.` + **컀밋:** `feat(domain): LottoResult 객체 κ΅¬ν˜„` --- -### 2. μž…λ ₯ 검증 κ΅¬ν˜„ +### 2. μž…λ ₯ νŒŒμ‹± κ΅¬ν˜„ + +#### 2.1 PurchaseAmountParser κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ λ³€ν™˜ν•œλ‹€ +- [ ] μ•žλ’€ 곡백을 μ œκ±°ν•œλ‹€ + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] μž…λ ₯이 null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] μž…λ ₯이 빈 λ¬Έμžμ—΄μ΄λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜λ©΄ `NumberFormatException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` + +**컀밋:** `feat(parser): ꡬ맀 κΈˆμ•‘ νŒŒμ‹± κ΅¬ν˜„` + +#### 2.2 WinningNumbersParser κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] μ‰Όν‘œ κΈ°μ€€μœΌλ‘œ λ¬Έμžμ—΄μ„ λΆ„λ¦¬ν•œλ‹€ +- [ ] 각 번호의 μ•žλ’€ 곡백을 μ œκ±°ν•œλ‹€ +- [ ] λ¬Έμžμ—΄ 리슀트λ₯Ό μ •μˆ˜ 리슀트둜 λ³€ν™˜ν•œλ‹€ + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] μž…λ ₯이 null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] μž…λ ₯이 빈 λ¬Έμžμ—΄μ΄λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] μ‰Όν‘œλ‘œ κ΅¬λΆ„λ˜μ§€ μ•Šμ€ 경우 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 λ²ˆν˜ΈλŠ” μ‰Όν‘œ(,)둜 ꡬ뢄해야 ν•©λ‹ˆλ‹€.` +- [ ] μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λ˜λ©΄ `NumberFormatException` λ°œμƒ + - `[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` +- [ ] 곡백만 μžˆλŠ” λ²ˆν˜Έκ°€ 있으면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€.` + +**컀밋:** `feat(parser): 당첨 번호 νŒŒμ‹± κ΅¬ν˜„` + +#### 2.3 BonusNumberParser κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ λ³€ν™˜ν•œλ‹€ +- [ ] μ•žλ’€ 곡백을 μ œκ±°ν•œλ‹€ + +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] μž…λ ₯이 null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] μž…λ ₯이 빈 λ¬Έμžμ—΄μ΄λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜λ©΄ `NumberFormatException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` + +**컀밋:** `feat(parser): λ³΄λ„ˆμŠ€ 번호 νŒŒμ‹± κ΅¬ν˜„` + +--- + +### 3. μž…λ ₯ 검증 κ΅¬ν˜„ -#### 2.1 ꡬ맀 κΈˆμ•‘ 검증 +#### 3.1 ꡬ맀 κΈˆμ•‘ 검증 **정상 μΌ€μ΄μŠ€:** - [ ] 1,000원 μ΄μƒμ˜ κΈˆμ•‘μ΄λ©΄ 톡과 - [ ] 1,000원 λ‹¨μœ„λ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€λ©΄ 톡과 **μ˜ˆμ™Έ μΌ€μ΄μŠ€:** -- [ ] μˆ«μžκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] 1,000원 미만이면 μ˜ˆμ™Έ λ°œμƒ -- [ ] 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μ΄λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 음수λ₯Ό μž…λ ₯ν•˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.` +- [ ] 0을 μž…λ ₯ν•˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€.` +- [ ] 1,000원 미만이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€.` +- [ ] 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€.` **컀밋:** `feat(validator): ꡬ맀 κΈˆμ•‘ 검증 κ΅¬ν˜„` -#### 2.2 당첨 번호 검증 +#### 3.2 당첨 번호 검증 **정상 μΌ€μ΄μŠ€:** -- [ ] μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ 6개의 번호면 톡과 +- [ ] 6개의 번호면 톡과 - [ ] 각 λ²ˆν˜Έκ°€ 1~45 λ²”μœ„ 내에 있으면 톡과 - [ ] μ€‘λ³΅λ˜μ§€ μ•ŠμœΌλ©΄ 톡과 **μ˜ˆμ™Έ μΌ€μ΄μŠ€:** -- [ ] λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 μ˜ˆμ™Έ λ°œμƒ -- [ ] μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λ˜λ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μ΄λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 번호 λ¦¬μŠ€νŠΈκ°€ null이면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.` +- [ ] λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€.` +- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` +- [ ] μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 `IllegalArgumentException` λ°œμƒ + - `[ERROR] 당첨 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€.` **컀밋:** `feat(validator): 당첨 번호 검증 κ΅¬ν˜„` -#### 2.3 λ³΄λ„ˆμŠ€ 번호 검증 +#### 3.3 λ³΄λ„ˆμŠ€ 번호 검증 **정상 μΌ€μ΄μŠ€:** - [ ] 1~45 λ²”μœ„μ˜ 숫자면 톡과 - [ ] 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜μ§€ μ•ŠμœΌλ©΄ 톡과 **μ˜ˆμ™Έ μΌ€μ΄μŠ€:** -- [ ] μˆ«μžκ°€ μ•„λ‹ˆλ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ μ˜ˆμ™Έ λ°œμƒ -- [ ] nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μ΄λ©΄ μ˜ˆμ™Έ λ°œμƒ +- [ ] 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.` +- [ ] 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€.` **컀밋:** `feat(validator): λ³΄λ„ˆμŠ€ 번호 검증 κ΅¬ν˜„` --- -### 3. 둜또 번호 생성 +### 4. 둜또 번호 생성 -#### 3.1 μžλ™ 둜또 번호 생성 +#### 4.1 μžλ™ 둜또 번호 생성 **정상 μΌ€μ΄μŠ€:** - [ ] 1~45 λ²”μœ„μ—μ„œ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” 6개의 숫자λ₯Ό μƒμ„±ν•œλ‹€ - [ ] `Randoms.pickUniqueNumbersInRange()` μ‚¬μš© - [ ] ꡬ맀 κΈˆμ•‘μ— λ§žλŠ” 개수만큼 둜또λ₯Ό μƒμ„±ν•œλ‹€ +**μ˜ˆμ™Έ μΌ€μ΄μŠ€:** +- [ ] 생성할 둜또 κ°œμˆ˜κ°€ 0 μ΄ν•˜λ©΄ `IllegalArgumentException` λ°œμƒ + - `[ERROR] 둜또 κ°œμˆ˜λŠ” 1개 이상이어야 ν•©λ‹ˆλ‹€.` + **컀밋:** `feat(util): 둜또 번호 μžλ™ 생성 κ΅¬ν˜„` --- -### 4. μž…μΆœλ ₯ κΈ°λŠ₯ +### 5. μž…μΆœλ ₯ κΈ°λŠ₯ -#### 4.1 ꡬ맀 κΈˆμ•‘ μž…λ ₯ -- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯ -- [ ] λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ λ³€ν™˜ +#### 5.1 ꡬ맀 κΈˆμ•‘ μž…λ ₯ +- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯: "κ΅¬μž…κΈˆμ•‘μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”." +- [ ] `Console.readLine()`으둜 μž…λ ₯ λ°›κΈ° +- [ ] μž…λ ₯받은 λ¬Έμžμ—΄μ„ κ·ΈλŒ€λ‘œ λ°˜ν™˜ **컀밋:** `feat(view): ꡬ맀 κΈˆμ•‘ μž…λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` -#### 4.2 당첨 번호 μž…λ ₯ -- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯ -- [ ] μ‰Όν‘œ κΈ°μ€€μœΌλ‘œ 번호 뢄리 -- [ ] 각 번호의 곡백 제거 및 μ •μˆ˜ λ³€ν™˜ +#### 5.2 당첨 번호 μž…λ ₯ +- [ ] 빈 쀄 좜λ ₯ +- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯: "당첨 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”." +- [ ] `Console.readLine()`으둜 μž…λ ₯ λ°›κΈ° +- [ ] μž…λ ₯받은 λ¬Έμžμ—΄μ„ κ·ΈλŒ€λ‘œ λ°˜ν™˜ **컀밋:** `feat(view): 당첨 번호 μž…λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` -#### 4.3 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ -- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯ -- [ ] λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ λ³€ν™˜ +#### 5.3 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ +- [ ] 빈 쀄 좜λ ₯ +- [ ] μž…λ ₯ μ•ˆλ‚΄ λ©”μ‹œμ§€ 좜λ ₯: "λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”." +- [ ] `Console.readLine()`으둜 μž…λ ₯ λ°›κΈ° +- [ ] μž…λ ₯받은 λ¬Έμžμ—΄μ„ κ·ΈλŒ€λ‘œ λ°˜ν™˜ **컀밋:** `feat(view): λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` -#### 4.4 κ΅¬λ§€ν•œ 둜또 좜λ ₯ +#### 5.4 κ΅¬λ§€ν•œ 둜또 좜λ ₯ +- [ ] 빈 쀄 좜λ ₯ - [ ] "n개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€." 좜λ ₯ - [ ] 각 둜또λ₯Ό μ˜€λ¦„μ°¨μˆœ μ •λ ¬ν•˜μ—¬ 좜λ ₯ -- [ ] ν˜•μ‹: [번호1, 번호2, 번호3, 번호4, 번호5, 번호6] +- [ ] ν˜•μ‹: `[번호1, 번호2, 번호3, 번호4, 번호5, 번호6]` +- [ ] 각 λ‘œλ˜λ§ˆλ‹€ μ€„λ°”κΏˆ **컀밋:** `feat(view): ꡬ맀 둜또 좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` -#### 4.5 당첨 톡계 좜λ ₯ +#### 5.5 당첨 톡계 좜λ ₯ +- [ ] 빈 쀄 좜λ ₯ - [ ] "당첨 톡계" 헀더 좜λ ₯ - [ ] ꡬ뢄선 "---" 좜λ ₯ -- [ ] 각 λ“±μˆ˜λ³„ 당첨 개수 좜λ ₯ +- [ ] 5λ“±λΆ€ν„° 1λ“±κΉŒμ§€ μˆœμ„œλŒ€λ‘œ 좜λ ₯ - [ ] ν˜•μ‹: "n개 일치 (κΈˆμ•‘μ›) - m개" - [ ] 2λ“±: "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (κΈˆμ•‘μ›) - m개" +- [ ] κΈˆμ•‘μ€ 천 λ‹¨μœ„ μ‰Όν‘œ 포함 **컀밋:** `feat(view): 당첨 톡계 좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` -#### 4.6 수읡λ₯  좜λ ₯ +#### 5.6 수읡λ₯  좜λ ₯ - [ ] "총 수읡λ₯ μ€ n%μž…λ‹ˆλ‹€." ν˜•μ‹μœΌλ‘œ 좜λ ₯ - [ ] μ†Œμˆ˜μ  λ‘˜μ§Έ μžλ¦¬μ—μ„œ 반올림 +- [ ] 천 λ‹¨μœ„ μ‰Όν‘œ 포함 **컀밋:** `feat(view): 수읡λ₯  좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` -#### 4.7 μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ +#### 5.7 μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ - [ ] "[ERROR]"둜 μ‹œμž‘ν•˜λŠ” μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ -- [ ] μ—λŸ¬ λ°œμƒ ν›„ ν•΄λ‹Ή μž…λ ₯λΆ€ν„° μž¬μ‹œλ„ +- [ ] `System.out.println()` μ‚¬μš© **컀밋:** `feat(view): μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ κΈ°λŠ₯ κ΅¬ν˜„` --- -### 5. κ²Œμž„ μ§„ν–‰ μ„œλΉ„μŠ€ +### 6. κ²Œμž„ μ§„ν–‰ μ„œλΉ„μŠ€ -#### 5.1 LottoGameService κ΅¬ν˜„ -- [ ] ꡬ맀 κΈˆμ•‘ μž…λ ₯ 및 검증 +#### 6.1 LottoGameService κ΅¬ν˜„ +- [ ] ꡬ맀 κΈˆμ•‘ μž…λ ₯ 및 검증 (μž¬μ‹œλ„ 둜직) - [ ] 둜또 μžλ™ λ°œν–‰ - [ ] κ΅¬λ§€ν•œ 둜또 좜λ ₯ -- [ ] 당첨 번호 μž…λ ₯ 및 검증 -- [ ] λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ 및 검증 +- [ ] 당첨 번호 μž…λ ₯ 및 검증 (μž¬μ‹œλ„ 둜직) +- [ ] λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ 및 검증 (μž¬μ‹œλ„ 둜직) - [ ] 당첨 κ²°κ³Ό 계산 - [ ] 당첨 톡계 및 수읡λ₯  좜λ ₯ -- [ ] μ˜ˆμ™Έ λ°œμƒ μ‹œ ν•΄λ‹Ή 단계뢀터 μž¬μ‹œλ„ +- [ ] 각 μž…λ ₯ λ‹¨κ³„μ—μ„œ μ˜ˆμ™Έ λ°œμƒ μ‹œ ν•΄λ‹Ή 단계뢀터 μž¬μ‹œλ„ + +**μž¬μ‹œλ„ 흐름:** +- [ ] ꡬ맀 κΈˆμ•‘ μ˜ˆμ™Έ β†’ ꡬ맀 κΈˆμ•‘ μž…λ ₯λΆ€ν„° μž¬μ‹œλ„ +- [ ] 당첨 번호 μ˜ˆμ™Έ β†’ 당첨 번호 μž…λ ₯λΆ€ν„° μž¬μ‹œλ„ +- [ ] λ³΄λ„ˆμŠ€ 번호 μ˜ˆμ™Έ β†’ λ³΄λ„ˆμŠ€ 번호 μž…λ ₯λΆ€ν„° μž¬μ‹œλ„ **컀밋:** `feat(service): LottoGameService κ²Œμž„ μ§„ν–‰ 둜직 κ΅¬ν˜„` --- -### 6. 메인 μ‹€ν–‰ +### 7. 메인 μ‹€ν–‰ -#### 6.1 Application μ§„μž…μ  +#### 7.1 Application μ§„μž…μ  - [ ] LottoGameService μ‹€ν–‰ -- [ ] μ˜ˆμ™Έ λ°œμƒ μ‹œ μ μ ˆν•œ 처리 +- [ ] μ˜ˆμ™ΈλŠ” Service λ ˆμ΄μ–΄μ—μ„œ 처리 **컀밋:** `feat(app): Application 메인 μ‹€ν–‰ κ΅¬ν˜„` --- +## 🎯 μ˜ˆμ™Έ 처리 μ „λž΅ + +### Exception νƒ€μž…λ³„ μ‚¬μš© κΈ°μ€€ + +#### 1. `IllegalArgumentException` +**μ‚¬μš© μ‹œμ :** λ©”μ„œλ“œμ— μ „λ‹¬λœ μΈμžκ°€ μœ νš¨ν•˜μ§€ μ•Šμ„ λ•Œ +- 숫자 λ²”μœ„ 였λ₯˜ (1~45 λ²”μœ„ 벗어남) +- 개수 였λ₯˜ (6κ°œκ°€ μ•„λ‹˜) +- 쀑볡 였λ₯˜ +- λ‹¨μœ„ 였λ₯˜ (1,000원 λ‹¨μœ„ μ•„λ‹˜) +- null λ˜λŠ” 빈 κ°’ + +**μ˜ˆμ‹œ:** +```java +if (numbers.size() != 6) { + throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); +} +``` + +#### 2. `NumberFormatException` +**μ‚¬μš© μ‹œμ :** λ¬Έμžμ—΄μ„ 숫자둜 λ³€ν™˜ν•  수 없을 λ•Œ +- νŒŒμ‹± κ³Όμ •μ—μ„œ λ°œμƒ +- `Integer.parseInt()` μ‹€νŒ¨ μ‹œ + +**μ˜ˆμ‹œ:** +```java +try { + return Integer.parseInt(input); +} catch (NumberFormatException e) { + throw new NumberFormatException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); +} +``` + +#### 3. `IllegalStateException` (선택적 μ‚¬μš©) +**μ‚¬μš© μ‹œμ :** 객체의 μƒνƒœκ°€ λ©”μ„œλ“œ ν˜ΈμΆœμ— μ ν•©ν•˜μ§€ μ•Šμ„ λ•Œ +- κ²Œμž„μ΄ 이미 μ’…λ£Œλœ μƒνƒœμ—μ„œ μΆ”κ°€ μž‘μ—… μ‹œλ„ +- μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ 객체 μ‚¬μš© + +**μ˜ˆμ‹œ:** +```java +if (lottoTickets == null) { + throw new IllegalStateException("[ERROR] λ‘œλ˜κ°€ λ°œν–‰λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."); +} +``` +--- + ## πŸ—οΈ μ•„ν‚€ν…μ²˜ - **λ ˆμ΄μ–΄λ“œ μ•„ν‚€ν…μ²˜** (Layered Architecture) 기반 - **Package by Layer** 방식 -- **Inside-Out 개발** (Domain β†’ Validator β†’ Util β†’ View β†’ Service β†’ Application) +- **Inside-Out 개발** (Domain β†’ Parser β†’ Validator β†’ Util β†’ View β†’ Service β†’ Application) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” @@ -317,11 +469,11 @@ src/main/java/lotto/ β”‚ Service β”‚ (쑰율 + μž¬μ‹œλ„ 둜직) β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β–Ό β–Ό β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Domain β”‚ β”‚ View β”‚ β”‚ Util β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Domain β”‚ β”‚ Parser β”‚ β”‚ View β”‚ β”‚ Util β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² β–² β”‚ β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β” @@ -339,22 +491,18 @@ src/main/java/lotto/ - [ ] else μ˜ˆμ•½μ–΄ λ―Έμ‚¬μš© - [ ] ν•¨μˆ˜ 길이 15라인 μ΄ν•˜ - [ ] ν•¨μˆ˜κ°€ ν•œ κ°€μ§€ 일만 μˆ˜ν–‰ -- [ ] Java Enum ν™œμš© +- [ ] Java Enum ν™œμš© (Rank) - [ ] JUnit 5, AssertJ ν…ŒμŠ€νŠΈ μž‘μ„± - [ ] `Randoms.pickUniqueNumbersInRange()` μ‚¬μš© - [ ] `Console.readLine()` μ‚¬μš© -- [ ] 제곡된 Lotto 클래슀 ν™œμš© +- [ ] 제곡된 Lotto 클래슀 ν™œμš© (ν•„λ“œ μΆ”κ°€ λΆˆκ°€) - [ ] UI 둜직 μ œμ™Έ λ‹¨μœ„ ν…ŒμŠ€νŠΈ μž‘μ„± +- [ ] `IllegalArgumentException`, `IllegalStateException` λ“± λͺ…ν™•ν•œ μ˜ˆμ™Έ νƒ€μž… μ‚¬μš© --- ## πŸ§ͺ ν…ŒμŠ€νŠΈ -### ν…ŒμŠ€νŠΈ μ‹€ν–‰ -```bash -./gradlew test -``` - ### ν…ŒμŠ€νŠΈ ꡬ쑰 ``` src/test/java/lotto/ @@ -364,6 +512,10 @@ src/test/java/lotto/ β”‚ β”œβ”€β”€ WinningLottoTest.java β”‚ β”œβ”€β”€ LottoTicketsTest.java β”‚ └── LottoResultTest.java +β”œβ”€β”€ parser/ +β”‚ β”œβ”€β”€ PurchaseAmountParserTest.java +β”‚ β”œβ”€β”€ WinningNumbersParserTest.java +β”‚ └── BonusNumberParserTest.java β”œβ”€β”€ validator/ β”‚ └── InputValidatorTest.java β”œβ”€β”€ util/ @@ -372,14 +524,23 @@ src/test/java/lotto/ └── LottoGameServiceTest.java ``` +### ν…ŒμŠ€νŠΈ μž‘μ„± 원칙 +- [ ] 정상 μΌ€μ΄μŠ€μ™€ μ˜ˆμ™Έ μΌ€μ΄μŠ€ λͺ¨λ‘ ν…ŒμŠ€νŠΈ +- [ ] 경계값 ν…ŒμŠ€νŠΈ (0, 1, 45, 46 λ“±) +- [ ] `@ParameterizedTest` ν™œμš©ν•˜μ—¬ μ—¬λŸ¬ μΌ€μ΄μŠ€ 검증 +- [ ] `assertThatThrownBy()` 둜 μ˜ˆμ™Έ λ©”μ‹œμ§€κΉŒμ§€ 검증 +- [ ] UI 둜직(InputView, OutputView)은 ν…ŒμŠ€νŠΈ μ œμ™Έ + --- ## πŸ’­ 회고 ### 이번 μ£Όμ°¨ ν•™μŠ΅ λͺ©ν‘œ - [ ] Java Enum을 ν™œμš©ν•œ λ“±μˆ˜ 관리 -- [ ] 일급 μ»¬λ ‰μ…˜ νŒ¨ν„΄ 적용 +- [ ] 일급 μ»¬λ ‰μ…˜ νŒ¨ν„΄ 적용 (LottoTickets, LottoResult) - [ ] μ˜ˆμ™Έ λ°œμƒ μ‹œ μž¬μž…λ ₯ 둜직 κ΅¬ν˜„ +- [ ] Parser λ ˆμ΄μ–΄ λΆ„λ¦¬λ‘œ μ±…μž„ λͺ…ν™•ν™” +- [ ] λ‹€μ–‘ν•œ μ˜ˆμ™Έ νƒ€μž… ν™œμš© (IllegalArgumentException, NumberFormatException) - [ ] 2μ£Όμ°¨ 곡톡 ν”Όλ“œλ°± 반영 ### 2μ£Όμ°¨ ν”Όλ“œλ°± 반영 사항 @@ -387,14 +548,18 @@ src/test/java/lotto/ - [ ] Early return νŒ¨ν„΄μœΌλ‘œ else 제거 - [ ] 맀직 λ„˜λ²„ μƒμˆ˜ν™” - [ ] 도메인 둜직과 UI 둜직 뢄리 +- [ ] ν•¨μˆ˜ 길이 15라인 μ΄ν•˜ μœ μ§€ + +### μ˜ˆμƒ κ³ λ―Ό 포인트 +- Parser와 Validator의 경계 μ„€μ • +- μž¬μ‹œλ„ 둜직의 쀑볡 제거 +- Enum을 ν™œμš©ν•œ λ“±μˆ˜ 및 μƒκΈˆ 관리 +- 일급 μ»¬λ ‰μ…˜ 적용 λ²”μœ„ --- ## πŸ“š ν•™μŠ΅ λ‚΄μš© -- Java Enum ν™œμš© +- Java Enum ν™œμš© (μƒνƒœμ™€ ν–‰μœ„λ₯Ό ν•¨κ»˜ 관리) - 일급 μ»¬λ ‰μ…˜ (First-Class Collection) -- μ˜ˆμ™Έ 처리 및 μž¬μž…λ ₯ 둜직 -- 객체 κ°„ ν˜‘λ ₯ 섀계 -- λ‹¨μœ„ ν…ŒμŠ€νŠΈ μž‘μ„± -- 수읡λ₯  계산 및 ν¬λ§·νŒ… \ No newline at end of file +- μ˜ˆμ™Έ 처리 \ No newline at end of file From 9d5d7abd68e9f0c5bc0f7ebd36649cf519d61011 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 10:02:33 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat(domain):=20Rank=20=EC=97=B4=EA=B1=B0?= =?UTF-8?q?=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/domain/Rank.java | 47 +++++++++++++++ src/test/java/lotto/domain/RankTest.java | 77 ++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/main/java/lotto/domain/Rank.java create mode 100644 src/test/java/lotto/domain/RankTest.java diff --git a/src/main/java/lotto/domain/Rank.java b/src/main/java/lotto/domain/Rank.java new file mode 100644 index 0000000000..4bddef80ae --- /dev/null +++ b/src/main/java/lotto/domain/Rank.java @@ -0,0 +1,47 @@ +package lotto.domain; + +import java.util.Arrays; + +public enum Rank { + FIRST(6, false, 2_000_000_000, "6개 일치"), + SECOND(5, true, 30_000_000, "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치"), + THIRD(5, false, 1_500_000, "5개 일치"), + FOURTH(4, false, 50_000, "4개 일치"), + FIFTH(3, false, 5_000, "3개 일치"), + NONE(0, false, 0, ""); + + private final int matchCount; + private final boolean matchBonus; + private final int prize; + private final String description; + + Rank(int matchCount, boolean matchBonus, int prize, String description) { + this.matchCount = matchCount; + this.matchBonus = matchBonus; + this.prize = prize; + this.description = description; + } + + public static Rank valueOf(int matchCount, boolean matchBonus) { + if (matchCount == 5 && matchBonus) { + return SECOND; + } + return Arrays.stream(values()) + .filter(rank -> rank.matchCount == matchCount) + .filter(rank -> !rank.matchBonus) + .findFirst() + .orElse(NONE); + } + + public int getPrize() { + return prize; + } + + public String getDescription() { + return description; + } + + public boolean isWinning() { + return this != NONE; + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/RankTest.java b/src/test/java/lotto/domain/RankTest.java new file mode 100644 index 0000000000..29e91688fe --- /dev/null +++ b/src/test/java/lotto/domain/RankTest.java @@ -0,0 +1,77 @@ +package lotto.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 RankTest { + + @DisplayName("1: 6개 μΌμΉ˜ν•˜λ©΄ 1λ“±") + @Test + void test1MatchSixNumbers() { + Rank rank = Rank.valueOf(6, false); + + assertThat(rank).isEqualTo(Rank.FIRST); + assertThat(rank.getPrize()).isEqualTo(2_000_000_000); + } + + @DisplayName("2: 5개 μΌμΉ˜ν•˜κ³  λ³΄λ„ˆμŠ€λ³Ό μΌμΉ˜ν•˜λ©΄ 2λ“±") + @Test + void test2MatchFiveNumbersWithBonus() { + Rank rank = Rank.valueOf(5, true); + + assertThat(rank).isEqualTo(Rank.SECOND); + assertThat(rank.getPrize()).isEqualTo(30_000_000); + } + + @DisplayName("3: 5개 μΌμΉ˜ν•˜κ³  λ³΄λ„ˆμŠ€λ³Ό λΆˆμΌμΉ˜ν•˜λ©΄ 3λ“±") + @Test + void test3MatchFiveNumbersWithoutBonus() { + Rank rank = Rank.valueOf(5, false); + + assertThat(rank).isEqualTo(Rank.THIRD); + assertThat(rank.getPrize()).isEqualTo(1_500_000); + } + + @DisplayName("4: 4개 μΌμΉ˜ν•˜λ©΄ 4λ“±") + @Test + void test4MatchFourNumbers() { + Rank rank = Rank.valueOf(4, false); + + assertThat(rank).isEqualTo(Rank.FOURTH); + assertThat(rank.getPrize()).isEqualTo(50_000); + } + + @DisplayName("5: 3개 μΌμΉ˜ν•˜λ©΄ 5λ“±") + @Test + void test5MatchThreeNumbers() { + Rank rank = Rank.valueOf(3, false); + + assertThat(rank).isEqualTo(Rank.FIFTH); + assertThat(rank.getPrize()).isEqualTo(5_000); + } + + @DisplayName("6: 2개 μ΄ν•˜ μΌμΉ˜ν•˜λ©΄ NONE") + @ParameterizedTest + @CsvSource({"0, false", "1, false", "2, false"}) + void test6MatchLessThanThree(int matchCount, boolean matchBonus) { + Rank rank = Rank.valueOf(matchCount, matchBonus); + + assertThat(rank).isEqualTo(Rank.NONE); + assertThat(rank.getPrize()).isEqualTo(0); + } + + @DisplayName("7: 당첨 μ—¬λΆ€ 확인") + @Test + void test7IsWinning() { + assertThat(Rank.FIRST.isWinning()).isTrue(); + assertThat(Rank.SECOND.isWinning()).isTrue(); + assertThat(Rank.THIRD.isWinning()).isTrue(); + assertThat(Rank.FOURTH.isWinning()).isTrue(); + assertThat(Rank.FIFTH.isWinning()).isTrue(); + assertThat(Rank.NONE.isWinning()).isFalse(); + } +} \ No newline at end of file From 8a330269c78f49e127b369cd89a7c770855ed058 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 21:25:34 +0900 Subject: [PATCH 04/21] =?UTF-8?q?feat(domain):=20Lotto=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EA=B8=B0=EB=B3=B8=20=EA=B8=B0=EB=8A=A5=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 --- README.md | 30 ++-- src/main/java/lotto/domain/Lotto.java | 68 ++++++++ src/main/java/lotto/domain/WinningLotto.java | 56 +++++++ src/test/java/lotto/LottoTest.java | 25 --- src/test/java/lotto/domain/LottoTest.java | 147 ++++++++++++++++++ src/test/java/lotto/domain/RankTest.java | 14 +- .../java/lotto/domain/WinningLottoTest.java | 134 ++++++++++++++++ 7 files changed, 427 insertions(+), 47 deletions(-) create mode 100644 src/main/java/lotto/domain/Lotto.java create mode 100644 src/main/java/lotto/domain/WinningLotto.java delete mode 100644 src/test/java/lotto/LottoTest.java create mode 100644 src/test/java/lotto/domain/LottoTest.java create mode 100644 src/test/java/lotto/domain/WinningLottoTest.java diff --git a/README.md b/README.md index 91ffab6bb2..bc6eeef8c3 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,21 @@ src/main/java/lotto/ ### 1. 도메인 객체 κ΅¬ν˜„ -#### 1.1 Lotto 객체 κ΅¬ν˜„ +#### 1.2 Rank μ—΄κ±°ν˜• κ΅¬ν˜„ + +**정상 μΌ€μ΄μŠ€:** +- [ ] 일치 κ°œμˆ˜μ™€ λ³΄λ„ˆμŠ€ 일치 μ—¬λΆ€λ‘œ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ +- [ ] 각 λ“±μˆ˜λ³„ μƒκΈˆμ„ λ°˜ν™˜ν•œλ‹€ +- [ ] 5λ“±: 3개 일치 / 5,000원 +- [ ] 4λ“±: 4개 일치 / 50,000원 +- [ ] 3λ“±: 5개 일치 / 1,500,000원 +- [ ] 2λ“±: 5개 + λ³΄λ„ˆμŠ€ 일치 / 30,000,000원 +- [ ] 1λ“±: 6개 일치 / 2,000,000,000원 +- [ ] λ‹Ήμ²¨λ˜μ§€ μ•ŠμœΌλ©΄ NONE λ°˜ν™˜ + +**컀밋:** `feat(domain): Rank μ—΄κ±°ν˜• κ΅¬ν˜„` + +#### 1.2 Lotto 객체 κ΅¬ν˜„ **정상 μΌ€μ΄μŠ€:** - [ ] 6개의 둜또 번호λ₯Ό μ €μž₯ν•œλ‹€ @@ -123,20 +137,6 @@ src/main/java/lotto/ **컀밋:** `feat(domain): Lotto 객체 κΈ°λ³Έ κΈ°λŠ₯ κ΅¬ν˜„` -#### 1.2 Rank μ—΄κ±°ν˜• κ΅¬ν˜„ - -**정상 μΌ€μ΄μŠ€:** -- [ ] 일치 κ°œμˆ˜μ™€ λ³΄λ„ˆμŠ€ 일치 μ—¬λΆ€λ‘œ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ -- [ ] 각 λ“±μˆ˜λ³„ μƒκΈˆμ„ λ°˜ν™˜ν•œλ‹€ -- [ ] 5λ“±: 3개 일치 / 5,000원 -- [ ] 4λ“±: 4개 일치 / 50,000원 -- [ ] 3λ“±: 5개 일치 / 1,500,000원 -- [ ] 2λ“±: 5개 + λ³΄λ„ˆμŠ€ 일치 / 30,000,000원 -- [ ] 1λ“±: 6개 일치 / 2,000,000,000원 -- [ ] λ‹Ήμ²¨λ˜μ§€ μ•ŠμœΌλ©΄ NONE λ°˜ν™˜ - -**컀밋:** `feat(domain): Rank μ—΄κ±°ν˜• κ΅¬ν˜„` - #### 1.3 WinningLotto 객체 κ΅¬ν˜„ **정상 μΌ€μ΄μŠ€:** diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java new file mode 100644 index 0000000000..2d5dc54560 --- /dev/null +++ b/src/main/java/lotto/domain/Lotto.java @@ -0,0 +1,68 @@ +package lotto.domain; + +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +public class Lotto { + private static final int LOTTO_NUMBER_COUNT = 6; + private static final int MIN_LOTTO_NUMBER = 1; + private static final int MAX_LOTTO_NUMBER = 45; + + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + this.numbers = numbers.stream() + .sorted() + .collect(Collectors.toList()); + } + + private void validate(List numbers) { + validateNotNull(numbers); + validateSize(numbers); + validateDuplicate(numbers); + validateRange(numbers); + } + + private void validateNotNull(List numbers) { + if (numbers == null) { + throw new IllegalArgumentException("[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + } + + private void validateSize(List numbers) { + if (numbers.size() != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } + + private void validateDuplicate(List numbers) { + if (numbers.size() != new HashSet<>(numbers).size()) { + throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } + + private void validateRange(List numbers) { + boolean isOutOfRange = numbers.stream() + .anyMatch(number -> number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER); + + if (isOutOfRange) { + throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } + + public List getNumbers() { + return numbers; + } + + public int countMatches(List winningNumbers) { + return (int) numbers.stream() + .filter(winningNumbers::contains) + .count(); + } + + public boolean contains(int number) { + return numbers.contains(number); + } +} \ No newline at end of file diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java new file mode 100644 index 0000000000..c38ccaf75b --- /dev/null +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -0,0 +1,56 @@ +package lotto.domain; + +import java.util.List; +import java.util.Objects; + +public class WinningLotto { + private static final int MIN_LOTTO_NUMBER = 1; + private static final int MAX_LOTTO_NUMBER = 45; + private final Lotto winningNumbers; + private final int bonusNumber; + + public WinningLotto(Lotto winningNumbers, int bonusNumber) { + validate(winningNumbers, bonusNumber); + this.winningNumbers = winningNumbers; + this.bonusNumber = bonusNumber; + } + + private void validate(Lotto winningNumbers, int bonusNumber) { + validateNotNull(winningNumbers); + validateBonusNumberRange(bonusNumber); + validateDuplicateWithWinningNumbers(winningNumbers, bonusNumber); + } + + private void validateNotNull(Lotto winningNumbers) { + if (Objects.isNull(winningNumbers)) { + throw new IllegalArgumentException("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + } + + private void validateBonusNumberRange(int bonusNumber) { + if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) { + throw new IllegalArgumentException("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } + + private void validateDuplicateWithWinningNumbers(Lotto winningNumbers, int bonusNumber) { + if (winningNumbers.contains(bonusNumber)) { + throw new IllegalArgumentException("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + } + + /** + * κ΅¬λ§€ν•œ λ‘œλ˜μ™€ 당첨 번호λ₯Ό λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. + */ + public Rank match(Lotto userLotto) { + // 1. μΌμΉ˜ν•˜λŠ” 번호 개수 계산 + List numbers = winningNumbers.getNumbers(); + int matchCount = userLotto.countMatches(numbers); + + // 2. λ³΄λ„ˆμŠ€ 번호 일치 μ—¬λΆ€ 확인 + boolean matchBonus = userLotto.contains(bonusNumber); + + // 3. Rank Enum을 톡해 λ“±μˆ˜ νŒλ³„ + return Rank.valueOf(matchCount, matchBonus); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java deleted file mode 100644 index 309f4e50ae..0000000000 --- a/src/test/java/lotto/LottoTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package lotto; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class LottoTest { - @Test - void 둜또_번호의_κ°œμˆ˜κ°€_6κ°œκ°€_λ„˜μ–΄κ°€λ©΄_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { - assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 6, 7))) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("둜또 λ²ˆν˜Έμ— μ€‘λ³΅λœ μˆ«μžκ°€ 있으면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€.") - @Test - void 둜또_λ²ˆν˜Έμ—_μ€‘λ³΅λœ_μˆ«μžκ°€_있으면_μ˜ˆμ™Έκ°€_λ°œμƒν•œλ‹€() { - assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5))) - .isInstanceOf(IllegalArgumentException.class); - } - - // TODO: μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„μ— λ”°λ₯Έ ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± -} diff --git a/src/test/java/lotto/domain/LottoTest.java b/src/test/java/lotto/domain/LottoTest.java new file mode 100644 index 0000000000..b13319038a --- /dev/null +++ b/src/test/java/lotto/domain/LottoTest.java @@ -0,0 +1,147 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LottoTest { + + @DisplayName("정상1: 둜또 번호 6개λ₯Ό μ €μž₯ν•œλ‹€") + @Test + void test1() { + List numbers = List.of(1, 2, 3, 4, 5, 6); + + Lotto lotto = new Lotto(numbers); // 전체 경둜 제거 + assertThat(lotto.getNumbers()).hasSize(6); + assertThat(lotto.getNumbers()).containsExactly(1, 2, 3, 4, 5, 6); + } + + @DisplayName("정상2: 둜또 λ²ˆν˜ΈλŠ” 1~45 λ²”μœ„μ˜ μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” μˆ«μžμ΄λ‹€") + @Test + void test2() { + List numbers = List.of(1, 15, 23, 32, 41, 45); + + Lotto lotto = new Lotto(numbers); + assertThat(lotto.getNumbers()).allMatch(num -> num >= 1 && num <= 45); + } + + @DisplayName("정상3: 둜또 번호λ₯Ό μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•˜μ—¬ λ°˜ν™˜ν•œλ‹€") + @Test + void test3() { + List numbers = List.of(45, 1, 32, 15, 23, 8); + + Lotto lotto = new Lotto(numbers); + assertThat(lotto.getNumbers()).containsExactly(1, 8, 15, 23, 32, 45); + } + + @DisplayName("정상4: μ£Όμ–΄μ§„ 번호 λ¦¬μŠ€νŠΈμ™€ μΌμΉ˜ν•˜λŠ” 개수λ₯Ό λ°˜ν™˜ν•œλ‹€") + @Test + void test4() { + Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + List winningNumbers = List.of(1, 2, 3, 7, 8, 9); + + int matchCount = lotto.countMatches(winningNumbers); + assertThat(matchCount).isEqualTo(3); + } + + @DisplayName("정상5: μΌμΉ˜ν•˜λŠ” λ²ˆν˜Έκ°€ 없을 λ•Œ 0을 λ°˜ν™˜ν•œλ‹€") + @Test + void test5() { + Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + List winningNumbers = List.of(7, 8, 9, 10, 11, 12); + + int matchCount = lotto.countMatches(winningNumbers); + assertThat(matchCount).isEqualTo(0); + } + + @DisplayName("정상6: λͺ¨λ“  λ²ˆν˜Έκ°€ μΌμΉ˜ν•  λ•Œ 6을 λ°˜ν™˜ν•œλ‹€") + @Test + void test6() { + Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + List winningNumbers = List.of(1, 2, 3, 4, 5, 6); + + int matchCount = lotto.countMatches(winningNumbers); + assertThat(matchCount).isEqualTo(6); + } + + @DisplayName("정상7: νŠΉμ • 번호 포함 μ—¬λΆ€λ₯Ό ν™•μΈν•œλ‹€ - ν¬ν•¨ν•˜λŠ” 경우") + @Test + void test7() { + Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + + assertThat(lotto.contains(1)).isTrue(); + assertThat(lotto.contains(3)).isTrue(); + assertThat(lotto.contains(6)).isTrue(); + } + + @DisplayName("정상8: νŠΉμ • 번호 포함 μ—¬λΆ€λ₯Ό ν™•μΈν•œλ‹€ - ν¬ν•¨ν•˜μ§€ μ•ŠλŠ” 경우") + @Test + void test8() { + Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + + assertThat(lotto.contains(7)).isFalse(); + assertThat(lotto.contains(45)).isFalse(); + } + + @DisplayName("μ˜ˆμ™Έ1: 둜또 λ²ˆν˜Έκ°€ null이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test9() { + assertThatThrownBy(() -> new Lotto(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR]"); + } + + @DisplayName("μ˜ˆμ™Έ2: 둜또 번호의 κ°œμˆ˜κ°€ 6κ°œκ°€ λ„˜μ–΄κ°€λ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test10() { + assertThatThrownBy(() -> new Lotto(List.of(1,2,3,4,5,6,7))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ3: 둜또 번호의 κ°œμˆ˜κ°€ 6개 미만이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test11() { + assertThatThrownBy(() -> new Lotto(List.of(1,2,3,4,5))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @Tag("error") + @DisplayName("μ˜ˆμ™Έ4: 둜또 λ²ˆν˜Έμ— μ€‘λ³΅λœ μˆ«μžκ°€ 있으면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test12() { + assertThatThrownBy(() -> new Lotto(List.of(1,2,3,4,5,5))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ5: 둜또 λ²ˆν˜Έκ°€ 1보닀 μž‘μœΌλ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test13() { + assertThatThrownBy(() -> new Lotto(List.of(0,1,2,3,4,5))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ6: 둜또 λ²ˆν˜Έκ°€ 45보닀 크면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test14() { + assertThatThrownBy(() -> new Lotto(List.of(1,2,3,4,5,46))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ7: 둜또 λ²ˆν˜Έκ°€ 음수이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test15() { + assertThatThrownBy(() -> new Lotto(List.of(-1,1,2,3,4,5))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } +} diff --git a/src/test/java/lotto/domain/RankTest.java b/src/test/java/lotto/domain/RankTest.java index 29e91688fe..3979fb8e3f 100644 --- a/src/test/java/lotto/domain/RankTest.java +++ b/src/test/java/lotto/domain/RankTest.java @@ -11,7 +11,7 @@ class RankTest { @DisplayName("1: 6개 μΌμΉ˜ν•˜λ©΄ 1λ“±") @Test - void test1MatchSixNumbers() { + void test1() { Rank rank = Rank.valueOf(6, false); assertThat(rank).isEqualTo(Rank.FIRST); @@ -20,7 +20,7 @@ void test1MatchSixNumbers() { @DisplayName("2: 5개 μΌμΉ˜ν•˜κ³  λ³΄λ„ˆμŠ€λ³Ό μΌμΉ˜ν•˜λ©΄ 2λ“±") @Test - void test2MatchFiveNumbersWithBonus() { + void test2() { Rank rank = Rank.valueOf(5, true); assertThat(rank).isEqualTo(Rank.SECOND); @@ -29,7 +29,7 @@ void test2MatchFiveNumbersWithBonus() { @DisplayName("3: 5개 μΌμΉ˜ν•˜κ³  λ³΄λ„ˆμŠ€λ³Ό λΆˆμΌμΉ˜ν•˜λ©΄ 3λ“±") @Test - void test3MatchFiveNumbersWithoutBonus() { + void test3() { Rank rank = Rank.valueOf(5, false); assertThat(rank).isEqualTo(Rank.THIRD); @@ -38,7 +38,7 @@ void test3MatchFiveNumbersWithoutBonus() { @DisplayName("4: 4개 μΌμΉ˜ν•˜λ©΄ 4λ“±") @Test - void test4MatchFourNumbers() { + void test4() { Rank rank = Rank.valueOf(4, false); assertThat(rank).isEqualTo(Rank.FOURTH); @@ -47,7 +47,7 @@ void test4MatchFourNumbers() { @DisplayName("5: 3개 μΌμΉ˜ν•˜λ©΄ 5λ“±") @Test - void test5MatchThreeNumbers() { + void test5() { Rank rank = Rank.valueOf(3, false); assertThat(rank).isEqualTo(Rank.FIFTH); @@ -57,7 +57,7 @@ void test5MatchThreeNumbers() { @DisplayName("6: 2개 μ΄ν•˜ μΌμΉ˜ν•˜λ©΄ NONE") @ParameterizedTest @CsvSource({"0, false", "1, false", "2, false"}) - void test6MatchLessThanThree(int matchCount, boolean matchBonus) { + void test6(int matchCount, boolean matchBonus) { Rank rank = Rank.valueOf(matchCount, matchBonus); assertThat(rank).isEqualTo(Rank.NONE); @@ -66,7 +66,7 @@ void test6MatchLessThanThree(int matchCount, boolean matchBonus) { @DisplayName("7: 당첨 μ—¬λΆ€ 확인") @Test - void test7IsWinning() { + void test7() { assertThat(Rank.FIRST.isWinning()).isTrue(); assertThat(Rank.SECOND.isWinning()).isTrue(); assertThat(Rank.THIRD.isWinning()).isTrue(); diff --git a/src/test/java/lotto/domain/WinningLottoTest.java b/src/test/java/lotto/domain/WinningLottoTest.java new file mode 100644 index 0000000000..ae043781ae --- /dev/null +++ b/src/test/java/lotto/domain/WinningLottoTest.java @@ -0,0 +1,134 @@ +package lotto.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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class WinningLottoTest { + + private final Lotto validWinningLottoNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + + @DisplayName("정상1: 당첨 번호 6κ°œμ™€ λ³΄λ„ˆμŠ€ 번호 1개λ₯Ό μ €μž₯ν•œλ‹€") + @Test + void test1() { + // Given + int bonusNumber = 7; + + // When + WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, bonusNumber); + + // Then (λ‚΄λΆ€ ν•„λ“œμ— λŒ€ν•œ 직접적인 getterλŠ” μ—†μ§€λ§Œ, 검증 둜직 톡과 μ—¬λΆ€λ‘œ 확인) + assertThat(winningLotto).isNotNull(); + } + + @DisplayName("정상2: λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ - 1λ“±") + @Test + void test2() { + // Given: 1, 2, 3, 4, 5, 6 (당첨 번호), 7 (λ³΄λ„ˆμŠ€) + WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, 7); + // When: 1, 2, 3, 4, 5, 6 (6개 일치) + Lotto userLotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + + // Then + assertThat(winningLotto.match(userLotto)).isEqualTo(Rank.FIRST); + } + + @DisplayName("정상3: λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ - 2λ“±") + @Test + void test3() { + // Given: 1, 2, 3, 4, 5, 6 (당첨 번호), 7 (λ³΄λ„ˆμŠ€) + WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, 7); + // When: 1, 2, 3, 4, 5, 7 (5개 일치, λ³΄λ„ˆμŠ€ 일치) + Lotto userLotto = new Lotto(List.of(1, 2, 3, 4, 5, 7)); + + // Then + assertThat(winningLotto.match(userLotto)).isEqualTo(Rank.SECOND); + } + + @DisplayName("정상4: λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ - 3λ“±") + @Test + void test4() { + // Given: 1, 2, 3, 4, 5, 6 (당첨 번호), 7 (λ³΄λ„ˆμŠ€) + WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, 7); + // When: 1, 2, 3, 4, 5, 8 (5개 일치, λ³΄λ„ˆμŠ€ 뢈일치) + Lotto userLotto = new Lotto(List.of(1, 2, 3, 4, 5, 8)); + + // Then + assertThat(winningLotto.match(userLotto)).isEqualTo(Rank.THIRD); + } + + @DisplayName("정상5: λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ - 5λ“±") + @Test + void test5() { + // Given: 1, 2, 3, 4, 5, 6 (당첨 번호), 7 (λ³΄λ„ˆμŠ€) + WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, 7); + // When: 1, 2, 3, 8, 9, 10 (3개 일치) + Lotto userLotto = new Lotto(List.of(1, 2, 3, 8, 9, 10)); + + // Then + assertThat(winningLotto.match(userLotto)).isEqualTo(Rank.FIFTH); + } + + @DisplayName("정상6: λ‘œλ˜μ™€ λΉ„κ΅ν•˜μ—¬ λ“±μˆ˜λ₯Ό νŒλ³„ν•œλ‹€ - 꽝 (NONE)") + @ParameterizedTest(name = "{0}개 일치 μ‹œ NONE") + @CsvSource({"0", "1", "2"}) + void test6(int matchCount) { + // Given: 1, 2, 3, 4, 5, 6 (당첨 번호), 7 (λ³΄λ„ˆμŠ€) + WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, 7); + + // When: matchCount에 맞게 둜또 생성 (λ³΄λ„ˆμŠ€ λ²ˆν˜Έμ™€λ„ λΆˆμΌμΉ˜ν•˜κ²Œ) + List lottoNumbers = List.of(1, 2, 3, 4, 5, 6).subList(0, matchCount); + lottoNumbers = new java.util.ArrayList<>(lottoNumbers); + // λ‚˜λ¨Έμ§€ 번호λ₯Ό 7, 8, ...둜 μ±„μ›Œμ„œ λ³΄λ„ˆμŠ€ 번호(7)와도 κ²ΉμΉ˜μ§€ μ•Šκ²Œ 함 + for (int i = 0; lottoNumbers.size() < 6; i++) { + lottoNumbers.add(8 + i); + } + Lotto userLotto = new Lotto(lottoNumbers); + + // Then + assertThat(winningLotto.match(userLotto)).isEqualTo(Rank.NONE); + } + + // μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ + @DisplayName("μ˜ˆμ™Έ1: 당첨 λ²ˆν˜Έκ°€ null이면 IllegalArgumentException λ°œμƒ") + @Test + void test7() { + assertThatThrownBy(() -> new WinningLotto(null, 7)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @DisplayName("μ˜ˆμ™Έ2: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ IllegalArgumentException λ°œμƒ") + @Test + void test8() { + // 당첨 번호: 1, 2, 3, 4, 5, 6 + int bonusNumber = 6; // 쀑볡 + assertThatThrownBy(() -> new WinningLotto(validWinningLottoNumbers, bonusNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ3: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1보닀 μž‘μœΌλ©΄ IllegalArgumentException λ°œμƒ") + @Test + void test9() { + int bonusNumber = 0; + assertThatThrownBy(() -> new WinningLotto(validWinningLottoNumbers, bonusNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ4: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 45보닀 크면 IllegalArgumentException λ°œμƒ") + @Test + void test10() { + int bonusNumber = 46; + assertThatThrownBy(() -> new WinningLotto(validWinningLottoNumbers, bonusNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } +} \ No newline at end of file From 84c4e5ac87b74870b7613adc7124ebb96cf3d676 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 21:46:35 +0900 Subject: [PATCH 05/21] =?UTF-8?q?feat(domain):=20LottoTickets=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/domain/LottoTickets.java | 48 +++++++++++ .../java/lotto/domain/LottoTicketsTest.java | 79 +++++++++++++++++++ .../java/lotto/domain/WinningLottoTest.java | 4 +- 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/main/java/lotto/domain/LottoTickets.java create mode 100644 src/test/java/lotto/domain/LottoTicketsTest.java diff --git a/src/main/java/lotto/domain/LottoTickets.java b/src/main/java/lotto/domain/LottoTickets.java new file mode 100644 index 0000000000..3540e446c0 --- /dev/null +++ b/src/main/java/lotto/domain/LottoTickets.java @@ -0,0 +1,48 @@ +package lotto.domain; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class LottoTickets { + private static final int MIN_LOTTO_COUNT = 1; + + private final List lottos; + + public LottoTickets(List lottos) { + if (Objects.isNull(lottos)) { + throw new IllegalArgumentException("[ERROR] 둜또 λͺ©λ‘μ΄ μ—†μŠ΅λ‹ˆλ‹€."); + } + + if (lottos.size() < MIN_LOTTO_COUNT) { + throw new IllegalArgumentException("[ERROR] μ΅œμ†Œ 1개 μ΄μƒμ˜ 둜또λ₯Ό ꡬ맀해야 ν•©λ‹ˆλ‹€."); + } + + this.lottos = lottos; + } + + /** + * κ΅¬λ§€ν•œ 둜또의 개수λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. + */ + public int getLottoCount() { + return lottos.size(); + } + + /** + * λͺ¨λ“  둜또λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. + */ + public List getLottos() { + return Collections.unmodifiableList(lottos); + } + + // public LottoResult compareWithWinningLotto(WinningLotto winningLotto, int purchaseAmount) { + // // λͺ¨λ“  둜또의 당첨 λ“±μˆ˜λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€. + // List ranks = lottos.stream() + // .map(winningLotto::match) + // .collect(Collectors.toList()); + // + // // LottoResult 객체λ₯Ό μƒμ„±ν•˜μ—¬ λ“±μˆ˜λ³„ 개수 및 수읡λ₯ μ„ κ΄€λ¦¬ν•©λ‹ˆλ‹€. + // return new LottoResult(ranks, purchaseAmount); + // } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoTicketsTest.java b/src/test/java/lotto/domain/LottoTicketsTest.java new file mode 100644 index 0000000000..119034c94a --- /dev/null +++ b/src/test/java/lotto/domain/LottoTicketsTest.java @@ -0,0 +1,79 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LottoTicketsTest { + + private final Lotto lotto1 = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + private final Lotto lotto2 = new Lotto(List.of(7, 8, 9, 10, 11, 12)); + private final List validLottoList = List.of(lotto1, lotto2); + + @DisplayName("정상1: μ—¬λŸ¬ 개의 둜또λ₯Ό κ΄€λ¦¬ν•˜λŠ” 객체λ₯Ό μƒμ„±ν•œλ‹€") + @Test + void test1() { + // When + LottoTickets lottoTickets = new LottoTickets(validLottoList); + + // Then + assertThat(lottoTickets).isNotNull(); + assertThat(lottoTickets.getLottoCount()).isEqualTo(2); + assertThat(lottoTickets.getLottos()).containsExactly(lotto1, lotto2); + } + + @DisplayName("정상2: κ΅¬λ§€ν•œ 둜또 개수λ₯Ό μ •ν™•νžˆ λ°˜ν™˜ν•œλ‹€") + @Test + void test2() { + // When + LottoTickets lottoTickets = new LottoTickets(validLottoList); + + // Then + assertThat(lottoTickets.getLottoCount()).isEqualTo(2); + } + + // @DisplayName("정상3: 당첨 λ²ˆν˜Έμ™€ λΉ„κ΅ν•˜μ—¬ 각 둜또의 λ“±μˆ˜λ₯Ό νŒλ³„ν•˜κ³  LottoResultλ₯Ό λ°˜ν™˜ν•œλ‹€") + // @Test + // void test3() { + // // Given + // // lotto1: 1, 2, 3, 4, 5, 6 (당첨 λ²ˆν˜Έμ™€ 6개 일치 -> 1λ“±) + // // lotto2: 7, 8, 9, 10, 11, 12 (당첨 λ²ˆν˜Έμ™€ 0개 일치 -> 꽝) + // LottoTickets lottoTickets = new LottoTickets(validLottoList); + // WinningLotto winningLotto = new WinningLotto(lotto1, 7); // 1~6 당첨, λ³΄λ„ˆμŠ€ 7 + // int purchaseAmount = 2000; + // + // // When + // LottoResult result = lottoTickets.compareWithWinningLotto(winningLotto, purchaseAmount); + // + // // Then (LottoResult의 λ‚΄λΆ€ κ΅¬ν˜„μ— 따라 검증) + // // 1λ“± 1개, 꽝 1개 μ˜ˆμƒ + // assertThat(result.getPrizeCount(Rank.FIRST)).isEqualTo(1); + // assertThat(result.getPrizeCount(Rank.NONE)).isEqualTo(1); + // + // // 주의: LottoResult ν΄λž˜μŠ€κ°€ 아직 κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ, + // // 이 ν…ŒμŠ€νŠΈλŠ” LottoResult κ΅¬ν˜„ ν›„ μ„±κ³΅μ μœΌλ‘œ λΉŒλ“œλ  수 μžˆμŠ΅λ‹ˆλ‹€. + // // ν˜„μž¬λŠ” LottoResult 객체가 μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜λŠ”μ§€λ§Œ ν™•μΈν•©λ‹ˆλ‹€. + // assertThat(result).isInstanceOf(LottoResult.class); + // } + + @DisplayName("μ˜ˆμ™Έ1: 둜또 λ¦¬μŠ€νŠΈκ°€ null이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test4() { + assertThatThrownBy(() -> new LottoTickets(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λͺ©λ‘μ΄ μ—†μŠ΅λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ2: 둜또 λ¦¬μŠ€νŠΈκ°€ λΉ„μ–΄μžˆμœΌλ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test5() { + assertThatThrownBy(() -> new LottoTickets(Collections.emptyList())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] μ΅œμ†Œ 1개 μ΄μƒμ˜ 둜또λ₯Ό ꡬ맀해야 ν•©λ‹ˆλ‹€."); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/WinningLottoTest.java b/src/test/java/lotto/domain/WinningLottoTest.java index ae043781ae..b62c32889f 100644 --- a/src/test/java/lotto/domain/WinningLottoTest.java +++ b/src/test/java/lotto/domain/WinningLottoTest.java @@ -23,7 +23,7 @@ void test1() { // When WinningLotto winningLotto = new WinningLotto(validWinningLottoNumbers, bonusNumber); - // Then (λ‚΄λΆ€ ν•„λ“œμ— λŒ€ν•œ 직접적인 getterλŠ” μ—†μ§€λ§Œ, 검증 둜직 톡과 μ—¬λΆ€λ‘œ 확인) + // Then assertThat(winningLotto).isNotNull(); } @@ -108,7 +108,7 @@ void test7() { @Test void test8() { // 당첨 번호: 1, 2, 3, 4, 5, 6 - int bonusNumber = 6; // 쀑볡 + int bonusNumber = 6; assertThatThrownBy(() -> new WinningLotto(validWinningLottoNumbers, bonusNumber)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); From 6f1840518e79c0ffb89524b95f845de360d361ee Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:05:12 +0900 Subject: [PATCH 06/21] =?UTF-8?q?feat(domain):=20LottoResult=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/domain/LottoResult.java | 60 ++++++++++++++++ .../java/lotto/domain/LottoResultTest.java | 70 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/main/java/lotto/domain/LottoResult.java create mode 100644 src/test/java/lotto/domain/LottoResultTest.java diff --git a/src/main/java/lotto/domain/LottoResult.java b/src/main/java/lotto/domain/LottoResult.java new file mode 100644 index 0000000000..2eca5aa286 --- /dev/null +++ b/src/main/java/lotto/domain/LottoResult.java @@ -0,0 +1,60 @@ +package lotto.domain; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class LottoResult { + private static final int MIN_PURCHASE_AMOUNT = 1; + private static final String PROFIT_RATE_FORMAT = "%.1f"; + + private final Map result; + private final int purchaseAmount; + + public LottoResult(List ranks, int purchaseAmount) { + if (purchaseAmount < MIN_PURCHASE_AMOUNT) { + throw new IllegalArgumentException("[ERROR] ꡬ맀 κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + } + this.purchaseAmount = purchaseAmount; + this.result = countRanks(ranks); + } + + private Map countRanks(List ranks) { + Map counts = new EnumMap<>(Rank.class); + + // NONE λ“±μˆ˜λ₯Ό μ œμ™Έν•œ λͺ¨λ“  당첨 λ“±μˆ˜λ₯Ό 미리 0으둜 μ΄ˆκΈ°ν™” + for (Rank rank : Rank.values()) { + if (rank.isWinning()) { + counts.put(rank, 0); + } + } + + // μ‹€μ œ 당첨 λ“±μˆ˜μ˜ 개수λ₯Ό 카운트 + ranks.stream() + .filter(Rank::isWinning) + .forEach(rank -> counts.merge(rank, 1, Integer::sum)); + + return counts; + } + + public int getPrizeCount(Rank rank) { + return result.getOrDefault(rank, 0); + } + + public long calculateTotalProfit() { + return result.entrySet().stream() + .mapToLong(entry -> (long) entry.getKey().getPrize() * entry.getValue()) + .sum(); + } + + /** + * 총 수읡λ₯ μ„ κ³„μ‚°ν•©λ‹ˆλ‹€. (μ†Œμˆ˜μ  λ‘˜μ§Έ μžλ¦¬μ—μ„œ λ°˜μ˜¬λ¦Όν•˜μ—¬ 첫째 μžλ¦¬κΉŒμ§€ ν‘œμ‹œ) + * 예: 62.5%, 100.0% + */ + public String calculateProfitRate() { + long totalProfit = calculateTotalProfit(); + double profitRate = (double) totalProfit * 100 / purchaseAmount; + + return String.format(PROFIT_RATE_FORMAT, profitRate); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoResultTest.java b/src/test/java/lotto/domain/LottoResultTest.java new file mode 100644 index 0000000000..dd07841aff --- /dev/null +++ b/src/test/java/lotto/domain/LottoResultTest.java @@ -0,0 +1,70 @@ +package lotto.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LottoResultTest { + + @DisplayName("정상1: λ“±μˆ˜λ³„ 당첨 개수λ₯Ό μ •ν™•νžˆ μ €μž₯ν•œλ‹€") + @Test + void test1() { + // Given + List ranks = List.of(Rank.THIRD, Rank.THIRD, Rank.FIFTH, Rank.NONE); + + // When + LottoResult result = new LottoResult(ranks, 4000); + + // Then + assertThat(result.getPrizeCount(Rank.THIRD)).isEqualTo(2); + assertThat(result.getPrizeCount(Rank.FIFTH)).isEqualTo(1); + assertThat(result.getPrizeCount(Rank.FOURTH)).isEqualTo(0); + assertThat(result.getPrizeCount(Rank.NONE)).isEqualTo(0); + } + + @DisplayName("정상2: 총 μˆ˜μ΅κΈˆμ„ μ •ν™•ν•˜κ²Œ κ³„μ‚°ν•œλ‹€") + @Test + void test2() { + // 1λ“± 1개 (20μ–΅) + 3λ“± 1개 (150만) = 2,001,500,000 + LottoResult result = new LottoResult(List.of(Rank.FIRST, Rank.THIRD), 8000); + + long expectedProfit = 2_000_000_000L + 1_500_000L; + + assertThat(result.calculateTotalProfit()).isEqualTo(expectedProfit); + } + + @DisplayName("정상3: 수읡λ₯  계산 (반올림 ν…ŒμŠ€νŠΈ 1) - 66.666..% -> 66.7%") + @Test + void test3() { + // Given: 3000원 ꡬ맀, 2000원 당첨 + int amount = 3000; + int profit = 2000; + + // When + double profitRate = (double) profit * 100 / amount; + String formattedRate = String.format("%.1f", profitRate); + + // Then + assertThat(formattedRate).isEqualTo("66.7"); + } + + @DisplayName("μ˜ˆμ™Έ1: ꡬ맀 κΈˆμ•‘μ΄ 0이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test6() { + assertThatThrownBy(() -> new LottoResult(List.of(Rank.FIFTH), 0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] ꡬ맀 κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ2: ꡬ맀 κΈˆμ•‘μ΄ 음수이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") + @Test + void test7() { + assertThatThrownBy(() -> new LottoResult(List.of(Rank.FIFTH), -1000)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] ꡬ맀 κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + } +} \ No newline at end of file From cc1c7f4f5a68b42cd3af03dbfd8154caa7e88b15 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:21:58 +0900 Subject: [PATCH 07/21] =?UTF-8?q?feat(parser):=20=EA=B5=AC=EB=A7=A4=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=20=ED=8C=8C=EC=8B=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/parser/PurchaseAmountParser.java | 21 +++++++ .../parser/PurchaseAmountParserTest.java | 56 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/main/java/lotto/parser/PurchaseAmountParser.java create mode 100644 src/test/java/lotto/parser/PurchaseAmountParserTest.java diff --git a/src/main/java/lotto/parser/PurchaseAmountParser.java b/src/main/java/lotto/parser/PurchaseAmountParser.java new file mode 100644 index 0000000000..d47dd6fba7 --- /dev/null +++ b/src/main/java/lotto/parser/PurchaseAmountParser.java @@ -0,0 +1,21 @@ +package lotto.parser; + +import java.util.Objects; + +public class PurchaseAmountParser { + + public static int parse(String input) { + if (input == null || input.trim().isEmpty()) { + throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + return parseToInteger(input); + } + + private static int parseToInteger(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new NumberFormatException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } +} \ No newline at end of file diff --git a/src/test/java/lotto/parser/PurchaseAmountParserTest.java b/src/test/java/lotto/parser/PurchaseAmountParserTest.java new file mode 100644 index 0000000000..9200c6652e --- /dev/null +++ b/src/test/java/lotto/parser/PurchaseAmountParserTest.java @@ -0,0 +1,56 @@ +package lotto.parser; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +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; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PurchaseAmountParserTest { + + @DisplayName("정상1: μœ νš¨ν•œ λ¬Έμžμ—΄ κΈˆμ•‘μ„ μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") + @Test + void test1() { + // Given + String input = "8000"; + + // When + int result = PurchaseAmountParser.parse(input); + + // Then + assertThat(result).isEqualTo(8000); + } + + @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @Test + void test2() { + String input = null; + + assertThatThrownBy(() -> PurchaseAmountParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @Tag("error") + @DisplayName("μ˜ˆμ™Έ2: μž…λ ₯이 빈 λ¬Έμžμ—΄ λ˜λŠ” 곡백이면 IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"", " "}) + void test3(String input) { + assertThatThrownBy(() -> PurchaseAmountParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @Tag("error") + @DisplayName("μ˜ˆμ™Έ3: μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜λ©΄ NumberFormatException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"1000원", "a1000", "1,000"}) + void test4(String input) { + assertThatThrownBy(() -> PurchaseAmountParser.parse(input)) + .isInstanceOf(NumberFormatException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } +} \ No newline at end of file From 9194306ff545f22a50599f76f4ea32bbb8aadd8f Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:23:22 +0900 Subject: [PATCH 08/21] =?UTF-8?q?feat(parser):=20=EA=B5=AC=EB=A7=A4=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=20=ED=8C=8C=EC=8B=B1=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?trim()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/parser/PurchaseAmountParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/lotto/parser/PurchaseAmountParser.java b/src/main/java/lotto/parser/PurchaseAmountParser.java index d47dd6fba7..720075c090 100644 --- a/src/main/java/lotto/parser/PurchaseAmountParser.java +++ b/src/main/java/lotto/parser/PurchaseAmountParser.java @@ -8,7 +8,7 @@ public static int parse(String input) { if (input == null || input.trim().isEmpty()) { throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); } - return parseToInteger(input); + return parseToInteger(input.trim()); } private static int parseToInteger(String input) { From e655f23647d835154ad5b06972037f8648cf2df6 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:23:45 +0900 Subject: [PATCH 09/21] =?UTF-8?q?feat(parser):=20=EA=B5=AC=EB=A7=A4=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=20=ED=8C=8C=EC=8B=B1=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20import=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/parser/PurchaseAmountParser.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/lotto/parser/PurchaseAmountParser.java b/src/main/java/lotto/parser/PurchaseAmountParser.java index 720075c090..0ffeb12f29 100644 --- a/src/main/java/lotto/parser/PurchaseAmountParser.java +++ b/src/main/java/lotto/parser/PurchaseAmountParser.java @@ -1,7 +1,5 @@ package lotto.parser; -import java.util.Objects; - public class PurchaseAmountParser { public static int parse(String input) { From 6db568b2244a755bb2ec9e9e1cd4e9ed7ce766df Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:35:18 +0900 Subject: [PATCH 10/21] =?UTF-8?q?feat(parser):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=8C=8C=EC=8B=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/parser/WinningNumbersParser.java | 53 +++++++++++++ .../parser/WinningNumbersParserTest.java | 76 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/main/java/lotto/parser/WinningNumbersParser.java create mode 100644 src/test/java/lotto/parser/WinningNumbersParserTest.java diff --git a/src/main/java/lotto/parser/WinningNumbersParser.java b/src/main/java/lotto/parser/WinningNumbersParser.java new file mode 100644 index 0000000000..c578b87198 --- /dev/null +++ b/src/main/java/lotto/parser/WinningNumbersParser.java @@ -0,0 +1,53 @@ +package lotto.parser; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class WinningNumbersParser { + private static final String SEPARATOR = ","; + + /** + * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(당첨 번호)을 μ •μˆ˜ 리슀트둜 νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. + * + * @param input μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 당첨 번호 λ¬Έμžμ—΄ (μ‰Όν‘œλ‘œ ꡬ뢄됨) + * @return νŒŒμ‹±λœ 당첨 번호 리슀트 (μ •μˆ˜) + * @throws IllegalArgumentException μž…λ ₯이 null, 빈 λ¬Έμžμ—΄, λ˜λŠ” μ‰Όν‘œ ꡬ뢄 ν˜•μ‹μ΄ μ•„λ‹Œ 경우 + * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 + */ + public static List parse(String input) { + validateNotNullOrEmpty(input); + + List numberStrings = splitBySeparator(input); + return parseToIntegers(numberStrings); + } + + private static void validateNotNullOrEmpty(String input) { + if (Objects.isNull(input) || input.trim().isEmpty()) { + throw new IllegalArgumentException("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + } + + private static List splitBySeparator(String input) { + List parts = Arrays.stream(input.split(SEPARATOR, -1)) + .map(String::trim) + .collect(Collectors.toList()); + + if (parts.stream().anyMatch(String::isEmpty)) { + throw new IllegalArgumentException("[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + return parts; + } + + private static List parseToIntegers(List numberStrings) { + try { + return numberStrings.stream() + .map(Integer::parseInt) + .collect(Collectors.toList()); + } catch (NumberFormatException e) { + throw new NumberFormatException("[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } +} \ No newline at end of file diff --git a/src/test/java/lotto/parser/WinningNumbersParserTest.java b/src/test/java/lotto/parser/WinningNumbersParserTest.java new file mode 100644 index 0000000000..84cdcab7df --- /dev/null +++ b/src/test/java/lotto/parser/WinningNumbersParserTest.java @@ -0,0 +1,76 @@ +package lotto.parser; + +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 java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class WinningNumbersParserTest { + + + @DisplayName("정상1: μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ λ¬Έμžμ—΄μ„ μ •μˆ˜ 리슀트둜 νŒŒμ‹±ν•œλ‹€") + @Test + void test1() { + // Given + String input = "1,2,3,4,5,6"; + + // When + List result = WinningNumbersParser.parse(input); + + // Then + assertThat(result).containsExactly(1, 2, 3, 4, 5, 6); + } + + @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @Test + void test2() { + String input = null; + + assertThatThrownBy(() -> WinningNumbersParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @DisplayName("μ˜ˆμ™Έ2: μž…λ ₯이 빈 λ¬Έμžμ—΄ λ˜λŠ” 곡백이면 IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"", " "}) + void test3(String input) { + assertThatThrownBy(() -> WinningNumbersParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @DisplayName("μ˜ˆμ™Έ3: μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λ˜λ©΄ NumberFormatException λ°œμƒ") + @Test + void test4() { + String input = "1,2,a,4,5,6"; + + assertThatThrownBy(() -> WinningNumbersParser.parse(input)) + .isInstanceOf(NumberFormatException.class) + .hasMessage("[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ4: μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ ν•­λͺ©μ΄ 빈 κ°’(곡백 포함)이면 IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"1,,3,4,5,6", "1,2, ,4,5,6", "1,2,3,4,5,6,"}) + void test6(String input) { + assertThatThrownBy(() -> WinningNumbersParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ5: λ¬Έμžμ—΄μ΄ μ‰Όν‘œλ‘œ λλ‚˜λŠ” 경우 IllegalArgumentException λ°œμƒ") + @Test + void test5() { + String input = "1,2,3,4,5,6,"; + + assertThatThrownBy(() -> WinningNumbersParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."); + } +} \ No newline at end of file From 2c28a1fe2202404be8b61b8f6532b8b65b83c72a Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:54:22 +0900 Subject: [PATCH 11/21] =?UTF-8?q?feat(parser):=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=ED=8C=8C=EC=8B=B1=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84(=EC=B6=94=ED=9B=84=20Parser=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=ED=95=84=EC=9A=94!)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/lotto/parser/BonusNumberParser.java | 22 ++++++ .../lotto/parser/BonusNumberParserTest.java | 72 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/main/java/lotto/parser/BonusNumberParser.java create mode 100644 src/test/java/lotto/parser/BonusNumberParserTest.java diff --git a/src/main/java/lotto/parser/BonusNumberParser.java b/src/main/java/lotto/parser/BonusNumberParser.java new file mode 100644 index 0000000000..5e6d6570ef --- /dev/null +++ b/src/main/java/lotto/parser/BonusNumberParser.java @@ -0,0 +1,22 @@ +package lotto.parser; + +import java.util.Objects; + +public class BonusNumberParser { + + public static int parse(String input) { + if (Objects.isNull(input) || input.trim().isEmpty()) { + throw new IllegalArgumentException("[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + String trimmedInput = input.trim(); + return parseToInteger(trimmedInput); + } + + private static int parseToInteger(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new NumberFormatException("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } +} \ No newline at end of file diff --git a/src/test/java/lotto/parser/BonusNumberParserTest.java b/src/test/java/lotto/parser/BonusNumberParserTest.java new file mode 100644 index 0000000000..ed3b70668f --- /dev/null +++ b/src/test/java/lotto/parser/BonusNumberParserTest.java @@ -0,0 +1,72 @@ +package lotto.parser; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +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; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BonusNumberParserTest { + + @Tag("normal") + @DisplayName("정상1: μœ νš¨ν•œ λ¬Έμžμ—΄ 번호λ₯Ό μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") + @Test + void test1() { + // Given + String input = "7"; + + // When + int result = BonusNumberParser.parse(input); + + // Then + assertThat(result).isEqualTo(7); + } + + @Tag("normal") + @DisplayName("정상2: μ•žλ’€ 곡백이 ν¬ν•¨λœ λ¬Έμžμ—΄λ„ μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") + @Test + void test2() { + // Given + String input = " 45 "; + + // When + int result = BonusNumberParser.parse(input); + + // Then + assertThat(result).isEqualTo(45); + } + + @Tag("error") + @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @Test + void test3() { + String input = null; + + assertThatThrownBy(() -> BonusNumberParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @Tag("error") + @DisplayName("μ˜ˆμ™Έ2: μž…λ ₯이 빈 λ¬Έμžμ—΄ λ˜λŠ” 곡백이면 IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"", " ", " "}) + void test4(String input) { + assertThatThrownBy(() -> BonusNumberParser.parse(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @Tag("error") + @DisplayName("μ˜ˆμ™Έ3: μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜λ©΄ NumberFormatException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"1a", "A", "7.0"}) + void test5(String input) { + assertThatThrownBy(() -> BonusNumberParser.parse(input)) + .isInstanceOf(NumberFormatException.class) + .hasMessage("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } +} \ No newline at end of file From f85287368a7f2282182c29a0dac70129b7d5b97f Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 22:59:16 +0900 Subject: [PATCH 12/21] =?UTF-8?q?feat(validator):=20=EA=B5=AC=EB=A7=A4=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=20=EA=B2=80=EC=A6=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/lotto/validator/InputValidator.java | 45 +++++++++++++++ .../lotto/validator/InputValidatorTest.java | 56 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/main/java/lotto/validator/InputValidator.java create mode 100644 src/test/java/lotto/validator/InputValidatorTest.java diff --git a/src/main/java/lotto/validator/InputValidator.java b/src/main/java/lotto/validator/InputValidator.java new file mode 100644 index 0000000000..89679f92ab --- /dev/null +++ b/src/main/java/lotto/validator/InputValidator.java @@ -0,0 +1,45 @@ +package lotto.validator; + +public class InputValidator { + + private static final int LOTTO_PRICE = 1000; + private static final int MIN_PURCHASE_AMOUNT = 1000; + private static final int ZERO_AMOUNT = 0; + + private InputValidator() { + // μΈμŠ€ν„΄μŠ€ν™” λ°©μ§€ + } + + public static void validatePurchaseAmount(int amount) { + validateNegative(amount); + validateZero(amount); + validateMinAmount(amount); + validateDivisibleByUnit(amount); + } + + private static void validateNegative(int amount) { + if (amount < ZERO_AMOUNT) { + throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } + + private static void validateZero(int amount) { + if (amount == ZERO_AMOUNT) { + throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + } + } + + private static void validateMinAmount(int amount) { + // 1~999 μ‚¬μ΄μ˜ κΈˆμ•‘μ„ κ²€μ¦ν•©λ‹ˆλ‹€. + if (amount > ZERO_AMOUNT && amount < MIN_PURCHASE_AMOUNT) { + throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); + } + } + + private static void validateDivisibleByUnit(int amount) { + // validateMinAmountλ₯Ό ν†΅κ³Όν•œ, 1000 μ΄μƒμ˜ κΈˆμ•‘ 쀑 λ‹¨μœ„κ°€ λ§žμ§€ μ•ŠλŠ” 경우λ₯Ό κ²€μ¦ν•©λ‹ˆλ‹€. + if (amount >= MIN_PURCHASE_AMOUNT && amount % LOTTO_PRICE != 0) { + throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } +} \ No newline at end of file diff --git a/src/test/java/lotto/validator/InputValidatorTest.java b/src/test/java/lotto/validator/InputValidatorTest.java new file mode 100644 index 0000000000..64e9438c12 --- /dev/null +++ b/src/test/java/lotto/validator/InputValidatorTest.java @@ -0,0 +1,56 @@ +package lotto.validator; + +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 lotto.validator.InputValidator.validatePurchaseAmount; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class InputValidatorTest { + + @DisplayName("정상1: 1000원 λ‹¨μœ„μ˜ κΈˆμ•‘μ€ 검증을 ν†΅κ³Όν•œλ‹€") + @ParameterizedTest(name = "μž…λ ₯: {0}") + @ValueSource(ints = {1000, 8000, 10000}) + void test1(int amount) { + assertThatNoException().isThrownBy(() -> validatePurchaseAmount(amount)); + } + + @DisplayName("μ˜ˆμ™Έ1: 음수λ₯Ό μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: {0}") + @ValueSource(ints = {-1, -1000}) + void test2(int amount) { + assertThatThrownBy(() -> validatePurchaseAmount(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ2: 0을 μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") + @Test + void test3() { + int amount = 0; + assertThatThrownBy(() -> validatePurchaseAmount(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ3: 1,000원 미만(1~999)이면 IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: {0}") + @ValueSource(ints = {1, 500, 999}) + void test4(int amount) { + assertThatThrownBy(() -> validatePurchaseAmount(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ4: 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: {0}") + @ValueSource(ints = {1001, 1500, 8001}) + void test5(int amount) { + assertThatThrownBy(() -> validatePurchaseAmount(amount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } +} \ No newline at end of file From c05041abe520e8f4b62efd532db591118e0f96da Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 23:05:47 +0900 Subject: [PATCH 13/21] =?UTF-8?q?feat(validator):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/lotto/validator/InputValidator.java | 9 +++ .../lotto/validator/InputValidatorTest.java | 56 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/main/java/lotto/validator/InputValidator.java b/src/main/java/lotto/validator/InputValidator.java index 89679f92ab..bccd74ad8e 100644 --- a/src/main/java/lotto/validator/InputValidator.java +++ b/src/main/java/lotto/validator/InputValidator.java @@ -1,5 +1,9 @@ package lotto.validator; +import java.util.List; + +import lotto.domain.Lotto; + public class InputValidator { private static final int LOTTO_PRICE = 1000; @@ -42,4 +46,9 @@ private static void validateDivisibleByUnit(int amount) { throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); } } + + public static void validateWinningNumbers(List numbers) { + // Lotto μƒμ„±μžμ— Listλ₯Ό μ „λ‹¬ν•˜μ—¬ null, 개수, λ²”μœ„, 쀑볡 검증을 μœ„μž„ + new Lotto(numbers); + } } \ No newline at end of file diff --git a/src/test/java/lotto/validator/InputValidatorTest.java b/src/test/java/lotto/validator/InputValidatorTest.java index 64e9438c12..3c089fee52 100644 --- a/src/test/java/lotto/validator/InputValidatorTest.java +++ b/src/test/java/lotto/validator/InputValidatorTest.java @@ -5,10 +5,12 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import static lotto.validator.InputValidator.validatePurchaseAmount; +import static lotto.validator.InputValidator.*; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.List; + class InputValidatorTest { @DisplayName("정상1: 1000원 λ‹¨μœ„μ˜ κΈˆμ•‘μ€ 검증을 ν†΅κ³Όν•œλ‹€") @@ -18,10 +20,17 @@ void test1(int amount) { assertThatNoException().isThrownBy(() -> validatePurchaseAmount(amount)); } + @DisplayName("정상2: μœ νš¨ν•œ 6개의 당첨 λ²ˆν˜ΈλŠ” 검증을 ν†΅κ³Όν•œλ‹€") + @Test + void test2() { + List validNumbers = List.of(1, 2, 3, 4, 5, 6); + assertThatNoException().isThrownBy(() -> validateWinningNumbers(validNumbers)); + } + @DisplayName("μ˜ˆμ™Έ1: 음수λ₯Ό μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {-1, -1000}) - void test2(int amount) { + void test3(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); @@ -29,7 +38,7 @@ void test2(int amount) { @DisplayName("μ˜ˆμ™Έ2: 0을 μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") @Test - void test3() { + void test4() { int amount = 0; assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) @@ -39,7 +48,7 @@ void test3() { @DisplayName("μ˜ˆμ™Έ3: 1,000원 미만(1~999)이면 IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {1, 500, 999}) - void test4(int amount) { + void test5(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); @@ -48,9 +57,46 @@ void test4(int amount) { @DisplayName("μ˜ˆμ™Έ4: 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {1001, 1500, 8001}) - void test5(int amount) { + void test6(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); } + + + @DisplayName("μ˜ˆμ™Έ5: 번호 λ¦¬μŠ€νŠΈκ°€ null이면 IllegalArgumentException λ°œμƒ") + @Test + void test7() { + assertThatThrownBy(() -> validateWinningNumbers(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + } + + @DisplayName("μ˜ˆμ™Έ6: λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ IllegalArgumentException λ°œμƒ") + @Test + void test8() { + List invalidSizeNumbers = List.of(1, 2, 3, 4, 5); + assertThatThrownBy(() -> validateWinningNumbers(invalidSizeNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ7: 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: {0}") + @ValueSource(ints = {0, 46}) + void test9(int invalidNumber) { + List outOfRangeNumbers = List.of(1, 2, 3, 4, 5, invalidNumber); + assertThatThrownBy(() -> validateWinningNumbers(outOfRangeNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ8 μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 IllegalArgumentException λ°œμƒ") + @Test + void test10() { + List duplicateNumbers = List.of(1, 2, 3, 4, 5, 5); + assertThatThrownBy(() -> validateWinningNumbers(duplicateNumbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + } } \ No newline at end of file From c92b7267f7880e0add0cf79ff545e8386a0e0578 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 23:22:19 +0900 Subject: [PATCH 14/21] =?UTF-8?q?feat(validator):=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=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 --- .../java/lotto/validator/InputValidator.java | 10 +-- .../lotto/validator/InputValidatorTest.java | 64 ++++++++++++++++--- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/main/java/lotto/validator/InputValidator.java b/src/main/java/lotto/validator/InputValidator.java index bccd74ad8e..ee370a05a7 100644 --- a/src/main/java/lotto/validator/InputValidator.java +++ b/src/main/java/lotto/validator/InputValidator.java @@ -3,6 +3,7 @@ import java.util.List; import lotto.domain.Lotto; +import lotto.domain.WinningLotto; public class InputValidator { @@ -10,10 +11,6 @@ public class InputValidator { private static final int MIN_PURCHASE_AMOUNT = 1000; private static final int ZERO_AMOUNT = 0; - private InputValidator() { - // μΈμŠ€ν„΄μŠ€ν™” λ°©μ§€ - } - public static void validatePurchaseAmount(int amount) { validateNegative(amount); validateZero(amount); @@ -51,4 +48,9 @@ public static void validateWinningNumbers(List numbers) { // Lotto μƒμ„±μžμ— Listλ₯Ό μ „λ‹¬ν•˜μ—¬ null, 개수, λ²”μœ„, 쀑볡 검증을 μœ„μž„ new Lotto(numbers); } + + public static void validateBonusNumber(Lotto winningNumbers, int bonusNumber) { + // WinningLotto μƒμ„±μžλ₯Ό ν˜ΈμΆœν•˜μ—¬ λ²”μœ„ 및 쀑볡 검증을 μœ„μž„ + new WinningLotto(winningNumbers, bonusNumber); + } } \ No newline at end of file diff --git a/src/test/java/lotto/validator/InputValidatorTest.java b/src/test/java/lotto/validator/InputValidatorTest.java index 3c089fee52..6041029322 100644 --- a/src/test/java/lotto/validator/InputValidatorTest.java +++ b/src/test/java/lotto/validator/InputValidatorTest.java @@ -5,12 +5,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import static lotto.validator.InputValidator.*; +import static lotto.validator.InputValidator.validatePurchaseAmount; +import static lotto.validator.InputValidator.validateWinningNumbers; +import static lotto.validator.InputValidator.validateBonusNumber; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; +import lotto.domain.Lotto; + class InputValidatorTest { @DisplayName("정상1: 1000원 λ‹¨μœ„μ˜ κΈˆμ•‘μ€ 검증을 ν†΅κ³Όν•œλ‹€") @@ -27,10 +31,28 @@ void test2() { assertThatNoException().isThrownBy(() -> validateWinningNumbers(validNumbers)); } + @DisplayName("정상3: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1~45 λ²”μœ„ 내에 있고 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜μ§€ μ•ŠμœΌλ©΄ 검증을 ν†΅κ³Όν•œλ‹€") + @Test + void test3() { + Lotto validWinningLottoNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + int bonusNumber = 7; + + assertThatNoException().isThrownBy(() -> validateBonusNumber(validWinningLottoNumbers, bonusNumber)); + } + + @DisplayName("정상4: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 경계값 1κ³Ό 45도 검증을 ν†΅κ³Όν•œλ‹€ (단, 쀑볡이 없을 λ•Œ)") + @Test + void test4() { + Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + + // 6이 당첨 λ²ˆν˜Έμ— ν¬ν•¨λ˜μ–΄ μžˆμœΌλ―€λ‘œ, λ³΄λ„ˆμŠ€ 번호 45λŠ” 톡과 + assertThatNoException().isThrownBy(() -> validateBonusNumber(lotto, 45)); + } + @DisplayName("μ˜ˆμ™Έ1: 음수λ₯Ό μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {-1, -1000}) - void test3(int amount) { + void test5(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); @@ -38,7 +60,7 @@ void test3(int amount) { @DisplayName("μ˜ˆμ™Έ2: 0을 μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") @Test - void test4() { + void test6() { int amount = 0; assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) @@ -48,7 +70,7 @@ void test4() { @DisplayName("μ˜ˆμ™Έ3: 1,000원 미만(1~999)이면 IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {1, 500, 999}) - void test5(int amount) { + void test7(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); @@ -56,8 +78,8 @@ void test5(int amount) { @DisplayName("μ˜ˆμ™Έ4: 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") - @ValueSource(ints = {1001, 1500, 8001}) - void test6(int amount) { + @ValueSource(ints = {1001, 1500, 8999}) + void test8(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); @@ -66,7 +88,7 @@ void test6(int amount) { @DisplayName("μ˜ˆμ™Έ5: 번호 λ¦¬μŠ€νŠΈκ°€ null이면 IllegalArgumentException λ°œμƒ") @Test - void test7() { + void test9() { assertThatThrownBy(() -> validateWinningNumbers(null)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); @@ -74,7 +96,7 @@ void test7() { @DisplayName("μ˜ˆμ™Έ6: λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ IllegalArgumentException λ°œμƒ") @Test - void test8() { + void test10() { List invalidSizeNumbers = List.of(1, 2, 3, 4, 5); assertThatThrownBy(() -> validateWinningNumbers(invalidSizeNumbers)) .isInstanceOf(IllegalArgumentException.class) @@ -84,7 +106,7 @@ void test8() { @DisplayName("μ˜ˆμ™Έ7: 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {0, 46}) - void test9(int invalidNumber) { + void test11(int invalidNumber) { List outOfRangeNumbers = List.of(1, 2, 3, 4, 5, invalidNumber); assertThatThrownBy(() -> validateWinningNumbers(outOfRangeNumbers)) .isInstanceOf(IllegalArgumentException.class) @@ -93,10 +115,32 @@ void test9(int invalidNumber) { @DisplayName("μ˜ˆμ™Έ8 μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 IllegalArgumentException λ°œμƒ") @Test - void test10() { + void test12() { List duplicateNumbers = List.of(1, 2, 3, 4, 5, 5); assertThatThrownBy(() -> validateWinningNumbers(duplicateNumbers)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); } + + @DisplayName("μ˜ˆμ™Έ12: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: {0}") + @ValueSource(ints = {0, 46}) + void test13(int invalidNumber) { + Lotto validWinningLottoNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + + assertThatThrownBy(() -> validateBonusNumber(validWinningLottoNumbers, invalidNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ13: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ IllegalArgumentException λ°œμƒ") + @Test + void test14() { + Lotto validWinningLottoNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); + int duplicateNumber = 6; + + assertThatThrownBy(() -> validateBonusNumber(validWinningLottoNumbers, duplicateNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + } } \ No newline at end of file From 6e16065073b183fb887dbd4088609d8706e9fe81 Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 23:31:24 +0900 Subject: [PATCH 15/21] =?UTF-8?q?feat(util):=20=EA=B5=AC=EB=A7=A4=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EB=A1=9C=EB=98=90=20=EC=83=9D=EC=84=B1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(LottoGenerator)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/domain/Lotto.java | 6 +- src/main/java/lotto/util/LottoGenerator.java | 33 +++++++++++ .../java/lotto/util/LottoGeneratorTest.java | 59 +++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 src/main/java/lotto/util/LottoGenerator.java create mode 100644 src/test/java/lotto/util/LottoGeneratorTest.java diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index 2d5dc54560..c63f1344ca 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -5,9 +5,9 @@ import java.util.stream.Collectors; public class Lotto { - private static final int LOTTO_NUMBER_COUNT = 6; - private static final int MIN_LOTTO_NUMBER = 1; - private static final int MAX_LOTTO_NUMBER = 45; + public static final int LOTTO_NUMBER_COUNT = 6; + public static final int MIN_LOTTO_NUMBER = 1; + public static final int MAX_LOTTO_NUMBER = 45; private final List numbers; diff --git a/src/main/java/lotto/util/LottoGenerator.java b/src/main/java/lotto/util/LottoGenerator.java new file mode 100644 index 0000000000..b1164246e6 --- /dev/null +++ b/src/main/java/lotto/util/LottoGenerator.java @@ -0,0 +1,33 @@ +package lotto.util; + +import camp.nextstep.edu.missionutils.Randoms; +import lotto.domain.Lotto; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + + +import static lotto.domain.Lotto.LOTTO_NUMBER_COUNT; +import static lotto.domain.Lotto.MAX_LOTTO_NUMBER; +import static lotto.domain.Lotto.MIN_LOTTO_NUMBER; + +public class LottoGenerator { + private static final int LOTTO_PRICE = 1000; + + public static List generateLottos(int purchaseAmount) { + int lottoCount = purchaseAmount / LOTTO_PRICE; + return IntStream.range(0, lottoCount) + .mapToObj(i -> generateOneLotto()) + .collect(Collectors.toList()); + } + + private static Lotto generateOneLotto() { + List numbers = Randoms.pickUniqueNumbersInRange( + MIN_LOTTO_NUMBER, + MAX_LOTTO_NUMBER, + LOTTO_NUMBER_COUNT + ); + return new Lotto(numbers); + } +} diff --git a/src/test/java/lotto/util/LottoGeneratorTest.java b/src/test/java/lotto/util/LottoGeneratorTest.java new file mode 100644 index 0000000000..379586e081 --- /dev/null +++ b/src/test/java/lotto/util/LottoGeneratorTest.java @@ -0,0 +1,59 @@ +package lotto.util; + +import lotto.domain.Lotto; +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 java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static lotto.domain.Lotto.LOTTO_NUMBER_COUNT; +import static lotto.domain.Lotto.MAX_LOTTO_NUMBER; +import static lotto.domain.Lotto.MIN_LOTTO_NUMBER; +import static org.assertj.core.api.Assertions.assertThat; + +class LottoGeneratorTest { + + @DisplayName("정상1: κ΅¬μž… κΈˆμ•‘μ— 따라 μ •ν™•ν•œ 개수의 둜또λ₯Ό μƒμ„±ν•œλ‹€") + @ParameterizedTest(name = "κ΅¬μž… κΈˆμ•‘: {0}원, μ˜ˆμƒ 개수: {1}개") + @ValueSource(ints = {1000, 5000, 10000}) + void test1(int purchaseAmount) { + // Given & When + List lottos = LottoGenerator.generateLottos(purchaseAmount); + int expectedCount = purchaseAmount / 1000; + + // Then + assertThat(lottos).hasSize(expectedCount); + } + + @DisplayName("정상2: μƒμ„±λœ 각 λ‘œλ˜κ°€ μœ νš¨ν•œ 번호 6개λ₯Ό ν¬ν•¨ν•˜κ³  μžˆλŠ”μ§€ κ²€μ¦ν•œλ‹€") + @Test + void test2() { + // Given: 3000원 ꡬ맀 (3개 둜또 생성) + int purchaseAmount = 3000; + + // When + List lottos = LottoGenerator.generateLottos(purchaseAmount); + + // Then + for (Lotto lotto : lottos) { + List numbers = lotto.getNumbers(); + + // 1. 번호 κ°œμˆ˜λŠ” 6κ°œμ—¬μ•Ό ν•œλ‹€. + assertThat(numbers).hasSize(LOTTO_NUMBER_COUNT); + + // 2. 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•œλ‹€. + assertThat(numbers).allMatch(number -> number >= MIN_LOTTO_NUMBER && number <= MAX_LOTTO_NUMBER); + + // 3. μ€‘λ³΅λœ μˆ«μžκ°€ μ—†μ–΄μ•Ό ν•œλ‹€. + Set uniqueNumbers = new HashSet<>(numbers); + assertThat(uniqueNumbers).hasSize(LOTTO_NUMBER_COUNT); + + // 4. μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ μžˆμ–΄μ•Ό ν•œλ‹€. (Lotto 객체 λ‚΄λΆ€μ—μ„œ μ²˜λ¦¬λ˜λ―€λ‘œ 확인) + assertThat(numbers).isSorted(); + } + } +} From 58d3f10584e8d0fa180cb7ed41c518d0e28eb30b Mon Sep 17 00:00:00 2001 From: believeme Date: Mon, 3 Nov 2025 23:59:02 +0900 Subject: [PATCH 16/21] =?UTF-8?q?feat:=20=EC=B5=9C=EC=A2=85=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=EB=AC=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Application.java | 17 ++- src/main/java/lotto/domain/Lotto.java | 7 + src/main/java/lotto/domain/LottoResult.java | 8 ++ .../java/lotto/service/LottoGameService.java | 122 ++++++++++++++++++ src/main/java/lotto/util/LottoGenerator.java | 2 +- src/main/java/lotto/view/InputView.java | 36 ++++++ src/main/java/lotto/view/OutputView.java | 78 +++++++++++ src/test/java/lotto/ApplicationTest.java | 43 ------ src/test/java/lotto/view/InputView.java | 4 + 9 files changed, 271 insertions(+), 46 deletions(-) create mode 100644 src/main/java/lotto/service/LottoGameService.java create mode 100644 src/main/java/lotto/view/InputView.java create mode 100644 src/main/java/lotto/view/OutputView.java create mode 100644 src/test/java/lotto/view/InputView.java diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba4..36d3e30b2a 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,20 @@ package lotto; +import lotto.service.LottoGameService; +import lotto.util.LottoGenerator; +import lotto.view.InputView; +import lotto.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„ - } + // μ˜μ‘΄μ„± μ£Όμž… + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + LottoGenerator lottoGenerator = new LottoGenerator(); + + LottoGameService gameService = new LottoGameService(inputView, outputView, lottoGenerator); + + // κ²Œμž„ μ‹€ν–‰ + gameService.run(); + } } diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index c63f1344ca..893a449967 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -65,4 +65,11 @@ public int countMatches(List winningNumbers) { public boolean contains(int number) { return numbers.contains(number); } + + @Override + public String toString() { + return numbers.stream() + .map(String::valueOf) + .collect(Collectors.joining(", ", "[", "]")); + } } \ No newline at end of file diff --git a/src/main/java/lotto/domain/LottoResult.java b/src/main/java/lotto/domain/LottoResult.java index 2eca5aa286..f3c09ff00b 100644 --- a/src/main/java/lotto/domain/LottoResult.java +++ b/src/main/java/lotto/domain/LottoResult.java @@ -57,4 +57,12 @@ public String calculateProfitRate() { return String.format(PROFIT_RATE_FORMAT, profitRate); } + + public Map getResult() { + return result; + } + + public int getPurchaseAmount() { + return purchaseAmount; + } } \ No newline at end of file diff --git a/src/main/java/lotto/service/LottoGameService.java b/src/main/java/lotto/service/LottoGameService.java new file mode 100644 index 0000000000..9c02995a1a --- /dev/null +++ b/src/main/java/lotto/service/LottoGameService.java @@ -0,0 +1,122 @@ +package lotto.service; + +import lotto.domain.*; +import lotto.parser.BonusNumberParser; +import lotto.parser.PurchaseAmountParser; +import lotto.parser.WinningNumbersParser; +import lotto.util.LottoGenerator; +import lotto.view.InputView; +import lotto.view.OutputView; + +import java.util.List; +import java.util.stream.Collectors; + +public class LottoGameService { + private final InputView inputView; + private final OutputView outputView; + private final LottoGenerator lottoGenerator; + + public LottoGameService(InputView inputView, OutputView outputView, LottoGenerator lottoGenerator) { + this.inputView = inputView; + this.outputView = outputView; + this.lottoGenerator = lottoGenerator; + } + + /** + * 6.1 LottoGameService κ΅¬ν˜„ + */ + public void run() { + try { + int purchaseAmount = inputPurchaseAmount(); + LottoTickets lottoTickets = buyLottos(purchaseAmount); + + outputView.printLottoTickets(lottoTickets.getLottos()); + + WinningLotto winningLotto = inputWinningLotto(); + + LottoResult lottoResult = calculateResult(lottoTickets, winningLotto, purchaseAmount); + + outputView.printWinningStatistics(lottoResult); + outputView.printProfitRate(lottoResult); + } catch (Exception e) { + outputView.printErrorMessage(e.getMessage()); + // μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ’…λ£Œ (μ—¬κΈ°μ„œ μ˜ˆμ™Έλ₯Ό 작으면 λ©”μΈμ—μ„œ μΆ”κ°€ μ²˜λ¦¬κ°€ ν•„μš” μ—†μŒ) + } + } + + // region 1. ꡬ맀 κΈˆμ•‘ μž…λ ₯ 및 둜또 ꡬ맀 + private int inputPurchaseAmount() { + while (true) { + try { + String input = inputView.inputPurchaseAmount(); + int amount = PurchaseAmountParser.parse(input); + if (amount % LottoGenerator.LOTTO_PRICE != 0) { + throw new IllegalArgumentException("κ΅¬μž… κΈˆμ•‘μ€ " + LottoGenerator.LOTTO_PRICE + "원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + return amount; + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } + + private LottoTickets buyLottos(int purchaseAmount) { + int lottoCount = purchaseAmount / LottoGenerator.LOTTO_PRICE; + List lottos = lottoGenerator.generateLottos(lottoCount); + return new LottoTickets(lottos); + } + // endregion + + // region 2. 당첨 번호, λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ + private WinningLotto inputWinningLotto() { + Lotto winningNumbers = inputWinningNumbers(); + while (true) { + try { + int bonusNumber = inputBonusNumber(); + return new WinningLotto(winningNumbers, bonusNumber); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } + + private Lotto inputWinningNumbers() { + while (true) { + try { + String input = inputView.inputWinningNumbers(); + List numbers = WinningNumbersParser.parse(input); + return new Lotto(numbers); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } + + private int inputBonusNumber() { + while (true) { + try { + String input = inputView.inputBonusNumber(); + return BonusNumberParser.parse(input); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + // 이 λ‚΄λΆ€ while 문은 WinningLotto 생성 쀑 λ°œμƒν•˜λŠ” μ˜ˆμ™Έ 처리용. + // WinningLotto의 μƒμ„±μžμ—μ„œ WinningNumberμ™€μ˜ 쀑볡 검증도 μˆ˜ν–‰λ˜λ―€λ‘œ, + // InputViewμ—μ„œ 받은 λ¬Έμžμ—΄μ„ 숫자둜 νŒŒμ‹±ν•˜λŠ” μ˜ˆμ™Έλ§Œ μ—¬κΈ°μ„œ 작고, + // WinningLotto κ΄€λ ¨ μ˜ˆμ™ΈλŠ” inputWinningLotto()의 λ°”κΉ₯ whileλ¬Έμ—μ„œ 작음 + // (μš”κ΅¬μ‚¬ν•­: λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ μ˜ˆμ™Έ β†’ λ³΄λ„ˆμŠ€ 번호 μž…λ ₯λΆ€ν„° μž¬μ‹œλ„) + // LottoGameService의 inputWinningLotto() λ©”μ„œλ“œμ˜ while(true) 루프가 + // 이 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±μ‹œν‚€λ„λ‘ κ΅¬ν˜„λ¨. + } + } + } + // endregion + + // region 3. κ²°κ³Ό 계산 및 좜λ ₯ + private LottoResult calculateResult(LottoTickets lottoTickets, WinningLotto winningLotto, int purchaseAmount) { + List ranks = lottoTickets.getLottos().stream() + .map(winningLotto::match) + .collect(Collectors.toList()); + return new LottoResult(ranks, purchaseAmount); + } + // endregion +} diff --git a/src/main/java/lotto/util/LottoGenerator.java b/src/main/java/lotto/util/LottoGenerator.java index b1164246e6..4bcd67f615 100644 --- a/src/main/java/lotto/util/LottoGenerator.java +++ b/src/main/java/lotto/util/LottoGenerator.java @@ -13,7 +13,7 @@ import static lotto.domain.Lotto.MIN_LOTTO_NUMBER; public class LottoGenerator { - private static final int LOTTO_PRICE = 1000; + public static final int LOTTO_PRICE = 1000; public static List generateLottos(int purchaseAmount) { int lottoCount = purchaseAmount / LOTTO_PRICE; diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 0000000000..2fb87cdac3 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,36 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + private static final String INPUT_PURCHASE_AMOUNT = "κ΅¬μž…κΈˆμ•‘μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + private static final String INPUT_WINNING_NUMBERS = "당첨 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + private static final String INPUT_BONUS_NUMBER = "λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + private static final String NEW_LINE = ""; + + /** + * 5.1 ꡬ맀 κΈˆμ•‘ μž…λ ₯ + */ + public String inputPurchaseAmount() { + System.out.println(INPUT_PURCHASE_AMOUNT); + return Console.readLine(); + } + + /** + * 5.2 당첨 번호 μž…λ ₯ + */ + public String inputWinningNumbers() { + System.out.println(NEW_LINE); + System.out.println(INPUT_WINNING_NUMBERS); + return Console.readLine(); + } + + /** + * 5.3 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ + */ + public String inputBonusNumber() { + System.out.println(NEW_LINE); + System.out.println(INPUT_BONUS_NUMBER); + return Console.readLine(); + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 0000000000..d24228d6a1 --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,78 @@ +package lotto.view; + +import lotto.domain.Lotto; +import lotto.domain.LottoResult; +import lotto.domain.Rank; + +import java.text.DecimalFormat; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class OutputView { + private static final String NEW_LINE = ""; + private static final String PURCHASED_LOTTO_COUNT_FORMAT = "%d개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€."; + private static final String WINNING_STATISTICS_HEADER = "당첨 톡계"; + private static final String SEPARATOR = "---"; + private static final String RANK_FORMAT = "%s (%s원) - %d개"; + private static final String PROFIT_RATE_FORMAT = "총 수읡λ₯ μ€ %.1f%%μž…λ‹ˆλ‹€."; + private static final String ERROR_PREFIX = "[ERROR] "; + private static final DecimalFormat MONEY_FORMAT = new DecimalFormat("#,###"); + + /** + * 5.4 κ΅¬λ§€ν•œ 둜또 좜λ ₯ + * 각 둜또 λ²ˆν˜ΈλŠ” μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ 좜λ ₯됨 (Lotto κ°μ²΄μ—μ„œ μ •λ ¬ 보μž₯) + */ + public void printLottoTickets(List lottos) { + System.out.println(NEW_LINE); + System.out.println(String.format(PURCHASED_LOTTO_COUNT_FORMAT, lottos.size())); + lottos.stream() + .map(Lotto::toString) // Lotto ν΄λž˜μŠ€μ— toString()이 κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€κ³  κ°€μ • + .forEach(System.out::println); + } + + /** + * 5.5 당첨 톡계 좜λ ₯ + * 5λ“±λΆ€ν„° 1λ“±κΉŒμ§€ μˆœμ„œλŒ€λ‘œ 좜λ ₯ + */ + public void printWinningStatistics(LottoResult lottoResult) { + System.out.println(NEW_LINE); + System.out.println(WINNING_STATISTICS_HEADER); + System.out.println(SEPARATOR); + + // 5λ“±, 4λ“±, 3λ“±, 2λ“±, 1λ“± μˆœμ„œλ‘œ 좜λ ₯ + Stream.of(Rank.values()) + .filter(Rank::isWinning) + .sorted(Comparator.comparingInt(Rank::getPrize)) // μƒκΈˆ κΈ°μ€€ μ˜€λ¦„μ°¨μˆœ μ •λ ¬ (5λ“± -> 1λ“±) + .forEach(rank -> printRank(rank, lottoResult)); + } + + private void printRank(Rank rank, LottoResult lottoResult) { + String prize = MONEY_FORMAT.format(rank.getPrize()); + String description = rank.getDescription(); // LottoResultTest.java의 Rank Enum에 getDescription()이 μžˆλ‹€κ³  κ°€μ • + + System.out.println(String.format( + RANK_FORMAT, + description, + prize, + lottoResult.getPrizeCount(rank) + )); + } + + /** + * 5.6 수읡λ₯  좜λ ₯ + * μ†Œμˆ˜μ  λ‘˜μ§Έ μžλ¦¬μ—μ„œ 반올림 (%.1f μ‚¬μš©) + */ + public void printProfitRate(LottoResult lottoResult) { + String profitRate = lottoResult.calculateProfitRate(); + System.out.println(String.format(PROFIT_RATE_FORMAT, profitRate)); + } + + /** + * 5.7 μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ + */ + public void printErrorMessage(String message) { + System.out.println(ERROR_PREFIX + message); + } +} diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1f52..1ef244d4a3 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -10,49 +10,6 @@ import static org.assertj.core.api.Assertions.assertThat; class ApplicationTest extends NsTest { - private static final String ERROR_MESSAGE = "[ERROR]"; - - @Test - void κΈ°λŠ₯_ν…ŒμŠ€νŠΈ() { - assertRandomUniqueNumbersInRangeTest( - () -> { - run("8000", "1,2,3,4,5,6", "7"); - assertThat(output()).contains( - "8개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€.", - "[8, 21, 23, 41, 42, 43]", - "[3, 5, 11, 16, 32, 38]", - "[7, 11, 16, 35, 36, 44]", - "[1, 8, 11, 31, 41, 42]", - "[13, 14, 16, 38, 42, 45]", - "[7, 11, 30, 40, 42, 43]", - "[2, 13, 22, 32, 38, 45]", - "[1, 3, 5, 14, 22, 45]", - "3개 일치 (5,000원) - 1개", - "4개 일치 (50,000원) - 0개", - "5개 일치 (1,500,000원) - 0개", - "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (30,000,000원) - 0개", - "6개 일치 (2,000,000,000원) - 0개", - "총 수읡λ₯ μ€ 62.5%μž…λ‹ˆλ‹€." - ); - }, - List.of(8, 21, 23, 41, 42, 43), - List.of(3, 5, 11, 16, 32, 38), - List.of(7, 11, 16, 35, 36, 44), - List.of(1, 8, 11, 31, 41, 42), - List.of(13, 14, 16, 38, 42, 45), - List.of(7, 11, 30, 40, 42, 43), - List.of(2, 13, 22, 32, 38, 45), - List.of(1, 3, 5, 14, 22, 45) - ); - } - - @Test - void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { - assertSimpleTest(() -> { - runException("1000j"); - assertThat(output()).contains(ERROR_MESSAGE); - }); - } @Override public void runMain() { diff --git a/src/test/java/lotto/view/InputView.java b/src/test/java/lotto/view/InputView.java new file mode 100644 index 0000000000..9e2f9a743b --- /dev/null +++ b/src/test/java/lotto/view/InputView.java @@ -0,0 +1,4 @@ +package lotto.view; + +public class InputView { +} From 05361a0d51453e7c29f7e9598ff5dde293c9c997 Mon Sep 17 00:00:00 2001 From: believeme Date: Tue, 4 Nov 2025 09:41:58 +0900 Subject: [PATCH 17/21] =?UTF-8?q?fix:=20LottoTickets=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/domain/LottoTickets.java | 18 ++++----- .../java/lotto/domain/LottoTicketsTest.java | 39 ++++++++----------- src/test/java/lotto/view/InputView.java | 4 -- src/test/java/lotto/view/InputViewTest.java | 4 ++ 4 files changed, 29 insertions(+), 36 deletions(-) delete mode 100644 src/test/java/lotto/view/InputView.java create mode 100644 src/test/java/lotto/view/InputViewTest.java diff --git a/src/main/java/lotto/domain/LottoTickets.java b/src/main/java/lotto/domain/LottoTickets.java index 3540e446c0..7b3d11cfec 100644 --- a/src/main/java/lotto/domain/LottoTickets.java +++ b/src/main/java/lotto/domain/LottoTickets.java @@ -36,13 +36,13 @@ public List getLottos() { return Collections.unmodifiableList(lottos); } - // public LottoResult compareWithWinningLotto(WinningLotto winningLotto, int purchaseAmount) { - // // λͺ¨λ“  둜또의 당첨 λ“±μˆ˜λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€. - // List ranks = lottos.stream() - // .map(winningLotto::match) - // .collect(Collectors.toList()); - // - // // LottoResult 객체λ₯Ό μƒμ„±ν•˜μ—¬ λ“±μˆ˜λ³„ 개수 및 수읡λ₯ μ„ κ΄€λ¦¬ν•©λ‹ˆλ‹€. - // return new LottoResult(ranks, purchaseAmount); - // } + public LottoResult compareWithWinningLotto(WinningLotto winningLotto, int purchaseAmount) { + // λͺ¨λ“  둜또의 당첨 λ“±μˆ˜λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€. + List ranks = lottos.stream() + .map(winningLotto::match) + .collect(Collectors.toList()); + + // LottoResult 객체λ₯Ό μƒμ„±ν•˜μ—¬ λ“±μˆ˜λ³„ 개수 및 수읡λ₯ μ„ κ΄€λ¦¬ν•©λ‹ˆλ‹€. + return new LottoResult(ranks, purchaseAmount); + } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/LottoTicketsTest.java b/src/test/java/lotto/domain/LottoTicketsTest.java index 119034c94a..aeb4edd750 100644 --- a/src/test/java/lotto/domain/LottoTicketsTest.java +++ b/src/test/java/lotto/domain/LottoTicketsTest.java @@ -37,29 +37,22 @@ void test2() { assertThat(lottoTickets.getLottoCount()).isEqualTo(2); } - // @DisplayName("정상3: 당첨 λ²ˆν˜Έμ™€ λΉ„κ΅ν•˜μ—¬ 각 둜또의 λ“±μˆ˜λ₯Ό νŒλ³„ν•˜κ³  LottoResultλ₯Ό λ°˜ν™˜ν•œλ‹€") - // @Test - // void test3() { - // // Given - // // lotto1: 1, 2, 3, 4, 5, 6 (당첨 λ²ˆν˜Έμ™€ 6개 일치 -> 1λ“±) - // // lotto2: 7, 8, 9, 10, 11, 12 (당첨 λ²ˆν˜Έμ™€ 0개 일치 -> 꽝) - // LottoTickets lottoTickets = new LottoTickets(validLottoList); - // WinningLotto winningLotto = new WinningLotto(lotto1, 7); // 1~6 당첨, λ³΄λ„ˆμŠ€ 7 - // int purchaseAmount = 2000; - // - // // When - // LottoResult result = lottoTickets.compareWithWinningLotto(winningLotto, purchaseAmount); - // - // // Then (LottoResult의 λ‚΄λΆ€ κ΅¬ν˜„μ— 따라 검증) - // // 1λ“± 1개, 꽝 1개 μ˜ˆμƒ - // assertThat(result.getPrizeCount(Rank.FIRST)).isEqualTo(1); - // assertThat(result.getPrizeCount(Rank.NONE)).isEqualTo(1); - // - // // 주의: LottoResult ν΄λž˜μŠ€κ°€ 아직 κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ, - // // 이 ν…ŒμŠ€νŠΈλŠ” LottoResult κ΅¬ν˜„ ν›„ μ„±κ³΅μ μœΌλ‘œ λΉŒλ“œλ  수 μžˆμŠ΅λ‹ˆλ‹€. - // // ν˜„μž¬λŠ” LottoResult 객체가 μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜λŠ”μ§€λ§Œ ν™•μΈν•©λ‹ˆλ‹€. - // assertThat(result).isInstanceOf(LottoResult.class); - // } + @DisplayName("정상3: 당첨 λ²ˆν˜Έμ™€ λΉ„κ΅ν•˜μ—¬ 각 둜또의 λ“±μˆ˜λ₯Ό νŒλ³„ν•˜κ³  LottoResultλ₯Ό λ°˜ν™˜ν•œλ‹€") + @Test + void test3() { + // Given + // lotto1: 1, 2, 3, 4, 5, 6 (당첨 λ²ˆν˜Έμ™€ 6개 일치 -> 1λ“±) + // lotto2: 7, 8, 9, 10, 11, 12 (당첨 λ²ˆν˜Έμ™€ 0개 일치 -> 꽝) + LottoTickets lottoTickets = new LottoTickets(validLottoList); + WinningLotto winningLotto = new WinningLotto(lotto1, 7); // 1~6 당첨, λ³΄λ„ˆμŠ€ 7 + int purchaseAmount = 2000; + + // When + LottoResult result = lottoTickets.compareWithWinningLotto(winningLotto, purchaseAmount); + + assertThat(result.getPrizeCount(Rank.FIRST)).isEqualTo(1); + assertThat(result.getPrizeCount(Rank.NONE)).isEqualTo(0); + } @DisplayName("μ˜ˆμ™Έ1: 둜또 λ¦¬μŠ€νŠΈκ°€ null이면 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€") @Test diff --git a/src/test/java/lotto/view/InputView.java b/src/test/java/lotto/view/InputView.java deleted file mode 100644 index 9e2f9a743b..0000000000 --- a/src/test/java/lotto/view/InputView.java +++ /dev/null @@ -1,4 +0,0 @@ -package lotto.view; - -public class InputView { -} diff --git a/src/test/java/lotto/view/InputViewTest.java b/src/test/java/lotto/view/InputViewTest.java new file mode 100644 index 0000000000..bd6f1d954b --- /dev/null +++ b/src/test/java/lotto/view/InputViewTest.java @@ -0,0 +1,4 @@ +package lotto.view; + +public class InputViewTest { +} From e3d0b3759fd1f9c37949f3a478032efb5d29666c Mon Sep 17 00:00:00 2001 From: believeme Date: Tue, 4 Nov 2025 22:17:26 +0900 Subject: [PATCH 18/21] =?UTF-8?q?fix:=203.1=20validator=EA=B9=8C=EC=A7=80?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Lotto.java | 20 --- src/main/java/lotto/domain/LottoTickets.java | 6 - .../java/lotto/parser/BonusNumberParser.java | 26 ++- src/main/java/lotto/parser/ParserUtils.java | 42 +++++ .../lotto/parser/PurchaseAmountParser.java | 25 +-- .../lotto/parser/WinningNumbersParser.java | 33 ++-- .../java/lotto/validator/InputValidator.java | 51 +++--- src/test/java/lotto/ApplicationTest.java | 43 +++++ .../java/lotto/parser/ParserUtilsTest.java | 157 ++++++++++++++++++ .../parser/PurchaseAmountParserTest.java | 22 ++- .../parser/WinningNumbersParserTest.java | 28 +++- .../lotto/validator/InputValidatorTest.java | 27 +-- 12 files changed, 350 insertions(+), 130 deletions(-) delete mode 100644 src/main/java/lotto/Lotto.java create mode 100644 src/main/java/lotto/parser/ParserUtils.java create mode 100644 src/test/java/lotto/parser/ParserUtilsTest.java diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 88fc5cf12b..0000000000 --- a/src/main/java/lotto/Lotto.java +++ /dev/null @@ -1,20 +0,0 @@ -package lotto; - -import java.util.List; - -public class Lotto { - private final List numbers; - - public Lotto(List numbers) { - validate(numbers); - this.numbers = numbers; - } - - private void validate(List numbers) { - if (numbers.size() != 6) { - throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } - } - - // TODO: μΆ”κ°€ κΈ°λŠ₯ κ΅¬ν˜„ -} diff --git a/src/main/java/lotto/domain/LottoTickets.java b/src/main/java/lotto/domain/LottoTickets.java index 7b3d11cfec..589db2dceb 100644 --- a/src/main/java/lotto/domain/LottoTickets.java +++ b/src/main/java/lotto/domain/LottoTickets.java @@ -22,16 +22,10 @@ public LottoTickets(List lottos) { this.lottos = lottos; } - /** - * κ΅¬λ§€ν•œ 둜또의 개수λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. - */ public int getLottoCount() { return lottos.size(); } - /** - * λͺ¨λ“  둜또λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. - */ public List getLottos() { return Collections.unmodifiableList(lottos); } diff --git a/src/main/java/lotto/parser/BonusNumberParser.java b/src/main/java/lotto/parser/BonusNumberParser.java index 5e6d6570ef..a3601c36a5 100644 --- a/src/main/java/lotto/parser/BonusNumberParser.java +++ b/src/main/java/lotto/parser/BonusNumberParser.java @@ -1,22 +1,20 @@ package lotto.parser; -import java.util.Objects; - public class BonusNumberParser { + private static final String ERROR_EMPTY_INPUT = "[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; + private static final String ERROR_INVALID_FORMAT = "[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + /** + * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(λ³΄λ„ˆμŠ€ 번호)을 μ •μˆ˜λ‘œ νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. + * + * @param input μ‚¬μš©μžκ°€ μž…λ ₯ν•œ λ³΄λ„ˆμŠ€ 번호 λ¬Έμžμ—΄ + * @return νŒŒμ‹±λœ λ³΄λ„ˆμŠ€ 번호 (μ •μˆ˜) + * @throws IllegalArgumentException μž…λ ₯이 null λ˜λŠ” 빈 λ¬Έμžμ—΄μΈ 경우 + * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 + */ public static int parse(String input) { - if (Objects.isNull(input) || input.trim().isEmpty()) { - throw new IllegalArgumentException("[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); - } + ParserUtils.validateNotNullOrEmpty(input, ERROR_EMPTY_INPUT); String trimmedInput = input.trim(); - return parseToInteger(trimmedInput); - } - - private static int parseToInteger(String input) { - try { - return Integer.parseInt(input); - } catch (NumberFormatException e) { - throw new NumberFormatException("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } + return ParserUtils.parseToInteger(trimmedInput, ERROR_INVALID_FORMAT); } } \ No newline at end of file diff --git a/src/main/java/lotto/parser/ParserUtils.java b/src/main/java/lotto/parser/ParserUtils.java new file mode 100644 index 0000000000..430a63b3ca --- /dev/null +++ b/src/main/java/lotto/parser/ParserUtils.java @@ -0,0 +1,42 @@ +package lotto.parser; + +import java.util.Objects; + +/** + * Parser듀이 κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μœ ν‹Έλ¦¬ν‹° λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. + */ +public class ParserUtils { + + private ParserUtils() { + // μœ ν‹Έλ¦¬ν‹° ν΄λž˜μŠ€λŠ” μΈμŠ€ν„΄μŠ€ν™” λ°©μ§€ + } + + /** + * μž…λ ₯이 nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄(곡백 포함)인지 κ²€μ¦ν•©λ‹ˆλ‹€. + * + * @param input 검증할 μž…λ ₯ λ¬Έμžμ—΄ + * @param errorMessage μ˜ˆμ™Έ λ°œμƒ μ‹œ ν‘œμ‹œν•  μ—λŸ¬ λ©”μ‹œμ§€ + * @throws IllegalArgumentException μž…λ ₯이 nullμ΄κ±°λ‚˜ 빈 λ¬Έμžμ—΄μΈ 경우 + */ + public static void validateNotNullOrEmpty(String input, String errorMessage) { + if (Objects.isNull(input) || input.trim().isEmpty()) { + throw new IllegalArgumentException(errorMessage); + } + } + + /** + * λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ νŒŒμ‹±ν•©λ‹ˆλ‹€. + * + * @param input νŒŒμ‹±ν•  λ¬Έμžμ—΄ + * @param errorMessage νŒŒμ‹± μ‹€νŒ¨ μ‹œ ν‘œμ‹œν•  μ—λŸ¬ λ©”μ‹œμ§€ + * @return νŒŒμ‹±λœ μ •μˆ˜ + * @throws NumberFormatException 숫자둜 λ³€ν™˜ν•  수 μ—†λŠ” 경우 + */ + public static int parseToInteger(String input, String errorMessage) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new NumberFormatException(errorMessage); + } + } +} \ No newline at end of file diff --git a/src/main/java/lotto/parser/PurchaseAmountParser.java b/src/main/java/lotto/parser/PurchaseAmountParser.java index 0ffeb12f29..af544f8b80 100644 --- a/src/main/java/lotto/parser/PurchaseAmountParser.java +++ b/src/main/java/lotto/parser/PurchaseAmountParser.java @@ -1,19 +1,20 @@ package lotto.parser; public class PurchaseAmountParser { + private static final String ERROR_EMPTY_INPUT = "[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."; + private static final String ERROR_INVALID_FORMAT = "[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + /** + * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(κ΅¬μž… κΈˆμ•‘)을 μ •μˆ˜λ‘œ νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. + * + * @param input μ‚¬μš©μžκ°€ μž…λ ₯ν•œ κ΅¬μž… κΈˆμ•‘ λ¬Έμžμ—΄ + * @return νŒŒμ‹±λœ κ΅¬μž… κΈˆμ•‘ (μ •μˆ˜) + * @throws IllegalArgumentException μž…λ ₯이 null λ˜λŠ” 빈 λ¬Έμžμ—΄μΈ 경우 + * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 + */ public static int parse(String input) { - if (input == null || input.trim().isEmpty()) { - throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); - } - return parseToInteger(input.trim()); - } - - private static int parseToInteger(String input) { - try { - return Integer.parseInt(input); - } catch (NumberFormatException e) { - throw new NumberFormatException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } + ParserUtils.validateNotNullOrEmpty(input, ERROR_EMPTY_INPUT); + String trimmedInput = input.trim(); + return ParserUtils.parseToInteger(trimmedInput, ERROR_INVALID_FORMAT); } } \ No newline at end of file diff --git a/src/main/java/lotto/parser/WinningNumbersParser.java b/src/main/java/lotto/parser/WinningNumbersParser.java index c578b87198..259fc0e0b1 100644 --- a/src/main/java/lotto/parser/WinningNumbersParser.java +++ b/src/main/java/lotto/parser/WinningNumbersParser.java @@ -2,52 +2,47 @@ import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; public class WinningNumbersParser { private static final String SEPARATOR = ","; + private static final String ERROR_EMPTY_INPUT = "[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; + private static final String ERROR_EMPTY_VALUE = "[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."; + private static final String ERROR_INVALID_FORMAT = "[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; /** * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(당첨 번호)을 μ •μˆ˜ 리슀트둜 νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. * * @param input μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 당첨 번호 λ¬Έμžμ—΄ (μ‰Όν‘œλ‘œ ꡬ뢄됨) * @return νŒŒμ‹±λœ 당첨 번호 리슀트 (μ •μˆ˜) - * @throws IllegalArgumentException μž…λ ₯이 null, 빈 λ¬Έμžμ—΄, λ˜λŠ” μ‰Όν‘œ ꡬ뢄 ν˜•μ‹μ΄ μ•„λ‹Œ 경우 + * @throws IllegalArgumentException μž…λ ₯이 null, 빈 λ¬Έμžμ—΄, λ˜λŠ” 빈 값이 ν¬ν•¨λœ 경우 * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 */ public static List parse(String input) { - validateNotNullOrEmpty(input); + ParserUtils.validateNotNullOrEmpty(input, ERROR_EMPTY_INPUT); List numberStrings = splitBySeparator(input); return parseToIntegers(numberStrings); } - private static void validateNotNullOrEmpty(String input) { - if (Objects.isNull(input) || input.trim().isEmpty()) { - throw new IllegalArgumentException("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); - } - } - private static List splitBySeparator(String input) { List parts = Arrays.stream(input.split(SEPARATOR, -1)) .map(String::trim) .collect(Collectors.toList()); + validateNoEmptyValues(parts); + return parts; + } + + private static void validateNoEmptyValues(List parts) { if (parts.stream().anyMatch(String::isEmpty)) { - throw new IllegalArgumentException("[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new IllegalArgumentException(ERROR_EMPTY_VALUE); } - - return parts; } private static List parseToIntegers(List numberStrings) { - try { - return numberStrings.stream() - .map(Integer::parseInt) - .collect(Collectors.toList()); - } catch (NumberFormatException e) { - throw new NumberFormatException("[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } + return numberStrings.stream() + .map(str -> ParserUtils.parseToInteger(str, ERROR_INVALID_FORMAT)) + .collect(Collectors.toList()); } } \ No newline at end of file diff --git a/src/main/java/lotto/validator/InputValidator.java b/src/main/java/lotto/validator/InputValidator.java index ee370a05a7..9dd427c303 100644 --- a/src/main/java/lotto/validator/InputValidator.java +++ b/src/main/java/lotto/validator/InputValidator.java @@ -9,48 +9,45 @@ public class InputValidator { private static final int LOTTO_PRICE = 1000; private static final int MIN_PURCHASE_AMOUNT = 1000; - private static final int ZERO_AMOUNT = 0; + /** + * ꡬ맀 κΈˆμ•‘μ΄ μœ νš¨ν•œμ§€ κ²€μ¦ν•©λ‹ˆλ‹€. + * + * @param amount ꡬ맀 κΈˆμ•‘ + * @throws IllegalArgumentException κΈˆμ•‘μ΄ 0 μ΄ν•˜, 1000원 미만, 1000원 λ‹¨μœ„κ°€ μ•„λ‹Œ 경우 + */ public static void validatePurchaseAmount(int amount) { - validateNegative(amount); - validateZero(amount); - validateMinAmount(amount); - validateDivisibleByUnit(amount); - } - - private static void validateNegative(int amount) { - if (amount < ZERO_AMOUNT) { - throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } - } - - private static void validateZero(int amount) { - if (amount == ZERO_AMOUNT) { + if (amount <= 0) { throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); } - } - - private static void validateMinAmount(int amount) { - // 1~999 μ‚¬μ΄μ˜ κΈˆμ•‘μ„ κ²€μ¦ν•©λ‹ˆλ‹€. - if (amount > ZERO_AMOUNT && amount < MIN_PURCHASE_AMOUNT) { + if (amount < MIN_PURCHASE_AMOUNT) { throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); } - } - - private static void validateDivisibleByUnit(int amount) { - // validateMinAmountλ₯Ό ν†΅κ³Όν•œ, 1000 μ΄μƒμ˜ κΈˆμ•‘ 쀑 λ‹¨μœ„κ°€ λ§žμ§€ μ•ŠλŠ” 경우λ₯Ό κ²€μ¦ν•©λ‹ˆλ‹€. - if (amount >= MIN_PURCHASE_AMOUNT && amount % LOTTO_PRICE != 0) { + if (amount % LOTTO_PRICE != 0) { throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); } } + /** + * 당첨 λ²ˆν˜Έκ°€ μœ νš¨ν•œμ§€ κ²€μ¦ν•©λ‹ˆλ‹€. + * Lotto 객체 생성을 톡해 null, 개수, λ²”μœ„, 쀑볡 검증을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. + * + * @param numbers 당첨 번호 리슀트 + * @throws IllegalArgumentException Lotto 생성 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠλŠ” 경우 + */ public static void validateWinningNumbers(List numbers) { - // Lotto μƒμ„±μžμ— Listλ₯Ό μ „λ‹¬ν•˜μ—¬ null, 개수, λ²”μœ„, 쀑볡 검증을 μœ„μž„ new Lotto(numbers); } + /** + * λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ μœ νš¨ν•œμ§€ κ²€μ¦ν•©λ‹ˆλ‹€. + * WinningLotto 객체 생성을 톡해 λ²”μœ„ 및 쀑볡 검증을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. + * + * @param winningNumbers 당첨 번호 Lotto 객체 + * @param bonusNumber λ³΄λ„ˆμŠ€ 번호 + * @throws IllegalArgumentException WinningLotto 생성 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠλŠ” 경우 + */ public static void validateBonusNumber(Lotto winningNumbers, int bonusNumber) { - // WinningLotto μƒμ„±μžλ₯Ό ν˜ΈμΆœν•˜μ—¬ λ²”μœ„ 및 쀑볡 검증을 μœ„μž„ new WinningLotto(winningNumbers, bonusNumber); } } \ No newline at end of file diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index 1ef244d4a3..a15c7d1f52 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -10,6 +10,49 @@ import static org.assertj.core.api.Assertions.assertThat; class ApplicationTest extends NsTest { + private static final String ERROR_MESSAGE = "[ERROR]"; + + @Test + void κΈ°λŠ₯_ν…ŒμŠ€νŠΈ() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("8000", "1,2,3,4,5,6", "7"); + assertThat(output()).contains( + "8개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€.", + "[8, 21, 23, 41, 42, 43]", + "[3, 5, 11, 16, 32, 38]", + "[7, 11, 16, 35, 36, 44]", + "[1, 8, 11, 31, 41, 42]", + "[13, 14, 16, 38, 42, 45]", + "[7, 11, 30, 40, 42, 43]", + "[2, 13, 22, 32, 38, 45]", + "[1, 3, 5, 14, 22, 45]", + "3개 일치 (5,000원) - 1개", + "4개 일치 (50,000원) - 0개", + "5개 일치 (1,500,000원) - 0개", + "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (30,000,000원) - 0개", + "6개 일치 (2,000,000,000원) - 0개", + "총 수읡λ₯ μ€ 62.5%μž…λ‹ˆλ‹€." + ); + }, + List.of(8, 21, 23, 41, 42, 43), + List.of(3, 5, 11, 16, 32, 38), + List.of(7, 11, 16, 35, 36, 44), + List.of(1, 8, 11, 31, 41, 42), + List.of(13, 14, 16, 38, 42, 45), + List.of(7, 11, 30, 40, 42, 43), + List.of(2, 13, 22, 32, 38, 45), + List.of(1, 3, 5, 14, 22, 45) + ); + } + + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { + assertSimpleTest(() -> { + runException("1000j"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } @Override public void runMain() { diff --git a/src/test/java/lotto/parser/ParserUtilsTest.java b/src/test/java/lotto/parser/ParserUtilsTest.java new file mode 100644 index 0000000000..9f5edebc33 --- /dev/null +++ b/src/test/java/lotto/parser/ParserUtilsTest.java @@ -0,0 +1,157 @@ +package lotto.parser; + +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; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ParserUtilsTest { + + @DisplayName("정상1: μœ νš¨ν•œ λ¬Έμžμ—΄μ€ 검증을 ν†΅κ³Όν•œλ‹€") + @Test + void test1() { + // Given + String input = "8000"; + String errorMessage = "[ERROR] ν…ŒμŠ€νŠΈ μ—λŸ¬"; + + // When & Then + ParserUtils.validateNotNullOrEmpty(input, errorMessage); + // μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ§€ μ•ŠμœΌλ©΄ ν…ŒμŠ€νŠΈ 톡과 + } + + @DisplayName("정상2: 곡백이 ν¬ν•¨λœ λ¬Έμžμ—΄μ€ 검증을 ν†΅κ³Όν•œλ‹€") + @Test + void test2() { + // Given + String input = " 8000 "; + String errorMessage = "[ERROR] ν…ŒμŠ€νŠΈ μ—λŸ¬"; + + // When & Then + ParserUtils.validateNotNullOrEmpty(input, errorMessage); + // μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ§€ μ•ŠμœΌλ©΄ ν…ŒμŠ€νŠΈ 톡과 + } + + @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @Test + void test3() { + // Given + String input = null; + String errorMessage = "[ERROR] null μž…λ ₯ μ—λŸ¬"; + + // When & Then + assertThatThrownBy(() -> ParserUtils.validateNotNullOrEmpty(input, errorMessage)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(errorMessage); + } + + @DisplayName("μ˜ˆμ™Έ2: μž…λ ₯이 빈 λ¬Έμžμ—΄μ΄λ©΄ IllegalArgumentException λ°œμƒ") + @Test + void test4() { + // Given + String input = ""; + String errorMessage = "[ERROR] 빈 λ¬Έμžμ—΄ μ—λŸ¬"; + + // When & Then + assertThatThrownBy(() -> ParserUtils.validateNotNullOrEmpty(input, errorMessage)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(errorMessage); + } + + @DisplayName("μ˜ˆμ™Έ3: μž…λ ₯이 곡백만 있으면 IllegalArgumentException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {" ", " "}) + void test5(String input) { + // Given + String errorMessage = "[ERROR] 곡백 μ—λŸ¬"; + + // When & Then + assertThatThrownBy(() -> ParserUtils.validateNotNullOrEmpty(input, errorMessage)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(errorMessage); + } + + + @DisplayName("정상3: μœ νš¨ν•œ λ¬Έμžμ—΄μ„ μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") + @Test + void test6() { + // Given + String input = "8000"; + String errorMessage = "[ERROR] νŒŒμ‹± μ—λŸ¬"; + + // When + int result = ParserUtils.parseToInteger(input, errorMessage); + + // Then + assertThat(result).isEqualTo(8000); + } + + @DisplayName("정상4: 음수 λ¬Έμžμ—΄μ„ 음수 μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") + @Test + void test7() { + // Given + String input = "-1000"; + String errorMessage = "[ERROR] νŒŒμ‹± μ—λŸ¬"; + + // When + int result = ParserUtils.parseToInteger(input, errorMessage); + + // Then + assertThat(result).isEqualTo(-1000); + } + + @DisplayName("정상5: 0을 μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") + @Test + void test8() { + // Given + String input = "0"; + String errorMessage = "[ERROR] νŒŒμ‹± μ—λŸ¬"; + + // When + int result = ParserUtils.parseToInteger(input, errorMessage); + + // Then + assertThat(result).isEqualTo(0); + } + + @DisplayName("μ˜ˆμ™Έ4: μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜λ©΄ NumberFormatException λ°œμƒ") + @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") + @ValueSource(strings = {"abc", "1000원", "1,000", "1.5"}) + void test9(String input) { + // Given + String errorMessage = "[ERROR] 숫자 ν˜•μ‹ μ—λŸ¬"; + + // When & Then + assertThatThrownBy(() -> ParserUtils.parseToInteger(input, errorMessage)) + .isInstanceOf(NumberFormatException.class) + .hasMessage(errorMessage); + } + + @DisplayName("μ˜ˆμ™Έ5: 빈 λ¬Έμžμ—΄μ€ NumberFormatException λ°œμƒ") + @Test + void test10() { + // Given + String input = ""; + String errorMessage = "[ERROR] 빈 λ¬Έμžμ—΄ νŒŒμ‹± μ—λŸ¬"; + + // When & Then + assertThatThrownBy(() -> ParserUtils.parseToInteger(input, errorMessage)) + .isInstanceOf(NumberFormatException.class) + .hasMessage(errorMessage); + } + + @DisplayName("μ˜ˆμ™Έ6: Integer λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ NumberFormatException λ°œμƒ") + @Test + void test11() { + // Given + String input = "9999999999999999999"; // Integer.MAX_VALUEλ₯Ό 초과 + String errorMessage = "[ERROR] λ²”μœ„ 초과 μ—λŸ¬"; + + // When & Then + assertThatThrownBy(() -> ParserUtils.parseToInteger(input, errorMessage)) + .isInstanceOf(NumberFormatException.class) + .hasMessage(errorMessage); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/parser/PurchaseAmountParserTest.java b/src/test/java/lotto/parser/PurchaseAmountParserTest.java index 9200c6652e..0400779ec1 100644 --- a/src/test/java/lotto/parser/PurchaseAmountParserTest.java +++ b/src/test/java/lotto/parser/PurchaseAmountParserTest.java @@ -1,7 +1,6 @@ package lotto.parser; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -24,9 +23,22 @@ void test1() { assertThat(result).isEqualTo(8000); } - @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @DisplayName("정상2: μ•žλ’€ 곡백이 ν¬ν•¨λœ λ¬Έμžμ—΄λ„ μ •μˆ˜λ‘œ νŒŒμ‹±ν•œλ‹€") @Test void test2() { + // Given + String input = " 8000 "; + + // When + int result = PurchaseAmountParser.parse(input); + + // Then + assertThat(result).isEqualTo(8000); + } + + @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @Test + void test3() { String input = null; assertThatThrownBy(() -> PurchaseAmountParser.parse(input)) @@ -34,21 +46,19 @@ void test2() { .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); } - @Tag("error") @DisplayName("μ˜ˆμ™Έ2: μž…λ ₯이 빈 λ¬Έμžμ—΄ λ˜λŠ” 곡백이면 IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") @ValueSource(strings = {"", " "}) - void test3(String input) { + void test4(String input) { assertThatThrownBy(() -> PurchaseAmountParser.parse(input)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."); } - @Tag("error") @DisplayName("μ˜ˆμ™Έ3: μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜λ©΄ NumberFormatException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") @ValueSource(strings = {"1000원", "a1000", "1,000"}) - void test4(String input) { + void test5(String input) { assertThatThrownBy(() -> PurchaseAmountParser.parse(input)) .isInstanceOf(NumberFormatException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); diff --git a/src/test/java/lotto/parser/WinningNumbersParserTest.java b/src/test/java/lotto/parser/WinningNumbersParserTest.java index 84cdcab7df..aee508835b 100644 --- a/src/test/java/lotto/parser/WinningNumbersParserTest.java +++ b/src/test/java/lotto/parser/WinningNumbersParserTest.java @@ -12,7 +12,6 @@ class WinningNumbersParserTest { - @DisplayName("정상1: μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ λ¬Έμžμ—΄μ„ μ •μˆ˜ 리슀트둜 νŒŒμ‹±ν•œλ‹€") @Test void test1() { @@ -26,9 +25,22 @@ void test1() { assertThat(result).containsExactly(1, 2, 3, 4, 5, 6); } - @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @DisplayName("정상2: 곡백이 ν¬ν•¨λœ λ¬Έμžμ—΄λ„ μ •μˆ˜ 리슀트둜 νŒŒμ‹±ν•œλ‹€") @Test void test2() { + // Given + String input = " 1 , 2 , 3 , 4 , 5 , 6 "; + + // When + List result = WinningNumbersParser.parse(input); + + // Then + assertThat(result).containsExactly(1, 2, 3, 4, 5, 6); + } + + @DisplayName("μ˜ˆμ™Έ1: μž…λ ₯이 null이면 IllegalArgumentException λ°œμƒ") + @Test + void test3() { String input = null; assertThatThrownBy(() -> WinningNumbersParser.parse(input)) @@ -38,8 +50,8 @@ void test2() { @DisplayName("μ˜ˆμ™Έ2: μž…λ ₯이 빈 λ¬Έμžμ—΄ λ˜λŠ” 곡백이면 IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") - @ValueSource(strings = {"", " "}) - void test3(String input) { + @ValueSource(strings = {"", " ", " "}) + void test4(String input) { assertThatThrownBy(() -> WinningNumbersParser.parse(input)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); @@ -47,7 +59,7 @@ void test3(String input) { @DisplayName("μ˜ˆμ™Έ3: μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λ˜λ©΄ NumberFormatException λ°œμƒ") @Test - void test4() { + void test5() { String input = "1,2,a,4,5,6"; assertThatThrownBy(() -> WinningNumbersParser.parse(input)) @@ -55,9 +67,9 @@ void test4() { .hasMessage("[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); } - @DisplayName("μ˜ˆμ™Έ4: μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ ν•­λͺ©μ΄ 빈 κ°’(곡백 포함)이면 IllegalArgumentException λ°œμƒ") + @DisplayName("μ˜ˆμ™Έ4: μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ ν•­λͺ©μ΄ 빈 값이면 IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: \"{0}\"") - @ValueSource(strings = {"1,,3,4,5,6", "1,2, ,4,5,6", "1,2,3,4,5,6,"}) + @ValueSource(strings = {"1,,3,4,5,6", "1,2, ,4,5,6"}) void test6(String input) { assertThatThrownBy(() -> WinningNumbersParser.parse(input)) .isInstanceOf(IllegalArgumentException.class) @@ -66,7 +78,7 @@ void test6(String input) { @DisplayName("μ˜ˆμ™Έ5: λ¬Έμžμ—΄μ΄ μ‰Όν‘œλ‘œ λλ‚˜λŠ” 경우 IllegalArgumentException λ°œμƒ") @Test - void test5() { + void test7() { String input = "1,2,3,4,5,6,"; assertThatThrownBy(() -> WinningNumbersParser.parse(input)) diff --git a/src/test/java/lotto/validator/InputValidatorTest.java b/src/test/java/lotto/validator/InputValidatorTest.java index 6041029322..c95ee4cfa2 100644 --- a/src/test/java/lotto/validator/InputValidatorTest.java +++ b/src/test/java/lotto/validator/InputValidatorTest.java @@ -51,17 +51,8 @@ void test4() { @DisplayName("μ˜ˆμ™Έ1: 음수λ₯Ό μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") - @ValueSource(ints = {-1, -1000}) + @ValueSource(ints = {-1, -1000, 0}) void test5(int amount) { - assertThatThrownBy(() -> validatePurchaseAmount(amount)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μ–‘μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } - - @DisplayName("μ˜ˆμ™Έ2: 0을 μž…λ ₯ν•˜λ©΄ IllegalArgumentException λ°œμƒ") - @Test - void test6() { - int amount = 0; assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); @@ -70,7 +61,7 @@ void test6() { @DisplayName("μ˜ˆμ™Έ3: 1,000원 미만(1~999)이면 IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {1, 500, 999}) - void test7(int amount) { + void test6(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); @@ -79,7 +70,7 @@ void test7(int amount) { @DisplayName("μ˜ˆμ™Έ4: 1,000μ›μœΌλ‘œ λ‚˜λˆ„μ–΄λ–¨μ–΄μ§€μ§€ μ•ŠμœΌλ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {1001, 1500, 8999}) - void test8(int amount) { + void test7(int amount) { assertThatThrownBy(() -> validatePurchaseAmount(amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); @@ -88,7 +79,7 @@ void test8(int amount) { @DisplayName("μ˜ˆμ™Έ5: 번호 λ¦¬μŠ€νŠΈκ°€ null이면 IllegalArgumentException λ°œμƒ") @Test - void test9() { + void test8() { assertThatThrownBy(() -> validateWinningNumbers(null)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); @@ -96,7 +87,7 @@ void test9() { @DisplayName("μ˜ˆμ™Έ6: λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹ˆλ©΄ IllegalArgumentException λ°œμƒ") @Test - void test10() { + void test9() { List invalidSizeNumbers = List.of(1, 2, 3, 4, 5); assertThatThrownBy(() -> validateWinningNumbers(invalidSizeNumbers)) .isInstanceOf(IllegalArgumentException.class) @@ -106,7 +97,7 @@ void test10() { @DisplayName("μ˜ˆμ™Έ7: 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {0, 46}) - void test11(int invalidNumber) { + void test10(int invalidNumber) { List outOfRangeNumbers = List.of(1, 2, 3, 4, 5, invalidNumber); assertThatThrownBy(() -> validateWinningNumbers(outOfRangeNumbers)) .isInstanceOf(IllegalArgumentException.class) @@ -115,7 +106,7 @@ void test11(int invalidNumber) { @DisplayName("μ˜ˆμ™Έ8 μ€‘λ³΅λœ λ²ˆν˜Έκ°€ 있으면 IllegalArgumentException λ°œμƒ") @Test - void test12() { + void test11() { List duplicateNumbers = List.of(1, 2, 3, 4, 5, 5); assertThatThrownBy(() -> validateWinningNumbers(duplicateNumbers)) .isInstanceOf(IllegalArgumentException.class) @@ -125,7 +116,7 @@ void test12() { @DisplayName("μ˜ˆμ™Έ12: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 1~45 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λ©΄ IllegalArgumentException λ°œμƒ") @ParameterizedTest(name = "μž…λ ₯: {0}") @ValueSource(ints = {0, 46}) - void test13(int invalidNumber) { + void test12(int invalidNumber) { Lotto validWinningLottoNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); assertThatThrownBy(() -> validateBonusNumber(validWinningLottoNumbers, invalidNumber)) @@ -135,7 +126,7 @@ void test13(int invalidNumber) { @DisplayName("μ˜ˆμ™Έ13: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ IllegalArgumentException λ°œμƒ") @Test - void test14() { + void test13() { Lotto validWinningLottoNumbers = new Lotto(List.of(1, 2, 3, 4, 5, 6)); int duplicateNumber = 6; From 09082b2be47a3e1605b074b375bff2e4b9919d55 Mon Sep 17 00:00:00 2001 From: believeme Date: Wed, 5 Nov 2025 08:57:59 +0900 Subject: [PATCH 19/21] =?UTF-8?q?feat(common):=20LottoConstants=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EC=83=81=EC=88=98=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1,=20=EC=97=90=EB=9F=AC=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=EB=8F=84=20=EA=B3=B5=ED=86=B5=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=A0=EA=BB=80?= =?UTF-8?q?=EC=A7=80=20=EA=B3=A0=EB=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/lotto/common/LottoConstants.java | 21 +++++++++++++ .../java/lotto/service/LottoGameService.java | 8 +++-- src/main/java/lotto/util/LottoGenerator.java | 6 +--- .../java/lotto/validator/InputValidator.java | 5 ++- .../java/lotto/util/LottoGeneratorTest.java | 31 ++++++++++++++----- 5 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 src/main/java/lotto/common/LottoConstants.java diff --git a/src/main/java/lotto/common/LottoConstants.java b/src/main/java/lotto/common/LottoConstants.java new file mode 100644 index 0000000000..faffb036b0 --- /dev/null +++ b/src/main/java/lotto/common/LottoConstants.java @@ -0,0 +1,21 @@ +package lotto.common; + +/** + * 둜또 κ²Œμž„μ—μ„œ μ‚¬μš©ν•˜λŠ” 곡톡 μƒμˆ˜λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. + */ +public class LottoConstants { + + private LottoConstants() { + // μƒμˆ˜ ν΄λž˜μŠ€λŠ” μΈμŠ€ν„΄μŠ€ν™” λ°©μ§€ + } + + // 둜또 번호 λ²”μœ„ + public static final int MIN_LOTTO_NUMBER = 1; + public static final int MAX_LOTTO_NUMBER = 45; + public static final int LOTTO_NUMBER_COUNT = 6; + + // 둜또 가격 + public static final int LOTTO_PRICE = 1000; + + public static final int MIN_PURCHASE_AMOUNT = 1000; +} \ No newline at end of file diff --git a/src/main/java/lotto/service/LottoGameService.java b/src/main/java/lotto/service/LottoGameService.java index 9c02995a1a..71227b0a6a 100644 --- a/src/main/java/lotto/service/LottoGameService.java +++ b/src/main/java/lotto/service/LottoGameService.java @@ -1,5 +1,7 @@ package lotto.service; +import static lotto.common.LottoConstants.*; + import lotto.domain.*; import lotto.parser.BonusNumberParser; import lotto.parser.PurchaseAmountParser; @@ -50,8 +52,8 @@ private int inputPurchaseAmount() { try { String input = inputView.inputPurchaseAmount(); int amount = PurchaseAmountParser.parse(input); - if (amount % LottoGenerator.LOTTO_PRICE != 0) { - throw new IllegalArgumentException("κ΅¬μž… κΈˆμ•‘μ€ " + LottoGenerator.LOTTO_PRICE + "원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + if (amount % LOTTO_PRICE != 0) { + throw new IllegalArgumentException("κ΅¬μž… κΈˆμ•‘μ€ " + LOTTO_PRICE + "원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); } return amount; } catch (IllegalArgumentException e) { @@ -61,7 +63,7 @@ private int inputPurchaseAmount() { } private LottoTickets buyLottos(int purchaseAmount) { - int lottoCount = purchaseAmount / LottoGenerator.LOTTO_PRICE; + int lottoCount = purchaseAmount / LOTTO_PRICE; List lottos = lottoGenerator.generateLottos(lottoCount); return new LottoTickets(lottos); } diff --git a/src/main/java/lotto/util/LottoGenerator.java b/src/main/java/lotto/util/LottoGenerator.java index 4bcd67f615..4b1aa77bbf 100644 --- a/src/main/java/lotto/util/LottoGenerator.java +++ b/src/main/java/lotto/util/LottoGenerator.java @@ -7,13 +7,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static lotto.domain.Lotto.LOTTO_NUMBER_COUNT; -import static lotto.domain.Lotto.MAX_LOTTO_NUMBER; -import static lotto.domain.Lotto.MIN_LOTTO_NUMBER; +import static lotto.common.LottoConstants.*; public class LottoGenerator { - public static final int LOTTO_PRICE = 1000; public static List generateLottos(int purchaseAmount) { int lottoCount = purchaseAmount / LOTTO_PRICE; diff --git a/src/main/java/lotto/validator/InputValidator.java b/src/main/java/lotto/validator/InputValidator.java index 9dd427c303..d9a410a9ff 100644 --- a/src/main/java/lotto/validator/InputValidator.java +++ b/src/main/java/lotto/validator/InputValidator.java @@ -1,5 +1,7 @@ package lotto.validator; +import static lotto.common.LottoConstants.*; + import java.util.List; import lotto.domain.Lotto; @@ -7,9 +9,6 @@ public class InputValidator { - private static final int LOTTO_PRICE = 1000; - private static final int MIN_PURCHASE_AMOUNT = 1000; - /** * ꡬ맀 κΈˆμ•‘μ΄ μœ νš¨ν•œμ§€ κ²€μ¦ν•©λ‹ˆλ‹€. * diff --git a/src/test/java/lotto/util/LottoGeneratorTest.java b/src/test/java/lotto/util/LottoGeneratorTest.java index 379586e081..b7ce1dc7fc 100644 --- a/src/test/java/lotto/util/LottoGeneratorTest.java +++ b/src/test/java/lotto/util/LottoGeneratorTest.java @@ -10,20 +10,18 @@ import java.util.List; import java.util.Set; -import static lotto.domain.Lotto.LOTTO_NUMBER_COUNT; -import static lotto.domain.Lotto.MAX_LOTTO_NUMBER; -import static lotto.domain.Lotto.MIN_LOTTO_NUMBER; +import static lotto.common.LottoConstants.*; import static org.assertj.core.api.Assertions.assertThat; class LottoGeneratorTest { @DisplayName("정상1: κ΅¬μž… κΈˆμ•‘μ— 따라 μ •ν™•ν•œ 개수의 둜또λ₯Ό μƒμ„±ν•œλ‹€") - @ParameterizedTest(name = "κ΅¬μž… κΈˆμ•‘: {0}원, μ˜ˆμƒ 개수: {1}개") + @ParameterizedTest(name = "κ΅¬μž… κΈˆμ•‘: {0}원") @ValueSource(ints = {1000, 5000, 10000}) void test1(int purchaseAmount) { // Given & When List lottos = LottoGenerator.generateLottos(purchaseAmount); - int expectedCount = purchaseAmount / 1000; + int expectedCount = purchaseAmount / LOTTO_PRICE; // Then assertThat(lottos).hasSize(expectedCount); @@ -32,7 +30,7 @@ void test1(int purchaseAmount) { @DisplayName("정상2: μƒμ„±λœ 각 λ‘œλ˜κ°€ μœ νš¨ν•œ 번호 6개λ₯Ό ν¬ν•¨ν•˜κ³  μžˆλŠ”μ§€ κ²€μ¦ν•œλ‹€") @Test void test2() { - // Given: 3000원 ꡬ맀 (3개 둜또 생성) + // Given int purchaseAmount = 3000; // When @@ -52,8 +50,25 @@ void test2() { Set uniqueNumbers = new HashSet<>(numbers); assertThat(uniqueNumbers).hasSize(LOTTO_NUMBER_COUNT); - // 4. μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ μžˆμ–΄μ•Ό ν•œλ‹€. (Lotto 객체 λ‚΄λΆ€μ—μ„œ μ²˜λ¦¬λ˜λ―€λ‘œ 확인) + // 4. μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ μžˆμ–΄μ•Ό ν•œλ‹€. assertThat(numbers).isSorted(); } } -} + + @DisplayName("정상3: μ—¬λŸ¬ 둜또 생성 μ‹œ 각 λ‘œλ˜κ°€ μ„œλ‘œ λ‹€λ₯Έ 번호 쑰합을 κ°€μ§ˆ κ°€λŠ₯성이 μžˆλ‹€") + @Test + void test3() { + // Given: 100개 둜또 생성 + int purchaseAmount = 100_000; + + // When + List lottos = LottoGenerator.generateLottos(purchaseAmount); + + // Then + // 100개 λ‘œλ˜κ°€ λͺ¨λ‘ 같을 ν™•λ₯ μ€ 거의 0μ΄λ―€λ‘œ, μ΅œμ†Œ 2κ°œλŠ” λ‹€λ₯Ό κ²ƒμœΌλ‘œ κΈ°λŒ€ + Set> uniqueLottos = new HashSet<>(); + lottos.forEach(lotto -> uniqueLottos.add(lotto.getNumbers())); + + assertThat(uniqueLottos.size()).isGreaterThan(1); + } +} \ No newline at end of file From 35ca8ebfb9db70d1b0f91e2ed1e9ca22fd268f3c Mon Sep 17 00:00:00 2001 From: believeme Date: Wed, 5 Nov 2025 09:42:11 +0900 Subject: [PATCH 20/21] =?UTF-8?q?fix:=20=EC=97=90=EB=9F=AC=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EC=83=81=EC=88=98=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 75 +++++++++++++ src/main/java/lotto/common/ErrorMessages.java | 40 +++++++ src/main/java/lotto/common/ViewMessages.java | 23 ++++ src/main/java/lotto/domain/Lotto.java | 18 ++-- src/main/java/lotto/domain/LottoResult.java | 15 +-- src/main/java/lotto/domain/LottoTickets.java | 6 +- src/main/java/lotto/domain/WinningLotto.java | 11 +- .../java/lotto/parser/BonusNumberParser.java | 8 +- .../lotto/parser/PurchaseAmountParser.java | 8 +- .../lotto/parser/WinningNumbersParser.java | 11 +- .../java/lotto/validator/InputValidator.java | 16 +-- src/main/java/lotto/view/InputView.java | 27 +++-- src/main/java/lotto/view/OutputView.java | 66 ++++++------ .../java/lotto/domain/WinningLottoTest.java | 2 +- src/test/java/lotto/view/OutputViewTest.java | 102 ++++++++++++++++++ 15 files changed, 338 insertions(+), 90 deletions(-) create mode 100644 src/main/java/lotto/common/ErrorMessages.java create mode 100644 src/main/java/lotto/common/ViewMessages.java create mode 100644 src/test/java/lotto/view/OutputViewTest.java diff --git a/README.md b/README.md index bc6eeef8c3..1f4cdf770b 100644 --- a/README.md +++ b/README.md @@ -501,6 +501,81 @@ if (lottoTickets == null) { --- +## πŸ“Š μƒμˆ˜ 관리 ν˜„ν™© + +### ErrorMessages μ‚¬μš© ν˜„ν™© + +| μƒμˆ˜ 이름 | μ‚¬μš© μœ„μΉ˜ | μƒνƒœ | +|-----------|-----------|------| +| `PURCHASE_AMOUNT_ZERO` | InputValidator | βœ… | +| `PURCHASE_AMOUNT_MINIMUM` | InputValidator | βœ… | +| `PURCHASE_AMOUNT_UNIT` | InputValidator | βœ… | +| `PURCHASE_AMOUNT_EMPTY` | PurchaseAmountParser | βœ… | +| `PURCHASE_AMOUNT_INVALID_FORMAT` | PurchaseAmountParser | βœ… | +| `LOTTO_NUMBERS_NULL` | Lotto, WinningLotto | βœ… | +| `LOTTO_NUMBERS_SIZE` | Lotto | βœ… | +| `LOTTO_NUMBERS_DUPLICATE` | Lotto | βœ… | +| `LOTTO_NUMBERS_RANGE` | Lotto | βœ… | +| `WINNING_NUMBERS_EMPTY` | WinningNumbersParser | βœ… | +| `WINNING_NUMBERS_INVALID_FORMAT` | WinningNumbersParser | βœ… | +| `WINNING_NUMBERS_EMPTY_VALUE` | WinningNumbersParser | βœ… | +| `BONUS_NUMBER_EMPTY` | BonusNumberParser | βœ… | +| `BONUS_NUMBER_INVALID_FORMAT` | BonusNumberParser | βœ… | +| `BONUS_NUMBER_RANGE` | WinningLotto | βœ… | +| `BONUS_NUMBER_DUPLICATE` | WinningLotto | βœ… | +| `LOTTO_TICKETS_NULL` | LottoTickets | βœ… | +| `LOTTO_TICKETS_EMPTY` | LottoTickets | βœ… | +| `LOTTO_RESULT_PURCHASE_AMOUNT` | LottoResult | βœ… | + +### ViewMessages μ‚¬μš© ν˜„ν™© + +| μƒμˆ˜ 이름 | μ‚¬μš© μœ„μΉ˜ | μš©λ„ | μƒνƒœ | +|-----------|-----------|------|------| +| `INPUT_PURCHASE_AMOUNT` | InputView | ꡬ맀 κΈˆμ•‘ μž…λ ₯ μ•ˆλ‚΄ | βœ… | +| `INPUT_WINNING_NUMBERS` | InputView | 당첨 번호 μž…λ ₯ μ•ˆλ‚΄ | βœ… | +| `INPUT_BONUS_NUMBER` | InputView | λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ μ•ˆλ‚΄ | βœ… | +| `PURCHASED_LOTTO_COUNT_FORMAT` | OutputView | ꡬ맀 개수 좜λ ₯ | βœ… | +| `WINNING_STATISTICS_HEADER` | OutputView | 톡계 헀더 좜λ ₯ | βœ… | +| `SEPARATOR` | OutputView | ꡬ뢄선 좜λ ₯ | βœ… | +| `RANK_FORMAT` | OutputView | λ“±μˆ˜λ³„ 당첨 κ²°κ³Ό 좜λ ₯ | βœ… | +| `PROFIT_RATE_FORMAT` | OutputView | 수읡λ₯  좜λ ₯ | βœ… | + +### LottoConstants μ‚¬μš© ν˜„ν™© + +| μƒμˆ˜ 이름 | κ°’ | μ‚¬μš© μœ„μΉ˜ | μƒνƒœ | +|-----------|-----|-----------|------| +| `MIN_LOTTO_NUMBER` | 1 | Lotto, WinningLotto, LottoGenerator | βœ… | +| `MAX_LOTTO_NUMBER` | 45 | Lotto, WinningLotto, LottoGenerator | βœ… | +| `LOTTO_NUMBER_COUNT` | 6 | Lotto, LottoGenerator | βœ… | +| `LOTTO_PRICE` | 1000 | InputValidator, LottoGenerator | βœ… | + +--- + +## 🎨 μƒμˆ˜ 섀계 원칙 + +### 1. 단일 μ±…μž„ 원칙 (SRP) +- **ErrorMessages**: μ—λŸ¬ λ©”μ‹œμ§€λ§Œ 관리 +- **ViewMessages**: View 좜λ ₯ λ©”μ‹œμ§€λ§Œ 관리 +- **LottoConstants**: λΉ„μ¦ˆλ‹ˆμŠ€ μƒμˆ˜λ§Œ 관리 + +### 2. 쀑볡 제거 (DRY) +- λͺ¨λ“  λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄μ„ μƒμˆ˜λ‘œ μΆ”μΆœ +- ν•œ κ³³μ—μ„œ μˆ˜μ •ν•˜λ©΄ 전체에 반영 + +### 3. λͺ…ν™•ν•œ 넀이밍 +- μƒμˆ˜ μ΄λ¦„λ§ŒμœΌλ‘œ μš©λ„λ₯Ό νŒŒμ•… κ°€λŠ₯ +- 접두사λ₯Ό ν†΅ν•œ κ·Έλ£Ήν•‘ (PURCHASE_AMOUNT_*, LOTTO_NUMBERS_*) + +### 4. νŒ¨ν‚€μ§€ ꡬ쑰 +``` +lotto/ +└── common/ + β”œβ”€β”€ ErrorMessages.java # μ—λŸ¬ λ©”μ‹œμ§€ + β”œβ”€β”€ ViewMessages.java # View λ©”μ‹œμ§€ + └── LottoConstants.java # λΉ„μ¦ˆλ‹ˆμŠ€ μƒμˆ˜ +``` +--- + ## πŸ§ͺ ν…ŒμŠ€νŠΈ ### ν…ŒμŠ€νŠΈ ꡬ쑰 diff --git a/src/main/java/lotto/common/ErrorMessages.java b/src/main/java/lotto/common/ErrorMessages.java new file mode 100644 index 0000000000..1bba3f67b4 --- /dev/null +++ b/src/main/java/lotto/common/ErrorMessages.java @@ -0,0 +1,40 @@ +package lotto.common; + +/** + * 둜또 κ²Œμž„μ—μ„œ μ‚¬μš©ν•˜λŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. + */ +public class ErrorMessages { + + private ErrorMessages() { + // μƒμˆ˜ ν΄λž˜μŠ€λŠ” μΈμŠ€ν„΄μŠ€ν™” λ°©μ§€ + } + + // ꡬ맀 κΈˆμ•‘ κ΄€λ ¨ + public static final String PURCHASE_AMOUNT_ZERO = "[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."; + public static final String PURCHASE_AMOUNT_MINIMUM = "[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."; + public static final String PURCHASE_AMOUNT_UNIT = "[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String PURCHASE_AMOUNT_EMPTY = "[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."; + public static final String PURCHASE_AMOUNT_INVALID_FORMAT = "[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + + // 둜또 번호 κ΄€λ ¨ + public static final String LOTTO_NUMBERS_NULL = "[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; + public static final String LOTTO_NUMBERS_SIZE = "[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String LOTTO_NUMBERS_DUPLICATE = "[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."; + public static final String LOTTO_NUMBERS_RANGE = "[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + + // 당첨 번호 κ΄€λ ¨ + public static final String WINNING_NUMBERS_EMPTY = "[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; + public static final String WINNING_NUMBERS_INVALID_FORMAT = "[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String WINNING_NUMBERS_EMPTY_VALUE = "[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."; + + // λ³΄λ„ˆμŠ€ 번호 κ΄€λ ¨ + public static final String BONUS_NUMBER_EMPTY = "[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; + public static final String BONUS_NUMBER_INVALID_FORMAT = "[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String BONUS_NUMBER_RANGE = "[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; + public static final String BONUS_NUMBER_DUPLICATE = "[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."; + + // 기타 + public static final String LOTTO_TICKETS_NULL = "[ERROR] 둜또 λͺ©λ‘μ΄ μ—†μŠ΅λ‹ˆλ‹€."; + public static final String LOTTO_TICKETS_EMPTY = "[ERROR] μ΅œμ†Œ 1개 μ΄μƒμ˜ 둜또λ₯Ό ꡬ맀해야 ν•©λ‹ˆλ‹€."; + public static final String LOTTO_RESULT_PURCHASE_AMOUNT = "[ERROR] ꡬ맀 κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."; +} \ No newline at end of file diff --git a/src/main/java/lotto/common/ViewMessages.java b/src/main/java/lotto/common/ViewMessages.java new file mode 100644 index 0000000000..f0b7d64a26 --- /dev/null +++ b/src/main/java/lotto/common/ViewMessages.java @@ -0,0 +1,23 @@ +package lotto.common; + +/** + * 둜또 κ²Œμž„μ—μ„œ μ‚¬μš©ν•˜λŠ” 좜λ ₯ λ©”μ‹œμ§€λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. + */ +public class ViewMessages { + + private ViewMessages() { + // μƒμˆ˜ ν΄λž˜μŠ€λŠ” μΈμŠ€ν„΄μŠ€ν™” λ°©μ§€ + } + + // μž…λ ₯ μ•ˆλ‚΄ + public static final String INPUT_PURCHASE_AMOUNT = "κ΅¬μž…κΈˆμ•‘μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + public static final String INPUT_WINNING_NUMBERS = "당첨 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + public static final String INPUT_BONUS_NUMBER = "λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + + // κ²°κ³Ό 좜λ ₯ + public static final String PURCHASED_LOTTO_COUNT_FORMAT = "%d개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€."; + public static final String WINNING_STATISTICS_HEADER = "당첨 톡계"; + public static final String SEPARATOR = "---"; + public static final String RANK_FORMAT = "%s (%s원) - %d개"; + public static final String PROFIT_RATE_FORMAT = "총 수읡λ₯ μ€ %.1f%%μž…λ‹ˆλ‹€."; +} \ No newline at end of file diff --git a/src/main/java/lotto/domain/Lotto.java b/src/main/java/lotto/domain/Lotto.java index 893a449967..011cb381a9 100644 --- a/src/main/java/lotto/domain/Lotto.java +++ b/src/main/java/lotto/domain/Lotto.java @@ -3,11 +3,10 @@ import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; +import static lotto.common.ErrorMessages.*; +import static lotto.common.LottoConstants.*; public class Lotto { - public static final int LOTTO_NUMBER_COUNT = 6; - public static final int MIN_LOTTO_NUMBER = 1; - public static final int MAX_LOTTO_NUMBER = 45; private final List numbers; @@ -27,19 +26,18 @@ private void validate(List numbers) { private void validateNotNull(List numbers) { if (numbers == null) { - throw new IllegalArgumentException("[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + throw new IllegalArgumentException(LOTTO_NUMBERS_NULL); } } private void validateSize(List numbers) { if (numbers.size() != LOTTO_NUMBER_COUNT) { - throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 6κ°œμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(LOTTO_NUMBERS_SIZE); } } - private void validateDuplicate(List numbers) { if (numbers.size() != new HashSet<>(numbers).size()) { - throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new IllegalArgumentException(LOTTO_NUMBERS_DUPLICATE); } } @@ -48,7 +46,7 @@ private void validateRange(List numbers) { .anyMatch(number -> number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER); if (isOutOfRange) { - throw new IllegalArgumentException("[ERROR] 둜또 λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(LOTTO_NUMBERS_RANGE); } } @@ -68,8 +66,6 @@ public boolean contains(int number) { @Override public String toString() { - return numbers.stream() - .map(String::valueOf) - .collect(Collectors.joining(", ", "[", "]")); + return numbers.toString(); } } \ No newline at end of file diff --git a/src/main/java/lotto/domain/LottoResult.java b/src/main/java/lotto/domain/LottoResult.java index f3c09ff00b..ae1a748cb4 100644 --- a/src/main/java/lotto/domain/LottoResult.java +++ b/src/main/java/lotto/domain/LottoResult.java @@ -1,5 +1,7 @@ package lotto.domain; +import static lotto.common.ErrorMessages.*; + import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -13,7 +15,7 @@ public class LottoResult { public LottoResult(List ranks, int purchaseAmount) { if (purchaseAmount < MIN_PURCHASE_AMOUNT) { - throw new IllegalArgumentException("[ERROR] ꡬ맀 κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(LOTTO_RESULT_PURCHASE_AMOUNT); } this.purchaseAmount = purchaseAmount; this.result = countRanks(ranks); @@ -48,14 +50,13 @@ public long calculateTotalProfit() { } /** - * 총 수읡λ₯ μ„ κ³„μ‚°ν•©λ‹ˆλ‹€. (μ†Œμˆ˜μ  λ‘˜μ§Έ μžλ¦¬μ—μ„œ λ°˜μ˜¬λ¦Όν•˜μ—¬ 첫째 μžλ¦¬κΉŒμ§€ ν‘œμ‹œ) - * 예: 62.5%, 100.0% + * 총 수읡λ₯ μ„ κ³„μ‚°ν•©λ‹ˆλ‹€. + * + * @return 수읡λ₯  (νΌμ„ΌνŠΈ, μ†Œμˆ˜μ  포함) */ - public String calculateProfitRate() { + public double calculateProfitRate() { long totalProfit = calculateTotalProfit(); - double profitRate = (double) totalProfit * 100 / purchaseAmount; - - return String.format(PROFIT_RATE_FORMAT, profitRate); + return (double) totalProfit * 100 / purchaseAmount; } public Map getResult() { diff --git a/src/main/java/lotto/domain/LottoTickets.java b/src/main/java/lotto/domain/LottoTickets.java index 589db2dceb..2108a8fc94 100644 --- a/src/main/java/lotto/domain/LottoTickets.java +++ b/src/main/java/lotto/domain/LottoTickets.java @@ -1,5 +1,7 @@ package lotto.domain; +import static lotto.common.ErrorMessages.*; + import java.util.Collections; import java.util.List; import java.util.Objects; @@ -12,11 +14,11 @@ public class LottoTickets { public LottoTickets(List lottos) { if (Objects.isNull(lottos)) { - throw new IllegalArgumentException("[ERROR] 둜또 λͺ©λ‘μ΄ μ—†μŠ΅λ‹ˆλ‹€."); + throw new IllegalArgumentException(LOTTO_TICKETS_NULL); } if (lottos.size() < MIN_LOTTO_COUNT) { - throw new IllegalArgumentException("[ERROR] μ΅œμ†Œ 1개 μ΄μƒμ˜ 둜또λ₯Ό ꡬ맀해야 ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(LOTTO_TICKETS_EMPTY); } this.lottos = lottos; diff --git a/src/main/java/lotto/domain/WinningLotto.java b/src/main/java/lotto/domain/WinningLotto.java index c38ccaf75b..ddc15483d8 100644 --- a/src/main/java/lotto/domain/WinningLotto.java +++ b/src/main/java/lotto/domain/WinningLotto.java @@ -1,11 +1,12 @@ package lotto.domain; +import static lotto.common.ErrorMessages.*; +import static lotto.common.LottoConstants.*; + import java.util.List; import java.util.Objects; public class WinningLotto { - private static final int MIN_LOTTO_NUMBER = 1; - private static final int MAX_LOTTO_NUMBER = 45; private final Lotto winningNumbers; private final int bonusNumber; @@ -23,19 +24,19 @@ private void validate(Lotto winningNumbers, int bonusNumber) { private void validateNotNull(Lotto winningNumbers) { if (Objects.isNull(winningNumbers)) { - throw new IllegalArgumentException("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + throw new IllegalArgumentException(LOTTO_NUMBERS_NULL); } } private void validateBonusNumberRange(int bonusNumber) { if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) { - throw new IllegalArgumentException("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 1λΆ€ν„° 45 μ‚¬μ΄μ˜ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(BONUS_NUMBER_RANGE); } } private void validateDuplicateWithWinningNumbers(Lotto winningNumbers, int bonusNumber) { if (winningNumbers.contains(bonusNumber)) { - throw new IllegalArgumentException("[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” 당첨 λ²ˆν˜Έμ™€ 쀑볡될 수 μ—†μŠ΅λ‹ˆλ‹€."); + throw new IllegalArgumentException(BONUS_NUMBER_DUPLICATE); } } diff --git a/src/main/java/lotto/parser/BonusNumberParser.java b/src/main/java/lotto/parser/BonusNumberParser.java index a3601c36a5..6bb392d378 100644 --- a/src/main/java/lotto/parser/BonusNumberParser.java +++ b/src/main/java/lotto/parser/BonusNumberParser.java @@ -1,8 +1,8 @@ package lotto.parser; +import static lotto.common.ErrorMessages.*; + public class BonusNumberParser { - private static final String ERROR_EMPTY_INPUT = "[ERROR] λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; - private static final String ERROR_INVALID_FORMAT = "[ERROR] λ³΄λ„ˆμŠ€ λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; /** * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(λ³΄λ„ˆμŠ€ 번호)을 μ •μˆ˜λ‘œ νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. @@ -13,8 +13,8 @@ public class BonusNumberParser { * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 */ public static int parse(String input) { - ParserUtils.validateNotNullOrEmpty(input, ERROR_EMPTY_INPUT); + ParserUtils.validateNotNullOrEmpty(input, BONUS_NUMBER_EMPTY); String trimmedInput = input.trim(); - return ParserUtils.parseToInteger(trimmedInput, ERROR_INVALID_FORMAT); + return ParserUtils.parseToInteger(trimmedInput, BONUS_NUMBER_INVALID_FORMAT); } } \ No newline at end of file diff --git a/src/main/java/lotto/parser/PurchaseAmountParser.java b/src/main/java/lotto/parser/PurchaseAmountParser.java index af544f8b80..9d62cc9c69 100644 --- a/src/main/java/lotto/parser/PurchaseAmountParser.java +++ b/src/main/java/lotto/parser/PurchaseAmountParser.java @@ -1,8 +1,8 @@ package lotto.parser; +import static lotto.common.ErrorMessages.*; + public class PurchaseAmountParser { - private static final String ERROR_EMPTY_INPUT = "[ERROR] κ΅¬μž… κΈˆμ•‘μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."; - private static final String ERROR_INVALID_FORMAT = "[ERROR] κ΅¬μž… κΈˆμ•‘μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; /** * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(κ΅¬μž… κΈˆμ•‘)을 μ •μˆ˜λ‘œ νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. @@ -13,8 +13,8 @@ public class PurchaseAmountParser { * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 */ public static int parse(String input) { - ParserUtils.validateNotNullOrEmpty(input, ERROR_EMPTY_INPUT); + ParserUtils.validateNotNullOrEmpty(input, PURCHASE_AMOUNT_EMPTY); String trimmedInput = input.trim(); - return ParserUtils.parseToInteger(trimmedInput, ERROR_INVALID_FORMAT); + return ParserUtils.parseToInteger(trimmedInput, PURCHASE_AMOUNT_INVALID_FORMAT); } } \ No newline at end of file diff --git a/src/main/java/lotto/parser/WinningNumbersParser.java b/src/main/java/lotto/parser/WinningNumbersParser.java index 259fc0e0b1..143d9f99b7 100644 --- a/src/main/java/lotto/parser/WinningNumbersParser.java +++ b/src/main/java/lotto/parser/WinningNumbersParser.java @@ -1,14 +1,13 @@ package lotto.parser; +import static lotto.common.ErrorMessages.*; + import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class WinningNumbersParser { private static final String SEPARATOR = ","; - private static final String ERROR_EMPTY_INPUT = "[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."; - private static final String ERROR_EMPTY_VALUE = "[ERROR] 당첨 λ²ˆν˜ΈλŠ” 빈 값일 수 μ—†μŠ΅λ‹ˆλ‹€."; - private static final String ERROR_INVALID_FORMAT = "[ERROR] 당첨 λ²ˆν˜ΈλŠ” μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€."; /** * μ‚¬μš©μž μž…λ ₯ λ¬Έμžμ—΄(당첨 번호)을 μ •μˆ˜ 리슀트둜 νŒŒμ‹±ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€. @@ -19,7 +18,7 @@ public class WinningNumbersParser { * @throws NumberFormatException μˆ«μžκ°€ μ•„λ‹Œ 값이 ν¬ν•¨λœ 경우 */ public static List parse(String input) { - ParserUtils.validateNotNullOrEmpty(input, ERROR_EMPTY_INPUT); + ParserUtils.validateNotNullOrEmpty(input, WINNING_NUMBERS_EMPTY); List numberStrings = splitBySeparator(input); return parseToIntegers(numberStrings); @@ -36,13 +35,13 @@ private static List splitBySeparator(String input) { private static void validateNoEmptyValues(List parts) { if (parts.stream().anyMatch(String::isEmpty)) { - throw new IllegalArgumentException(ERROR_EMPTY_VALUE); + throw new IllegalArgumentException(WINNING_NUMBERS_EMPTY_VALUE); } } private static List parseToIntegers(List numberStrings) { return numberStrings.stream() - .map(str -> ParserUtils.parseToInteger(str, ERROR_INVALID_FORMAT)) + .map(str -> ParserUtils.parseToInteger(str, WINNING_NUMBERS_INVALID_FORMAT)) .collect(Collectors.toList()); } } \ No newline at end of file diff --git a/src/main/java/lotto/validator/InputValidator.java b/src/main/java/lotto/validator/InputValidator.java index d9a410a9ff..f359d409b6 100644 --- a/src/main/java/lotto/validator/InputValidator.java +++ b/src/main/java/lotto/validator/InputValidator.java @@ -1,12 +1,16 @@ package lotto.validator; -import static lotto.common.LottoConstants.*; - import java.util.List; import lotto.domain.Lotto; import lotto.domain.WinningLotto; +import static lotto.common.LottoConstants.*; +import static lotto.common.ErrorMessages.*; + +/** + * μ‚¬μš©μž μž…λ ₯값을 κ²€μ¦ν•˜λŠ” μœ ν‹Έλ¦¬ν‹° ν΄λž˜μŠ€μž…λ‹ˆλ‹€. + */ public class InputValidator { /** @@ -17,13 +21,13 @@ public class InputValidator { */ public static void validatePurchaseAmount(int amount) { if (amount <= 0) { - throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 0보닀 컀야 ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(PURCHASE_AMOUNT_ZERO); } - if (amount < MIN_PURCHASE_AMOUNT) { - throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 이상이어야 ν•©λ‹ˆλ‹€."); + if (amount < LOTTO_PRICE) { + throw new IllegalArgumentException(PURCHASE_AMOUNT_MINIMUM); } if (amount % LOTTO_PRICE != 0) { - throw new IllegalArgumentException("[ERROR] κ΅¬μž… κΈˆμ•‘μ€ 1,000원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + throw new IllegalArgumentException(PURCHASE_AMOUNT_UNIT); } } diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index 2fb87cdac3..827592492f 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -2,14 +2,17 @@ import camp.nextstep.edu.missionutils.Console; +import static lotto.common.ViewMessages.*; + +/** + * μ‚¬μš©μž μž…λ ₯을 λ°›λŠ” View ν΄λž˜μŠ€μž…λ‹ˆλ‹€. + */ public class InputView { - private static final String INPUT_PURCHASE_AMOUNT = "κ΅¬μž…κΈˆμ•‘μ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”."; - private static final String INPUT_WINNING_NUMBERS = "당첨 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."; - private static final String INPUT_BONUS_NUMBER = "λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯ν•΄ μ£Όμ„Έμš”."; - private static final String NEW_LINE = ""; /** - * 5.1 ꡬ맀 κΈˆμ•‘ μž…λ ₯ + * ꡬ맀 κΈˆμ•‘μ„ μž…λ ₯λ°›μŠ΅λ‹ˆλ‹€. + * + * @return μ‚¬μš©μžκ°€ μž…λ ₯ν•œ ꡬ맀 κΈˆμ•‘ λ¬Έμžμ—΄ */ public String inputPurchaseAmount() { System.out.println(INPUT_PURCHASE_AMOUNT); @@ -17,20 +20,24 @@ public String inputPurchaseAmount() { } /** - * 5.2 당첨 번호 μž…λ ₯ + * 당첨 번호λ₯Ό μž…λ ₯λ°›μŠ΅λ‹ˆλ‹€. + * + * @return μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 당첨 번호 λ¬Έμžμ—΄ (μ‰Όν‘œλ‘œ ꡬ뢄) */ public String inputWinningNumbers() { - System.out.println(NEW_LINE); + System.out.println(); System.out.println(INPUT_WINNING_NUMBERS); return Console.readLine(); } /** - * 5.3 λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ + * λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯λ°›μŠ΅λ‹ˆλ‹€. + * + * @return μ‚¬μš©μžκ°€ μž…λ ₯ν•œ λ³΄λ„ˆμŠ€ 번호 λ¬Έμžμ—΄ */ public String inputBonusNumber() { - System.out.println(NEW_LINE); + System.out.println(); System.out.println(INPUT_BONUS_NUMBER); return Console.readLine(); } -} +} \ No newline at end of file diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index d24228d6a1..a5a509fd8b 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -7,72 +7,70 @@ import java.text.DecimalFormat; import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; +import static lotto.common.ViewMessages.*; + +/** + * κ²Œμž„ κ²°κ³Όλ₯Ό 좜λ ₯ν•˜λŠ” View ν΄λž˜μŠ€μž…λ‹ˆλ‹€. + */ public class OutputView { - private static final String NEW_LINE = ""; - private static final String PURCHASED_LOTTO_COUNT_FORMAT = "%d개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€."; - private static final String WINNING_STATISTICS_HEADER = "당첨 톡계"; - private static final String SEPARATOR = "---"; - private static final String RANK_FORMAT = "%s (%s원) - %d개"; - private static final String PROFIT_RATE_FORMAT = "총 수읡λ₯ μ€ %.1f%%μž…λ‹ˆλ‹€."; - private static final String ERROR_PREFIX = "[ERROR] "; private static final DecimalFormat MONEY_FORMAT = new DecimalFormat("#,###"); /** - * 5.4 κ΅¬λ§€ν•œ 둜또 좜λ ₯ - * 각 둜또 λ²ˆν˜ΈλŠ” μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ 좜λ ₯됨 (Lotto κ°μ²΄μ—μ„œ μ •λ ¬ 보μž₯) + * κ΅¬λ§€ν•œ 둜또 λͺ©λ‘μ„ 좜λ ₯ν•©λ‹ˆλ‹€. + * 각 둜또 λ²ˆν˜ΈλŠ” μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ 좜λ ₯λ©λ‹ˆλ‹€. + * + * @param lottos κ΅¬λ§€ν•œ 둜또 리슀트 */ public void printLottoTickets(List lottos) { - System.out.println(NEW_LINE); - System.out.println(String.format(PURCHASED_LOTTO_COUNT_FORMAT, lottos.size())); - lottos.stream() - .map(Lotto::toString) // Lotto ν΄λž˜μŠ€μ— toString()이 κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€κ³  κ°€μ • - .forEach(System.out::println); + System.out.println(); + System.out.printf(PURCHASED_LOTTO_COUNT_FORMAT + "%n", lottos.size()); + lottos.forEach(lotto -> System.out.println(lotto.toString())); } /** - * 5.5 당첨 톡계 좜λ ₯ - * 5λ“±λΆ€ν„° 1λ“±κΉŒμ§€ μˆœμ„œλŒ€λ‘œ 좜λ ₯ + * 당첨 톡계λ₯Ό 좜λ ₯ν•©λ‹ˆλ‹€. + * 5λ“±λΆ€ν„° 1λ“±κΉŒμ§€ μˆœμ„œλŒ€λ‘œ 좜λ ₯ν•©λ‹ˆλ‹€. + * + * @param lottoResult 당첨 κ²°κ³Ό */ public void printWinningStatistics(LottoResult lottoResult) { - System.out.println(NEW_LINE); + System.out.println(); System.out.println(WINNING_STATISTICS_HEADER); System.out.println(SEPARATOR); - // 5λ“±, 4λ“±, 3λ“±, 2λ“±, 1λ“± μˆœμ„œλ‘œ 좜λ ₯ Stream.of(Rank.values()) .filter(Rank::isWinning) - .sorted(Comparator.comparingInt(Rank::getPrize)) // μƒκΈˆ κΈ°μ€€ μ˜€λ¦„μ°¨μˆœ μ •λ ¬ (5λ“± -> 1λ“±) + .sorted(Comparator.comparingInt(Rank::getPrize)) .forEach(rank -> printRank(rank, lottoResult)); } private void printRank(Rank rank, LottoResult lottoResult) { String prize = MONEY_FORMAT.format(rank.getPrize()); - String description = rank.getDescription(); // LottoResultTest.java의 Rank Enum에 getDescription()이 μžˆλ‹€κ³  κ°€μ • + String description = rank.getDescription(); + int count = lottoResult.getPrizeCount(rank); - System.out.println(String.format( - RANK_FORMAT, - description, - prize, - lottoResult.getPrizeCount(rank) - )); + System.out.printf(RANK_FORMAT + "%n", description, prize, count); } /** - * 5.6 수읡λ₯  좜λ ₯ - * μ†Œμˆ˜μ  λ‘˜μ§Έ μžλ¦¬μ—μ„œ 반올림 (%.1f μ‚¬μš©) + * 수읡λ₯ μ„ 좜λ ₯ν•©λ‹ˆλ‹€. + * μ†Œμˆ˜μ  첫째 μžλ¦¬κΉŒμ§€ ν‘œμ‹œν•˜λ©° λ°˜μ˜¬λ¦Όλ©λ‹ˆλ‹€. + * + * @param lottoResult 당첨 κ²°κ³Ό */ public void printProfitRate(LottoResult lottoResult) { - String profitRate = lottoResult.calculateProfitRate(); - System.out.println(String.format(PROFIT_RATE_FORMAT, profitRate)); + double profitRate = lottoResult.calculateProfitRate(); + System.out.printf(PROFIT_RATE_FORMAT + "%n", profitRate); } /** - * 5.7 μ—λŸ¬ λ©”μ‹œμ§€ 좜λ ₯ + * μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•©λ‹ˆλ‹€. + * + * @param message μ—λŸ¬ λ©”μ‹œμ§€ (이미 [ERROR] 접두사 포함) */ public void printErrorMessage(String message) { - System.out.println(ERROR_PREFIX + message); + System.out.println(message); } -} +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/WinningLottoTest.java b/src/test/java/lotto/domain/WinningLottoTest.java index b62c32889f..0b26ab4534 100644 --- a/src/test/java/lotto/domain/WinningLottoTest.java +++ b/src/test/java/lotto/domain/WinningLottoTest.java @@ -101,7 +101,7 @@ void test6(int matchCount) { void test7() { assertThatThrownBy(() -> new WinningLotto(null, 7)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("[ERROR] 당첨 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); + .hasMessageContaining("[ERROR] 둜또 번호λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."); } @DisplayName("μ˜ˆμ™Έ2: λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λ˜λ©΄ IllegalArgumentException λ°œμƒ") diff --git a/src/test/java/lotto/view/OutputViewTest.java b/src/test/java/lotto/view/OutputViewTest.java new file mode 100644 index 0000000000..f069c57f58 --- /dev/null +++ b/src/test/java/lotto/view/OutputViewTest.java @@ -0,0 +1,102 @@ +package lotto.view; + +import lotto.domain.Lotto; +import lotto.domain.LottoResult; +import lotto.domain.Rank; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class OutputViewTest { + + private OutputView outputView; + private ByteArrayOutputStream outputStream; + private PrintStream originalOut; + + @BeforeEach + void setUp() { + outputView = new OutputView(); + outputStream = new ByteArrayOutputStream(); + originalOut = System.out; + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + void tearDown() { + System.setOut(originalOut); + } + + @DisplayName("정상1: κ΅¬λ§€ν•œ 둜또 λͺ©λ‘μ„ 좜λ ₯ν•œλ‹€") + @Test + void test1() { + // Given + List lottos = List.of( + new Lotto(List.of(1, 2, 3, 4, 5, 6)), + new Lotto(List.of(7, 8, 9, 10, 11, 12)) + ); + + // When + outputView.printLottoTickets(lottos); + + // Then + String output = outputStream.toString(); + assertThat(output).contains("2개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€."); + assertThat(output).contains("[1, 2, 3, 4, 5, 6]"); + assertThat(output).contains("[7, 8, 9, 10, 11, 12]"); + } + + @DisplayName("정상2: 당첨 톡계λ₯Ό 좜λ ₯ν•œλ‹€") + @Test + void test2() { + // Given + List ranks = List.of(Rank.FIFTH, Rank.THIRD); + LottoResult lottoResult = new LottoResult(ranks, 8000); + + // When + outputView.printWinningStatistics(lottoResult); + + // Then + String output = outputStream.toString(); + assertThat(output).contains("당첨 톡계"); + assertThat(output).contains("---"); + assertThat(output).contains("3개 일치"); + assertThat(output).contains("5개 일치"); + } + + @DisplayName("정상3: 수읡λ₯ μ„ 좜λ ₯ν•œλ‹€") + @Test + void test3() { + // Given + List ranks = List.of(Rank.FIFTH); + LottoResult lottoResult = new LottoResult(ranks, 8000); + + // When + outputView.printProfitRate(lottoResult); + + // Then + String output = outputStream.toString(); + assertThat(output).contains("총 수읡λ₯ μ€"); + assertThat(output).contains("%μž…λ‹ˆλ‹€."); + } + + @DisplayName("μ˜ˆμ™Έ1: μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•œλ‹€") + @Test + void test4() { + // Given + String errorMessage = "[ERROR] ν…ŒμŠ€νŠΈ μ—λŸ¬"; + + // When + outputView.printErrorMessage(errorMessage); + + // Then + String output = outputStream.toString(); + assertThat(output).contains("[ERROR] ν…ŒμŠ€νŠΈ μ—λŸ¬"); + } +} \ No newline at end of file From 685070ae1aa0e7868365e509a22af15530a86c76 Mon Sep 17 00:00:00 2001 From: believeme Date: Thu, 6 Nov 2025 10:22:15 +0900 Subject: [PATCH 21/21] =?UTF-8?q?fix:=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20=EC=99=84=EB=A3=8C,=20Service=EB=8A=94=20View?= =?UTF-8?q?=EB=A7=8C=20=EC=9E=88=EA=B8=B0=EC=97=90=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=95=88=ED=95=B4=EB=8F=84=20=EC=83=81?= =?UTF-8?q?=EA=B4=80=20=EC=97=86=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/lotto/Application.java | 18 +- .../java/lotto/service/LottoGameService.java | 110 ++++++------- src/test/java/lotto/ApplicationTest.java | 155 ++++++++++++------ .../LottoGameServiceWithMockitoTest.java | 106 ++++++++++++ 4 files changed, 264 insertions(+), 125 deletions(-) create mode 100644 src/test/java/lotto/service/LottoGameServiceWithMockitoTest.java diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index 36d3e30b2a..ff20da09af 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,20 +1,10 @@ package lotto; import lotto.service.LottoGameService; -import lotto.util.LottoGenerator; -import lotto.view.InputView; -import lotto.view.OutputView; public class Application { - public static void main(String[] args) { - // μ˜μ‘΄μ„± μ£Όμž… - InputView inputView = new InputView(); - OutputView outputView = new OutputView(); - LottoGenerator lottoGenerator = new LottoGenerator(); - - LottoGameService gameService = new LottoGameService(inputView, outputView, lottoGenerator); - - // κ²Œμž„ μ‹€ν–‰ - gameService.run(); + public static void main(String[] args) { + LottoGameService lottoGameService = new LottoGameService(); + lottoGameService.run(); } -} +} \ No newline at end of file diff --git a/src/main/java/lotto/service/LottoGameService.java b/src/main/java/lotto/service/LottoGameService.java index 71227b0a6a..7e92cb350e 100644 --- a/src/main/java/lotto/service/LottoGameService.java +++ b/src/main/java/lotto/service/LottoGameService.java @@ -1,60 +1,64 @@ package lotto.service; -import static lotto.common.LottoConstants.*; - -import lotto.domain.*; +import lotto.domain.Lotto; +import lotto.domain.LottoResult; +import lotto.domain.LottoTickets; +import lotto.domain.WinningLotto; import lotto.parser.BonusNumberParser; import lotto.parser.PurchaseAmountParser; import lotto.parser.WinningNumbersParser; import lotto.util.LottoGenerator; +import lotto.validator.InputValidator; import lotto.view.InputView; import lotto.view.OutputView; import java.util.List; -import java.util.stream.Collectors; +/** + * 둜또 κ²Œμž„μ˜ 전체 흐름을 κ΄€λ¦¬ν•˜λŠ” μ„œλΉ„μŠ€ ν΄λž˜μŠ€μž…λ‹ˆλ‹€. + */ public class LottoGameService { private final InputView inputView; private final OutputView outputView; - private final LottoGenerator lottoGenerator; - public LottoGameService(InputView inputView, OutputView outputView, LottoGenerator lottoGenerator) { + public LottoGameService() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + } + + public LottoGameService(InputView inputView, OutputView outputView) { this.inputView = inputView; this.outputView = outputView; - this.lottoGenerator = lottoGenerator; } /** - * 6.1 LottoGameService κ΅¬ν˜„ + * 둜또 κ²Œμž„μ„ μ‹€ν–‰ν•©λ‹ˆλ‹€. */ public void run() { - try { - int purchaseAmount = inputPurchaseAmount(); - LottoTickets lottoTickets = buyLottos(purchaseAmount); - - outputView.printLottoTickets(lottoTickets.getLottos()); + int purchaseAmount = inputPurchaseAmount(); + LottoTickets lottoTickets = purchaseLottos(purchaseAmount); + outputView.printLottoTickets(lottoTickets.getLottos()); - WinningLotto winningLotto = inputWinningLotto(); + Lotto winningNumbers = inputWinningNumbers(); + int bonusNumber = inputBonusNumber(winningNumbers); - LottoResult lottoResult = calculateResult(lottoTickets, winningLotto, purchaseAmount); + WinningLotto winningLotto = new WinningLotto(winningNumbers, bonusNumber); + LottoResult result = lottoTickets.compareWithWinningLotto(winningLotto, purchaseAmount); - outputView.printWinningStatistics(lottoResult); - outputView.printProfitRate(lottoResult); - } catch (Exception e) { - outputView.printErrorMessage(e.getMessage()); - // μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ’…λ£Œ (μ—¬κΈ°μ„œ μ˜ˆμ™Έλ₯Ό 작으면 λ©”μΈμ—μ„œ μΆ”κ°€ μ²˜λ¦¬κ°€ ν•„μš” μ—†μŒ) - } + outputView.printWinningStatistics(result); + outputView.printProfitRate(result); } - // region 1. ꡬ맀 κΈˆμ•‘ μž…λ ₯ 및 둜또 ꡬ맀 + /** + * ꡬ맀 κΈˆμ•‘μ„ μž…λ ₯λ°›κ³  κ²€μ¦ν•©λ‹ˆλ‹€. + * μ˜ˆμ™Έ λ°œμƒ μ‹œ μž¬μž…λ ₯을 μš”μ²­ν•©λ‹ˆλ‹€. + */ private int inputPurchaseAmount() { while (true) { try { String input = inputView.inputPurchaseAmount(); int amount = PurchaseAmountParser.parse(input); - if (amount % LOTTO_PRICE != 0) { - throw new IllegalArgumentException("κ΅¬μž… κΈˆμ•‘μ€ " + LOTTO_PRICE + "원 λ‹¨μœ„μ—¬μ•Ό ν•©λ‹ˆλ‹€."); - } + InputValidator.validatePurchaseAmount(amount); return amount; } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); @@ -62,31 +66,24 @@ private int inputPurchaseAmount() { } } - private LottoTickets buyLottos(int purchaseAmount) { - int lottoCount = purchaseAmount / LOTTO_PRICE; - List lottos = lottoGenerator.generateLottos(lottoCount); + /** + * ꡬ맀 κΈˆμ•‘λ§ŒνΌ 둜또λ₯Ό μžλ™ μƒμ„±ν•©λ‹ˆλ‹€. + */ + private LottoTickets purchaseLottos(int purchaseAmount) { + List lottos = LottoGenerator.generateLottos(purchaseAmount); return new LottoTickets(lottos); } - // endregion - - // region 2. 당첨 번호, λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ - private WinningLotto inputWinningLotto() { - Lotto winningNumbers = inputWinningNumbers(); - while (true) { - try { - int bonusNumber = inputBonusNumber(); - return new WinningLotto(winningNumbers, bonusNumber); - } catch (IllegalArgumentException e) { - outputView.printErrorMessage(e.getMessage()); - } - } - } + /** + * 당첨 번호λ₯Ό μž…λ ₯λ°›κ³  κ²€μ¦ν•©λ‹ˆλ‹€. + * μ˜ˆμ™Έ λ°œμƒ μ‹œ μž¬μž…λ ₯을 μš”μ²­ν•©λ‹ˆλ‹€. + */ private Lotto inputWinningNumbers() { while (true) { try { String input = inputView.inputWinningNumbers(); List numbers = WinningNumbersParser.parse(input); + InputValidator.validateWinningNumbers(numbers); return new Lotto(numbers); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); @@ -94,31 +91,20 @@ private Lotto inputWinningNumbers() { } } - private int inputBonusNumber() { + /** + * λ³΄λ„ˆμŠ€ 번호λ₯Ό μž…λ ₯λ°›κ³  κ²€μ¦ν•©λ‹ˆλ‹€. + * μ˜ˆμ™Έ λ°œμƒ μ‹œ μž¬μž…λ ₯을 μš”μ²­ν•©λ‹ˆλ‹€. + */ + private int inputBonusNumber(Lotto winningNumbers) { while (true) { try { String input = inputView.inputBonusNumber(); - return BonusNumberParser.parse(input); + int bonusNumber = BonusNumberParser.parse(input); + InputValidator.validateBonusNumber(winningNumbers, bonusNumber); + return bonusNumber; } catch (IllegalArgumentException e) { outputView.printErrorMessage(e.getMessage()); - // 이 λ‚΄λΆ€ while 문은 WinningLotto 생성 쀑 λ°œμƒν•˜λŠ” μ˜ˆμ™Έ 처리용. - // WinningLotto의 μƒμ„±μžμ—μ„œ WinningNumberμ™€μ˜ 쀑볡 검증도 μˆ˜ν–‰λ˜λ―€λ‘œ, - // InputViewμ—μ„œ 받은 λ¬Έμžμ—΄μ„ 숫자둜 νŒŒμ‹±ν•˜λŠ” μ˜ˆμ™Έλ§Œ μ—¬κΈ°μ„œ 작고, - // WinningLotto κ΄€λ ¨ μ˜ˆμ™ΈλŠ” inputWinningLotto()의 λ°”κΉ₯ whileλ¬Έμ—μ„œ 작음 - // (μš”κ΅¬μ‚¬ν•­: λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ μ˜ˆμ™Έ β†’ λ³΄λ„ˆμŠ€ 번호 μž…λ ₯λΆ€ν„° μž¬μ‹œλ„) - // LottoGameService의 inputWinningLotto() λ©”μ„œλ“œμ˜ while(true) 루프가 - // 이 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±μ‹œν‚€λ„λ‘ κ΅¬ν˜„λ¨. } } } - // endregion - - // region 3. κ²°κ³Ό 계산 및 좜λ ₯ - private LottoResult calculateResult(LottoTickets lottoTickets, WinningLotto winningLotto, int purchaseAmount) { - List ranks = lottoTickets.getLottos().stream() - .map(winningLotto::match) - .collect(Collectors.toList()); - return new LottoResult(ranks, purchaseAmount); - } - // endregion -} +} \ No newline at end of file diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1f52..a322c2dd20 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -1,6 +1,7 @@ package lotto; import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; @@ -10,52 +11,108 @@ import static org.assertj.core.api.Assertions.assertThat; class ApplicationTest extends NsTest { - private static final String ERROR_MESSAGE = "[ERROR]"; - - @Test - void κΈ°λŠ₯_ν…ŒμŠ€νŠΈ() { - assertRandomUniqueNumbersInRangeTest( - () -> { - run("8000", "1,2,3,4,5,6", "7"); - assertThat(output()).contains( - "8개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€.", - "[8, 21, 23, 41, 42, 43]", - "[3, 5, 11, 16, 32, 38]", - "[7, 11, 16, 35, 36, 44]", - "[1, 8, 11, 31, 41, 42]", - "[13, 14, 16, 38, 42, 45]", - "[7, 11, 30, 40, 42, 43]", - "[2, 13, 22, 32, 38, 45]", - "[1, 3, 5, 14, 22, 45]", - "3개 일치 (5,000원) - 1개", - "4개 일치 (50,000원) - 0개", - "5개 일치 (1,500,000원) - 0개", - "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (30,000,000원) - 0개", - "6개 일치 (2,000,000,000원) - 0개", - "총 수읡λ₯ μ€ 62.5%μž…λ‹ˆλ‹€." - ); - }, - List.of(8, 21, 23, 41, 42, 43), - List.of(3, 5, 11, 16, 32, 38), - List.of(7, 11, 16, 35, 36, 44), - List.of(1, 8, 11, 31, 41, 42), - List.of(13, 14, 16, 38, 42, 45), - List.of(7, 11, 30, 40, 42, 43), - List.of(2, 13, 22, 32, 38, 45), - List.of(1, 3, 5, 14, 22, 45) - ); - } - - @Test - void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { - assertSimpleTest(() -> { - runException("1000j"); - assertThat(output()).contains(ERROR_MESSAGE); - }); - } - - @Override - public void runMain() { - Application.main(new String[]{}); - } -} + private static final String ERROR_MESSAGE = "[ERROR]"; + + @DisplayName("κΈ°λŠ₯ ν…ŒμŠ€νŠΈ") + @Test + void κΈ°λŠ₯_ν…ŒμŠ€νŠΈ() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("8000", "1,2,3,4,5,6", "7"); + assertThat(output()).contains( + "8개λ₯Ό κ΅¬λ§€ν–ˆμŠ΅λ‹ˆλ‹€.", + "[8, 21, 23, 41, 42, 43]", + "[3, 5, 11, 16, 32, 38]", + "[7, 11, 16, 35, 36, 44]", + "[1, 8, 11, 31, 41, 42]", + "[13, 14, 16, 38, 42, 45]", + "[7, 11, 30, 40, 42, 43]", + "[2, 13, 22, 32, 38, 45]", + "[1, 3, 5, 14, 22, 45]", + "3개 일치 (5,000원) - 1개", + "4개 일치 (50,000원) - 0개", + "5개 일치 (1,500,000원) - 0개", + "5개 일치, λ³΄λ„ˆμŠ€ λ³Ό 일치 (30,000,000원) - 0개", + "6개 일치 (2,000,000,000원) - 0개", + "총 수읡λ₯ μ€ 62.5%μž…λ‹ˆλ‹€." + ); + }, + List.of(8, 21, 23, 41, 42, 43), + List.of(3, 5, 11, 16, 32, 38), + List.of(7, 11, 16, 35, 36, 44), + List.of(1, 8, 11, 31, 41, 42), + List.of(13, 14, 16, 38, 42, 45), + List.of(7, 11, 30, 40, 42, 43), + List.of(2, 13, 22, 32, 38, 45), + List.of(1, 3, 5, 14, 22, 45) + ); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - ꡬ맀 κΈˆμ•‘μ΄ μˆ«μžκ°€ μ•„λ‹Œ 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { + assertSimpleTest(() -> { + runException("1000j"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - ꡬ맀 κΈˆμ•‘μ΄ 1000원 λ‹¨μœ„κ°€ μ•„λ‹Œ 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ_κ΅¬λ§€κΈˆμ•‘_λ‹¨μœ„() { + assertSimpleTest(() -> { + runException("1500"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - 당첨 λ²ˆν˜Έκ°€ 6κ°œκ°€ μ•„λ‹Œ 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ_λ‹Ήμ²¨λ²ˆν˜Έ_개수() { + assertSimpleTest(() -> { + runException("1000", "1,2,3,4,5"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - 당첨 λ²ˆν˜Έμ— 쀑볡이 μžˆλŠ” 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ_λ‹Ήμ²¨λ²ˆν˜Έ_쀑볡() { + assertSimpleTest(() -> { + runException("1000", "1,2,3,4,5,5"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - 당첨 λ²ˆν˜Έκ°€ λ²”μœ„λ₯Ό λ²—μ–΄λ‚œ 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ_λ‹Ήμ²¨λ²ˆν˜Έ_λ²”μœ„() { + assertSimpleTest(() -> { + runException("1000", "1,2,3,4,5,46"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ 당첨 λ²ˆν˜Έμ™€ μ€‘λ³΅λœ 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ_λ³΄λ„ˆμŠ€λ²ˆν˜Έ_쀑볡() { + assertSimpleTest(() -> { + runException("1000", "1,2,3,4,5,6", "6"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @DisplayName("μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ - λ³΄λ„ˆμŠ€ λ²ˆν˜Έκ°€ λ²”μœ„λ₯Ό λ²—μ–΄λ‚œ 경우") + @Test + void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ_λ³΄λ„ˆμŠ€λ²ˆν˜Έ_λ²”μœ„() { + assertSimpleTest(() -> { + runException("1000", "1,2,3,4,5,6", "46"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Override + public void runMain() { + Application.main(new String[]{}); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/service/LottoGameServiceWithMockitoTest.java b/src/test/java/lotto/service/LottoGameServiceWithMockitoTest.java new file mode 100644 index 0000000000..f31393f399 --- /dev/null +++ b/src/test/java/lotto/service/LottoGameServiceWithMockitoTest.java @@ -0,0 +1,106 @@ +// package lotto.service; +// +// import lotto.view.InputView; +// import lotto.view.OutputView; +// import org.junit.jupiter.api.DisplayName; +// import org.junit.jupiter.api.Test; +// +// import static org.assertj.core.api.Assertions.assertThat; +// import static org.mockito.Mockito.*; +// +// class LottoGameServiceWithMockitoTest { +// +// @DisplayName("정상1: κ²Œμž„μ΄ μ •μƒμ μœΌλ‘œ μ‹€ν–‰λœλ‹€") +// @Test +// void test1() { +// // Given +// InputView inputView = mock(InputView.class); +// OutputView outputView = mock(OutputView.class); +// +// when(inputView.inputPurchaseAmount()).thenReturn("1000"); +// when(inputView.inputWinningNumbers()).thenReturn("1,2,3,4,5,6"); +// when(inputView.inputBonusNumber()).thenReturn("7"); +// +// LottoGameService service = new LottoGameService(inputView, outputView); +// +// // When +// service.run(); +// +// // Then +// verify(inputView, times(1)).inputPurchaseAmount(); +// verify(inputView, times(1)).inputWinningNumbers(); +// verify(inputView, times(1)).inputBonusNumber(); +// verify(outputView, times(1)).printLottoTickets(any()); +// verify(outputView, times(1)).printWinningStatistics(any()); +// verify(outputView, times(1)).printProfitRate(any()); +// } +// +// @DisplayName("μ˜ˆμ™Έ1: 잘λͺ»λœ ꡬ맀 κΈˆμ•‘ μž…λ ₯ μ‹œ μž¬μž…λ ₯을 μš”μ²­ν•œλ‹€") +// @Test +// void test2() { +// // Given +// InputView inputView = mock(InputView.class); +// OutputView outputView = mock(OutputView.class); +// +// when(inputView.inputPurchaseAmount()) +// .thenReturn("1500") // 첫 번째: 잘λͺ»λœ μž…λ ₯ +// .thenReturn("1000"); // 두 번째: μ˜¬λ°”λ₯Έ μž…λ ₯ +// when(inputView.inputWinningNumbers()).thenReturn("1,2,3,4,5,6"); +// when(inputView.inputBonusNumber()).thenReturn("7"); +// +// LottoGameService service = new LottoGameService(inputView, outputView); +// +// // When +// service.run(); +// +// // Then +// verify(inputView, times(2)).inputPurchaseAmount(); // 2번 호좜 +// verify(outputView, times(1)).printErrorMessage(any()); // μ—λŸ¬ λ©”μ‹œμ§€ 1번 좜λ ₯ +// } +// +// @DisplayName("μ˜ˆμ™Έ2: 잘λͺ»λœ 당첨 번호 μž…λ ₯ μ‹œ μž¬μž…λ ₯을 μš”μ²­ν•œλ‹€") +// @Test +// void test3() { +// // Given +// InputView inputView = mock(InputView.class); +// OutputView outputView = mock(OutputView.class); +// +// when(inputView.inputPurchaseAmount()).thenReturn("1000"); +// when(inputView.inputWinningNumbers()) +// .thenReturn("1,2,3,4,5") // 첫 번째: 5개 μž…λ ₯ (였λ₯˜) +// .thenReturn("1,2,3,4,5,6"); // 두 번째: μ˜¬λ°”λ₯Έ μž…λ ₯ +// when(inputView.inputBonusNumber()).thenReturn("7"); +// +// LottoGameService service = new LottoGameService(inputView, outputView); +// +// // When +// service.run(); +// +// // Then +// verify(inputView, times(2)).inputWinningNumbers(); // 2번 호좜 +// verify(outputView, times(1)).printErrorMessage(any()); // μ—λŸ¬ λ©”μ‹œμ§€ 1번 좜λ ₯ +// } +// +// @DisplayName("μ˜ˆμ™Έ3: 잘λͺ»λœ λ³΄λ„ˆμŠ€ 번호 μž…λ ₯ μ‹œ μž¬μž…λ ₯을 μš”μ²­ν•œλ‹€") +// @Test +// void test4() { +// // Given +// InputView inputView = mock(InputView.class); +// OutputView outputView = mock(OutputView.class); +// +// when(inputView.inputPurchaseAmount()).thenReturn("1000"); +// when(inputView.inputWinningNumbers()).thenReturn("1,2,3,4,5,6"); +// when(inputView.inputBonusNumber()) +// .thenReturn("6") // 첫 번째: 쀑볡 (였λ₯˜) +// .thenReturn("7"); // 두 번째: μ˜¬λ°”λ₯Έ μž…λ ₯ +// +// LottoGameService service = new LottoGameService(inputView, outputView); +// +// // When +// service.run(); +// +// // Then +// verify(inputView, times(2)).inputBonusNumber(); // 2번 호좜 +// verify(outputView, times(1)).printErrorMessage(any()); // μ—λŸ¬ λ©”μ‹œμ§€ 1번 좜λ ₯ +// } +// } \ No newline at end of file