From 42aab52b06dbcb2a16fae2599e9c68c2d664b695 Mon Sep 17 00:00:00 2001 From: moongua404 Date: Sun, 16 Feb 2025 02:09:48 +0900 Subject: [PATCH 1/7] =?UTF-8?q?docs(README.md):=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 과제 내용, 코드 흐름, 구현 기능 목록, 처리할 예외 착성 - Mermail를 통해 flow chart 작성 --- README.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/README.md b/README.md index 5fa2560..3671526 100644 --- a/README.md +++ b/README.md @@ -1 +1,130 @@ # java-lotto-precourse + +> 로또 맞게 해주세요...ㅠ + +
+ 과제 세부 내용 + +## 과제 내용 +로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다. +``` +- 로또 번호의 숫자 범위는 1~45까지이다. +- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. +- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다. +- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +``` +- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다. +- 로또 1장의 가격은 1,000원이다. +- 당첨 번호와 보너스 번호를 입력받는다. +- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. + +### 입출력 +- 입력 + - 로또 구입 금액 + - 당첨 번호 6개 + - 보너스 번호 +- 출력 + - 발행한 로또 수량 및 번호 + - 당첨 내역 + - 수익률 + - (예외 문구) + +ex) + +``` +구입금액을 입력해 주세요. +8000 + +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] + +당첨 번호를 입력해 주세요. +1,2,3,4,5,6 + +보너스 번호를 입력해 주세요. +7 + +당첨 통계 +--- +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%입니다. +``` + +
+ +## 코드 흐름 +- 로또 구매 금액을 입력받는다. +- 로또를 번호를 생성하고 출력한다. +- 당첨 번호를 입력받는다. +- 결과를 계산한 후 출력한다. + +```mermaid +sequenceDiagram + participant View + participant Controller + participant Model + + Controller->>View: 금액 입력 요청 + View->>Controller: 구입 금액 반환 + Controller->>Model: 구입 금액 전달 + Model-->Model: 로또 생성 + Model->>Controller: 로또 번호 반환 + Controller->>View: 로또 번호 출력 + View->>Controller: - + Controller->>View: 당첨 번호 입력 요청 + View->>Controller: 당첨 번호 반환 + Controller->>Model: 당첨 번호 전달 + Model-->Model: 당첨 여부 확인 + Model->>Controller: 결과 반환 + Controller->>View: 결과 출력 + View->>Controller: - + Controller-->Controller: 프로그램 종료 + +``` + +## 구현 기능 목록 +- 입출력 + - [ ] 구입 금액 입력 + - [ ] 당첨 번호 입력 + - [ ] 보너스 번호 입력 + - [ ] 발행한 로또 수량 및 번호 + - [ ] 당첨 내역 + - [ ] 수익률 + - [ ] 예외 +- 로또 + - [ ] 로또 생성 + - [ ] 당첨 확인 + - [ ] 수익률 계산 + +## 처리할 예외 +- 부적절한 구입 금액 (1000 단위로 떨어지지 않을 때) + - `[ERROR] 1,000원 단위로 입력해 주세요.` +- 너무 큰 구입 금액 + - `[ERROR] ${최대 금액}보다 작은 금액을 입력해 주세요.` +- 부적절한 구입 금액 (음수 입력 등 포함, 입력 예외) + - `[ERROR] 유효한 구입 금액을 입력해 주세요` +- 부적절한 로또의 범위 (1-45 밖의 숫자) + - `[ERROR] 1부터 45 사이의 값을 입력해 주세요.` +- 중복되는 번호 / 보너스 번호 + - `[ERROR] 중복되지 않은 번호를 입력해주세요.` +- 부적절한 양식 혹은 갯수 + - `[ERROR] 당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6` + - `[ERROR] 보너스 번호 하나를 정확히 입력해주세요. ex)7` From 04bc5351e4a028e1d400d51103314304026fec9a Mon Sep 17 00:00:00 2001 From: moongua404 Date: Sun, 16 Feb 2025 02:21:46 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore(init):=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MVC 패턴 도입 - 파일을 우선적으로 생성, 추후 변동 가능 --- src/main/java/lotto/AppConfig.java | 4 ++++ src/main/java/lotto/controller/LottoController.java | 4 ++++ src/main/java/lotto/{ => model}/Lotto.java | 2 +- src/main/java/lotto/model/dto/LottoNumberDto.java | 5 +++++ src/main/java/lotto/model/dto/WinningDataDto.java | 4 ++++ src/main/java/lotto/service/RandomService.java | 4 ++++ src/main/java/lotto/service/RandomServiceImpl.java | 4 ++++ src/main/java/lotto/utils/ExceptionConstants.java | 4 ++++ src/main/java/lotto/utils/LottoPrize.java | 4 ++++ src/main/java/lotto/utils/MessageConstants.java | 4 ++++ src/main/java/lotto/utils/Validator.java | 4 ++++ src/main/java/lotto/view/InputView.java | 4 ++++ src/main/java/lotto/view/OutputView.java | 4 ++++ src/main/java/lotto/view/provider/InputProvider.java | 7 +++++++ .../java/lotto/view/provider/WoowaInputProvider.java | 10 ++++++++++ src/test/java/lotto/LottoTest.java | 1 + 16 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/main/java/lotto/AppConfig.java create mode 100644 src/main/java/lotto/controller/LottoController.java rename src/main/java/lotto/{ => model}/Lotto.java (95%) create mode 100644 src/main/java/lotto/model/dto/LottoNumberDto.java create mode 100644 src/main/java/lotto/model/dto/WinningDataDto.java create mode 100644 src/main/java/lotto/service/RandomService.java create mode 100644 src/main/java/lotto/service/RandomServiceImpl.java create mode 100644 src/main/java/lotto/utils/ExceptionConstants.java create mode 100644 src/main/java/lotto/utils/LottoPrize.java create mode 100644 src/main/java/lotto/utils/MessageConstants.java create mode 100644 src/main/java/lotto/utils/Validator.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/main/java/lotto/view/provider/InputProvider.java create mode 100644 src/main/java/lotto/view/provider/WoowaInputProvider.java diff --git a/src/main/java/lotto/AppConfig.java b/src/main/java/lotto/AppConfig.java new file mode 100644 index 0000000..c153459 --- /dev/null +++ b/src/main/java/lotto/AppConfig.java @@ -0,0 +1,4 @@ +package lotto; + +public class AppConfig { +} diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 0000000..92ab476 --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,4 @@ +package lotto.controller; + +public class LottoController { +} diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/model/Lotto.java similarity index 95% rename from src/main/java/lotto/Lotto.java rename to src/main/java/lotto/model/Lotto.java index 88fc5cf..da4f30b 100644 --- a/src/main/java/lotto/Lotto.java +++ b/src/main/java/lotto/model/Lotto.java @@ -1,4 +1,4 @@ -package lotto; +package lotto.model; import java.util.List; diff --git a/src/main/java/lotto/model/dto/LottoNumberDto.java b/src/main/java/lotto/model/dto/LottoNumberDto.java new file mode 100644 index 0000000..d80f79a --- /dev/null +++ b/src/main/java/lotto/model/dto/LottoNumberDto.java @@ -0,0 +1,5 @@ +package lotto.model.dto; + +public class LottoNumberDto { + +} diff --git a/src/main/java/lotto/model/dto/WinningDataDto.java b/src/main/java/lotto/model/dto/WinningDataDto.java new file mode 100644 index 0000000..41b6055 --- /dev/null +++ b/src/main/java/lotto/model/dto/WinningDataDto.java @@ -0,0 +1,4 @@ +package lotto.model.dto; + +public class WinningDataDto { +} diff --git a/src/main/java/lotto/service/RandomService.java b/src/main/java/lotto/service/RandomService.java new file mode 100644 index 0000000..05a2c76 --- /dev/null +++ b/src/main/java/lotto/service/RandomService.java @@ -0,0 +1,4 @@ +package lotto.service; + +public interface RandomService { +} diff --git a/src/main/java/lotto/service/RandomServiceImpl.java b/src/main/java/lotto/service/RandomServiceImpl.java new file mode 100644 index 0000000..69acc00 --- /dev/null +++ b/src/main/java/lotto/service/RandomServiceImpl.java @@ -0,0 +1,4 @@ +package lotto.service; + +public class RandomServiceImpl { +} diff --git a/src/main/java/lotto/utils/ExceptionConstants.java b/src/main/java/lotto/utils/ExceptionConstants.java new file mode 100644 index 0000000..2f61c89 --- /dev/null +++ b/src/main/java/lotto/utils/ExceptionConstants.java @@ -0,0 +1,4 @@ +package lotto.utils; + +public enum ExceptionConstants { +} diff --git a/src/main/java/lotto/utils/LottoPrize.java b/src/main/java/lotto/utils/LottoPrize.java new file mode 100644 index 0000000..a3d4cf4 --- /dev/null +++ b/src/main/java/lotto/utils/LottoPrize.java @@ -0,0 +1,4 @@ +package lotto.utils; + +public enum LottoPrize { +} diff --git a/src/main/java/lotto/utils/MessageConstants.java b/src/main/java/lotto/utils/MessageConstants.java new file mode 100644 index 0000000..042d7aa --- /dev/null +++ b/src/main/java/lotto/utils/MessageConstants.java @@ -0,0 +1,4 @@ +package lotto.utils; + +public enum MessageConstants { +} diff --git a/src/main/java/lotto/utils/Validator.java b/src/main/java/lotto/utils/Validator.java new file mode 100644 index 0000000..8c9c7a0 --- /dev/null +++ b/src/main/java/lotto/utils/Validator.java @@ -0,0 +1,4 @@ +package lotto.utils; + +public class Validator { +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 0000000..9e2f9a7 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,4 @@ +package lotto.view; + +public class InputView { +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 0000000..c8141fa --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,4 @@ +package lotto.view; + +public class OutputView { +} diff --git a/src/main/java/lotto/view/provider/InputProvider.java b/src/main/java/lotto/view/provider/InputProvider.java new file mode 100644 index 0000000..dd05cb7 --- /dev/null +++ b/src/main/java/lotto/view/provider/InputProvider.java @@ -0,0 +1,7 @@ +package lotto.view.provider; + +import java.io.IOException; + +public interface InputProvider { + String readLine() throws IOException; +} diff --git a/src/main/java/lotto/view/provider/WoowaInputProvider.java b/src/main/java/lotto/view/provider/WoowaInputProvider.java new file mode 100644 index 0000000..0709fa2 --- /dev/null +++ b/src/main/java/lotto/view/provider/WoowaInputProvider.java @@ -0,0 +1,10 @@ +package lotto.view.provider; + +import camp.nextstep.edu.missionutils.Console; + +public class WoowaInputProvider implements InputProvider { + @Override + public String readLine() { + return Console.readLine(); + } +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 309f4e5..c8ca9d6 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,5 +1,6 @@ package lotto; +import lotto.model.Lotto; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 0cde100ad76031607082adaa003b874fa153f6c6 Mon Sep 17 00:00:00 2001 From: moongua404 Date: Sun, 16 Feb 2025 13:00:15 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat(utils):=20=EC=83=81=EC=88=98,=20?= =?UTF-8?q?=EC=98=88=EC=99=B8,=20=EB=8B=B9=EC=B2=A8=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=20=EC=83=81=EC=88=98=20Enum=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상수 파일의 경우 문자열을, 예외의 경우 예외를, 당첨 관리는 관련 정보를 반환할 수 있음 - 당첨 관리 enum에서는 이에 대한 판별을 포함함 --- README.md | 4 +- .../java/lotto/utils/ExceptionConstants.java | 23 +++++++++ src/main/java/lotto/utils/LottoPrize.java | 47 ++++++++++++++++++ .../java/lotto/utils/MessageConstants.java | 16 +++++++ src/test/java/lotto/UtilsTest.java | 48 +++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 src/test/java/lotto/UtilsTest.java diff --git a/README.md b/README.md index 3671526..22742a9 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,10 @@ sequenceDiagram - [ ] 수익률 계산 ## 처리할 예외 -- 부적절한 구입 금액 (1000 단위로 떨어지지 않을 때) +- 나누어 떨어지지 않는 금액 (1000 단위로 떨어지지 않을 때) - `[ERROR] 1,000원 단위로 입력해 주세요.` - 너무 큰 구입 금액 - - `[ERROR] ${최대 금액}보다 작은 금액을 입력해 주세요.` + - `[ERROR] $입력한 금액 보다 작은 금액을 입력해 주세요.` - 부적절한 구입 금액 (음수 입력 등 포함, 입력 예외) - `[ERROR] 유효한 구입 금액을 입력해 주세요` - 부적절한 로또의 범위 (1-45 밖의 숫자) diff --git a/src/main/java/lotto/utils/ExceptionConstants.java b/src/main/java/lotto/utils/ExceptionConstants.java index 2f61c89..41056c5 100644 --- a/src/main/java/lotto/utils/ExceptionConstants.java +++ b/src/main/java/lotto/utils/ExceptionConstants.java @@ -1,4 +1,27 @@ package lotto.utils; +import java.lang.reflect.InvocationTargetException; + public enum ExceptionConstants { + INDIVISIBLE_PRICE(IllegalArgumentException.class, "1,000원 단위로 입력해 주세요."), + TOO_BIG_PRICE(IllegalArgumentException.class, "입력한 금액 보다 작은 금액을 입력해 주세요."), + INVALID_PRICE(IllegalArgumentException.class, "유효한 구입 금액을 입력해 주세요"), + INVALID_LOTTO_RANGE(IllegalArgumentException.class, "1부터 45 사이의 값을 입력해 주세요."), + DUPLICATED_LOTTO_NUMBER(IllegalArgumentException.class, "중복되지 않은 번호를 입력해주세요."), + INVALID_LOTTO_NUMBER_FORM(IllegalArgumentException.class, "당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6"), + INVALID_BONUS_NUMBER_FORM(IllegalArgumentException.class, "보너스 번호 하나를 정확히 입력해주세요. ex)7"); + + + + private final Class exception; + private final String message; + + ExceptionConstants(Class exception, String message) { + this.exception = exception; + this.message = "[ERROR] " + message; + } + + public void getException() throws Exception { + throw this.exception.getConstructor(String.class).newInstance(message); + } } diff --git a/src/main/java/lotto/utils/LottoPrize.java b/src/main/java/lotto/utils/LottoPrize.java index a3d4cf4..3a6f0d0 100644 --- a/src/main/java/lotto/utils/LottoPrize.java +++ b/src/main/java/lotto/utils/LottoPrize.java @@ -1,4 +1,51 @@ package lotto.utils; +import java.util.List; +import java.util.stream.Stream; + public enum LottoPrize { + NOTHING("꽝", 0), + FIFTH_PRICE("3개 일치", 2_000_000_000), + FOURTH_PRICE("4개 일치", 30_000_000), + THIRD_PRICE("5개 일치", 1_500_000), + SECOND_PRICE("5개 일치, 보너스 볼 일치", 50_000), + FIRST_PRICE("6개 일치", 5_000); + + private final String condition; + private final int price; + + LottoPrize(String condition, int price) { + this.condition = condition; + this.price = price; + } + + public static LottoPrize getLottoPrize(List winningNumber, List lottoNumber, int bonusNumber) { + Stream totalNumber = Stream.concat(lottoNumber.stream(), Stream.of(bonusNumber)); + int matchCount = (int) totalNumber.filter((winningNumber::contains)).count(); + boolean matchBonus = winningNumber.contains(bonusNumber); + return getLottoPrize(matchCount, matchBonus); + } + + private static LottoPrize getLottoPrize(int matchCount, boolean matchBonus) { + if (matchCount == 6) { + return getFirstOrSecondPrize(matchBonus); + } + if (matchCount == 5) { + return THIRD_PRICE; + } + if (matchCount == 4) { + return FOURTH_PRICE; + } + if (matchCount == 3) { + return FIFTH_PRICE; + } + return NOTHING; + } + + private static LottoPrize getFirstOrSecondPrize(boolean matchBonus) { + if (matchBonus) { + return SECOND_PRICE; + } + return FIRST_PRICE; + } } diff --git a/src/main/java/lotto/utils/MessageConstants.java b/src/main/java/lotto/utils/MessageConstants.java index 042d7aa..0a5a764 100644 --- a/src/main/java/lotto/utils/MessageConstants.java +++ b/src/main/java/lotto/utils/MessageConstants.java @@ -1,4 +1,20 @@ package lotto.utils; public enum MessageConstants { + PURCHASE_GUIDE_MESSAGE("구입금액을 입력해 주세요."), + PURCHASE_AMOUNT_MESSAGE("8개를 구매했습니다."), + LOTTO_NUMBER_GUIDE_MESSAGE("당첨 번호를 입력해 주세요."), + BONUS_NUMBER_GUIDE_MESSAGE("보너스 번호를 입력해 주세요."), + WINNING_RESULTS_MESSAGE("당첨 통계\n---"), + WINNING_NUMBER_MESSAGE("%s (%,d원) - %d개"), + RATE_OF_RETURN_MESSAGE("총 수익률은 %.2f%입니다."); + + private final String message; + MessageConstants(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } } diff --git a/src/test/java/lotto/UtilsTest.java b/src/test/java/lotto/UtilsTest.java new file mode 100644 index 0000000..8371882 --- /dev/null +++ b/src/test/java/lotto/UtilsTest.java @@ -0,0 +1,48 @@ +package lotto; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Stream; +import lotto.utils.ExceptionConstants; +import lotto.utils.LottoPrize; +import lotto.utils.MessageConstants; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class UtilsTest { + @Test + void messageConstantsTest() { + String result = MessageConstants.PURCHASE_GUIDE_MESSAGE.getMessage(); + assertEquals("구입금액을 입력해 주세요.", result); + } + + @Test + void exceptionConstantsTest() { + assertThatThrownBy(ExceptionConstants.INDIVISIBLE_PRICE::getException) + .isInstanceOf(IllegalArgumentException.class) + .message().isEqualTo("[ERROR] 1,000원 단위로 입력해 주세요."); + } + + @ParameterizedTest + @MethodSource("provideLottoNumber") + void lottoPrizeTest(List winningNumber, List lottoNumber, int bonusNumber, LottoPrize prize) { + assertEquals(prize, LottoPrize.getLottoPrize(winningNumber, lottoNumber, bonusNumber)); + } + + private static Stream provideLottoNumber() { + return Stream.of( + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 5, 6), 45, LottoPrize.FIRST_PRICE), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 5, 45), 6, LottoPrize.SECOND_PRICE), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 5, 44), 45, LottoPrize.THIRD_PRICE), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 44, 45), 5, LottoPrize.THIRD_PRICE), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 43, 44), 45, LottoPrize.FOURTH_PRICE), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 42, 43, 44), 45, LottoPrize.FIFTH_PRICE), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 41, 42, 43, 44), 45, LottoPrize.NOTHING), + Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(39, 40, 41, 42, 43, 44), 45, LottoPrize.NOTHING) + ); + } +} From 5aa6526b89e7c1895c0c3f13c3db30e0f95cbcaa Mon Sep 17 00:00:00 2001 From: moongua404 Date: Sun, 16 Feb 2025 14:06:41 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat(View):=20=EC=9E=85=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InputProvider 인터페이스로 의존성 분리 - 테스트 코드 작성 --- .../java/lotto/model/dto/WinningDataDto.java | 4 +- .../java/lotto/utils/ExceptionConstants.java | 6 +- src/main/java/lotto/utils/LottoPrize.java | 16 +++- .../java/lotto/utils/MessageConstants.java | 3 +- src/main/java/lotto/view/InputView.java | 36 +++++++ src/main/java/lotto/view/OutputView.java | 23 +++++ src/test/java/lotto/MockInputProvider.java | 21 ++++ src/test/java/lotto/ViewTest.java | 95 +++++++++++++++++++ 8 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 src/test/java/lotto/MockInputProvider.java create mode 100644 src/test/java/lotto/ViewTest.java diff --git a/src/main/java/lotto/model/dto/WinningDataDto.java b/src/main/java/lotto/model/dto/WinningDataDto.java index 41b6055..0197333 100644 --- a/src/main/java/lotto/model/dto/WinningDataDto.java +++ b/src/main/java/lotto/model/dto/WinningDataDto.java @@ -1,4 +1,6 @@ package lotto.model.dto; -public class WinningDataDto { +import lotto.utils.LottoPrize; + +public record WinningDataDto(LottoPrize lottoPrize, int count) { } diff --git a/src/main/java/lotto/utils/ExceptionConstants.java b/src/main/java/lotto/utils/ExceptionConstants.java index 41056c5..90401e2 100644 --- a/src/main/java/lotto/utils/ExceptionConstants.java +++ b/src/main/java/lotto/utils/ExceptionConstants.java @@ -12,7 +12,6 @@ public enum ExceptionConstants { INVALID_BONUS_NUMBER_FORM(IllegalArgumentException.class, "보너스 번호 하나를 정확히 입력해주세요. ex)7"); - private final Class exception; private final String message; @@ -21,7 +20,8 @@ public enum ExceptionConstants { this.message = "[ERROR] " + message; } - public void getException() throws Exception { - throw this.exception.getConstructor(String.class).newInstance(message); + public Exception getException() + throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + return this.exception.getConstructor(String.class).newInstance(message); } } diff --git a/src/main/java/lotto/utils/LottoPrize.java b/src/main/java/lotto/utils/LottoPrize.java index 3a6f0d0..7b18915 100644 --- a/src/main/java/lotto/utils/LottoPrize.java +++ b/src/main/java/lotto/utils/LottoPrize.java @@ -5,11 +5,11 @@ public enum LottoPrize { NOTHING("꽝", 0), - FIFTH_PRICE("3개 일치", 2_000_000_000), - FOURTH_PRICE("4개 일치", 30_000_000), + FIFTH_PRICE("3개 일치", 5_000), + FOURTH_PRICE("4개 일치", 50_000), THIRD_PRICE("5개 일치", 1_500_000), - SECOND_PRICE("5개 일치, 보너스 볼 일치", 50_000), - FIRST_PRICE("6개 일치", 5_000); + SECOND_PRICE("5개 일치, 보너스 볼 일치", 30_000_000), + FIRST_PRICE("6개 일치", 2_000_000_000); private final String condition; private final int price; @@ -19,6 +19,14 @@ public enum LottoPrize { this.price = price; } + public String getCondition() { + return condition; + } + + public int getPrice() { + return price; + } + public static LottoPrize getLottoPrize(List winningNumber, List lottoNumber, int bonusNumber) { Stream totalNumber = Stream.concat(lottoNumber.stream(), Stream.of(bonusNumber)); int matchCount = (int) totalNumber.filter((winningNumber::contains)).count(); diff --git a/src/main/java/lotto/utils/MessageConstants.java b/src/main/java/lotto/utils/MessageConstants.java index 0a5a764..23aa74d 100644 --- a/src/main/java/lotto/utils/MessageConstants.java +++ b/src/main/java/lotto/utils/MessageConstants.java @@ -7,9 +7,10 @@ public enum MessageConstants { BONUS_NUMBER_GUIDE_MESSAGE("보너스 번호를 입력해 주세요."), WINNING_RESULTS_MESSAGE("당첨 통계\n---"), WINNING_NUMBER_MESSAGE("%s (%,d원) - %d개"), - RATE_OF_RETURN_MESSAGE("총 수익률은 %.2f%입니다."); + RATE_OF_RETURN_MESSAGE("총 수익률은 %.1f%%입니다."); private final String message; + MessageConstants(String message) { this.message = message; } diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index 9e2f9a7..54dcc6f 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -1,4 +1,40 @@ package lotto.view; +import lotto.utils.ExceptionConstants; +import lotto.utils.MessageConstants; +import lotto.view.provider.InputProvider; + public class InputView { + private final InputProvider inputProvider; + + public InputView(InputProvider inputProvider) { + this.inputProvider = inputProvider; + } + + public int getPrice() throws Exception { + try { + System.out.println(MessageConstants.PURCHASE_GUIDE_MESSAGE.getMessage()); + return Integer.parseInt(inputProvider.readLine()); + } catch (Exception e) { + throw ExceptionConstants.INVALID_PRICE.getException(); + } + } + + public String getLottoNumber() throws Exception { + try { + System.out.println(MessageConstants.LOTTO_NUMBER_GUIDE_MESSAGE.getMessage()); + return inputProvider.readLine(); + } catch (Exception e) { + throw ExceptionConstants.INVALID_LOTTO_NUMBER_FORM.getException(); + } + } + + public int getBonusNumber() throws Exception { + try { + System.out.println(MessageConstants.BONUS_NUMBER_GUIDE_MESSAGE.getMessage()); + return Integer.parseInt(inputProvider.readLine()); + } catch (Exception e) { + throw ExceptionConstants.INVALID_BONUS_NUMBER_FORM.getException(); + } + } } diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index c8141fa..0512df8 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -1,4 +1,27 @@ package lotto.view; +import java.util.List; +import lotto.model.dto.WinningDataDto; +import lotto.utils.MessageConstants; + public class OutputView { + private final String SEPARATOR = ", "; + private final String WRAPPER = "[%s]"; + + public void printLotto(List lottoNumber) { + List numbers = lottoNumber.stream().map(Object::toString).toList(); + System.out.printf(WRAPPER + "%n", String.join(SEPARATOR, numbers)); + } + + public void printResult(List result) { + System.out.println(MessageConstants.WINNING_RESULTS_MESSAGE.getMessage()); + result.forEach((data) -> + System.out.printf(MessageConstants.WINNING_NUMBER_MESSAGE.getMessage() + "%n", + data.lottoPrize().getCondition(), data.lottoPrize().getPrice(), data.count()) + ); + } + + public void printRateOfReturn(float rateOfReturn) { + System.out.printf(MessageConstants.RATE_OF_RETURN_MESSAGE.getMessage() + "%n", rateOfReturn); + } } diff --git a/src/test/java/lotto/MockInputProvider.java b/src/test/java/lotto/MockInputProvider.java new file mode 100644 index 0000000..6fcf60b --- /dev/null +++ b/src/test/java/lotto/MockInputProvider.java @@ -0,0 +1,21 @@ +package lotto; + +import java.io.IOException; +import lotto.view.provider.InputProvider; + +public class MockInputProvider implements InputProvider { + private final String[] inputs; + private int index = 0; + + public MockInputProvider(String... inputs) { + this.inputs = inputs; + } + + @Override + public String readLine() throws IOException { + if (index < inputs.length) { + return inputs[index++]; + } + throw new IOException(); + } +} diff --git a/src/test/java/lotto/ViewTest.java b/src/test/java/lotto/ViewTest.java new file mode 100644 index 0000000..a88f76f --- /dev/null +++ b/src/test/java/lotto/ViewTest.java @@ -0,0 +1,95 @@ +package lotto; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.List; +import lotto.model.dto.WinningDataDto; +import lotto.utils.LottoPrize; +import lotto.view.InputView; +import lotto.view.OutputView; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class ViewTest extends NsTest { + private InputView inputView; + private OutputView outputView; + + @BeforeEach + void setUp() { + inputView = new InputView(new MockInputProvider("1000", "1,2,3,4,5,6", "7")); + outputView = new OutputView(); + } + + @Nested + class InputTest { + @Test + void priceInputTest() throws Exception { + int price = inputView.getPrice(); + assertEquals(1000, price); + } + + @Test + void lottoNumberInputTest() throws Exception { + inputView.getPrice(); + String lottoNumber = inputView.getLottoNumber(); + assertEquals("1,2,3,4,5,6", lottoNumber); + } + + @Test + void bonusNumberInputTest() throws Exception { + inputView.getPrice(); + inputView.getLottoNumber(); + int bonusNumber = inputView.getBonusNumber(); + assertEquals(7, bonusNumber); + } + } + + @Nested + class OutputTest { + @Test + void lottoNumberOutputTest() throws Exception { + assertSimpleTest(() -> { + outputView.printLotto(List.of(1, 2, 3, 4, 5, 6)); + assertThat(output()).contains("[1, 2, 3, 4, 5, 6]"); + }); + } + + @Test + void resultOutputTest() throws Exception { + assertSimpleTest(() -> { + outputView.printResult(List.of( + new WinningDataDto(LottoPrize.FIFTH_PRICE, 5), + new WinningDataDto(LottoPrize.FOURTH_PRICE, 4), + new WinningDataDto(LottoPrize.THIRD_PRICE, 3), + new WinningDataDto(LottoPrize.SECOND_PRICE, 2), + new WinningDataDto(LottoPrize.FIRST_PRICE, 1) + )); + assertThat(output()).contains(""" + 당첨 통계 + --- + 3개 일치 (5,000원) - 5개 + 4개 일치 (50,000원) - 4개 + 5개 일치 (1,500,000원) - 3개 + 5개 일치, 보너스 볼 일치 (30,000,000원) - 2개 + 6개 일치 (2,000,000,000원) - 1개""" + ); + }); + } + } + + @Test + void ROIOutputTest() throws Exception { + assertSimpleTest(() -> { + outputView.printRateOfReturn(62.5f); + assertThat(output()).contains("총 수익률은 62.5%입니다."); + }); + } + + @Override + public void runMain() { + } +} From c80ab0cf933b621be87a533ae33eded8dea7d969 Mon Sep 17 00:00:00 2001 From: moongua404 Date: Sun, 16 Feb 2025 21:43:38 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat(model):=20Lotto=EC=99=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lotto 모델은 로또를 관리하는 책임을 가짐 - 당첨에 관한 책임은 LottoPrize로 분리됨 --- src/main/java/lotto/model/Lotto.java | 18 +++++++++++ .../java/lotto/model/dto/LottoNumberDto.java | 5 --- .../lotto/service/LottoControlService.java | 4 +++ .../service/LottoControlServiceImpl.java | 4 +++ .../java/lotto/utils/ExceptionConstants.java | 25 ++++++--------- src/main/java/lotto/utils/Utility.java | 31 +++++++++++++++++++ src/main/java/lotto/utils/Validator.java | 4 --- 7 files changed, 67 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/lotto/model/dto/LottoNumberDto.java create mode 100644 src/main/java/lotto/service/LottoControlService.java create mode 100644 src/main/java/lotto/service/LottoControlServiceImpl.java create mode 100644 src/main/java/lotto/utils/Utility.java delete mode 100644 src/main/java/lotto/utils/Validator.java diff --git a/src/main/java/lotto/model/Lotto.java b/src/main/java/lotto/model/Lotto.java index da4f30b..6f72594 100644 --- a/src/main/java/lotto/model/Lotto.java +++ b/src/main/java/lotto/model/Lotto.java @@ -1,6 +1,12 @@ package lotto.model; +import static lotto.utils.ExceptionConstants.DUPLICATED_LOTTO_NUMBER; +import static lotto.utils.ExceptionConstants.INVALID_LOTTO_RANGE; + import java.util.List; +import java.util.stream.Stream; +import lotto.utils.LottoPrize; +import lotto.utils.Utility; public class Lotto { private final List numbers; @@ -14,7 +20,19 @@ private void validate(List numbers) { if (numbers.size() != 6) { throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); } + if (Utility.hasDuplicatedValue(numbers)) { + throw new IllegalArgumentException(DUPLICATED_LOTTO_NUMBER.getMessage()); + } + if (Utility.isInRange(numbers, 0, 45)) { + throw new IllegalArgumentException(INVALID_LOTTO_RANGE.getMessage()); + } } // TODO: 추가 기능 구현 + public LottoPrize checkWinning(List winningNumbers, int bonusNumber) { + List totalWinningNumber = Stream.concat(winningNumbers.stream(), Stream.of(bonusNumber)).toList(); + int matchCount = (int) numbers.stream().filter(totalWinningNumber::contains).count(); + boolean matchBonus = numbers.contains(bonusNumber); + return LottoPrize.getLottoPrize(matchCount, matchBonus); + } } diff --git a/src/main/java/lotto/model/dto/LottoNumberDto.java b/src/main/java/lotto/model/dto/LottoNumberDto.java deleted file mode 100644 index d80f79a..0000000 --- a/src/main/java/lotto/model/dto/LottoNumberDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package lotto.model.dto; - -public class LottoNumberDto { - -} diff --git a/src/main/java/lotto/service/LottoControlService.java b/src/main/java/lotto/service/LottoControlService.java new file mode 100644 index 0000000..3fba147 --- /dev/null +++ b/src/main/java/lotto/service/LottoControlService.java @@ -0,0 +1,4 @@ +package lotto.service; + +public class LottoControlService { +} diff --git a/src/main/java/lotto/service/LottoControlServiceImpl.java b/src/main/java/lotto/service/LottoControlServiceImpl.java new file mode 100644 index 0000000..21c5875 --- /dev/null +++ b/src/main/java/lotto/service/LottoControlServiceImpl.java @@ -0,0 +1,4 @@ +package lotto.service; + +public class LottoControlServiceImpl { +} diff --git a/src/main/java/lotto/utils/ExceptionConstants.java b/src/main/java/lotto/utils/ExceptionConstants.java index 90401e2..dc5cf5b 100644 --- a/src/main/java/lotto/utils/ExceptionConstants.java +++ b/src/main/java/lotto/utils/ExceptionConstants.java @@ -1,27 +1,22 @@ package lotto.utils; -import java.lang.reflect.InvocationTargetException; - public enum ExceptionConstants { - INDIVISIBLE_PRICE(IllegalArgumentException.class, "1,000원 단위로 입력해 주세요."), - TOO_BIG_PRICE(IllegalArgumentException.class, "입력한 금액 보다 작은 금액을 입력해 주세요."), - INVALID_PRICE(IllegalArgumentException.class, "유효한 구입 금액을 입력해 주세요"), - INVALID_LOTTO_RANGE(IllegalArgumentException.class, "1부터 45 사이의 값을 입력해 주세요."), - DUPLICATED_LOTTO_NUMBER(IllegalArgumentException.class, "중복되지 않은 번호를 입력해주세요."), - INVALID_LOTTO_NUMBER_FORM(IllegalArgumentException.class, "당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6"), - INVALID_BONUS_NUMBER_FORM(IllegalArgumentException.class, "보너스 번호 하나를 정확히 입력해주세요. ex)7"); + INDIVISIBLE_PRICE("1,000원 단위로 입력해 주세요."), + TOO_BIG_PRICE("입력한 금액 보다 작은 금액을 입력해 주세요."), + INVALID_PRICE("유효한 구입 금액을 입력해 주세요"), + INVALID_LOTTO_RANGE("1부터 45 사이의 값을 입력해 주세요."), + DUPLICATED_LOTTO_NUMBER("중복되지 않은 번호를 입력해주세요."), + INVALID_LOTTO_NUMBER_FORM("당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6"), + INVALID_BONUS_NUMBER_FORM("보너스 번호 하나를 정확히 입력해주세요. ex)7"); - private final Class exception; private final String message; - ExceptionConstants(Class exception, String message) { - this.exception = exception; + ExceptionConstants(String message) { this.message = "[ERROR] " + message; } - public Exception getException() - throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - return this.exception.getConstructor(String.class).newInstance(message); + public String getMessage() { + return message; } } diff --git a/src/main/java/lotto/utils/Utility.java b/src/main/java/lotto/utils/Utility.java new file mode 100644 index 0000000..cf1f04c --- /dev/null +++ b/src/main/java/lotto/utils/Utility.java @@ -0,0 +1,31 @@ +package lotto.utils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Utility { + public static long countValue(List items, T target) { + return items.stream().filter(item -> item.equals(target)).count(); + } + + public static boolean hasDuplicatedValue(List itemList) { + Set uniqueNames = new HashSet<>(itemList); + return uniqueNames.size() != itemList.size(); + } + + public static boolean isInRange(int value, int min, int max) { + return value >= min && value <= max; + } + + public static boolean isInRange(List values, int min, int max) { + return values.stream() + .map(value -> isInRange(value, min, max)) + .toList() + .contains(false); + } + + public static boolean isDividedByThousand(int value) { + return value % 1000 == 0; + } +} diff --git a/src/main/java/lotto/utils/Validator.java b/src/main/java/lotto/utils/Validator.java deleted file mode 100644 index 8c9c7a0..0000000 --- a/src/main/java/lotto/utils/Validator.java +++ /dev/null @@ -1,4 +0,0 @@ -package lotto.utils; - -public class Validator { -} From 2a8598f6c889aa2f02ee099953c1cc5cc00ca70c Mon Sep 17 00:00:00 2001 From: moongua404 Date: Sun, 16 Feb 2025 21:44:06 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat(service):=20=EB=B9=84=EC=A7=80?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/service/LottoControlService.java | 10 ++++- .../service/LottoControlServiceImpl.java | 38 +++++++++++++++++++ .../java/lotto/service/RandomService.java | 3 ++ .../java/lotto/service/RandomServiceImpl.java | 8 +++- src/main/java/lotto/utils/LottoPrize.java | 10 ++--- src/main/java/lotto/view/InputView.java | 6 +-- src/test/java/lotto/LottoTest.java | 12 ++++-- src/test/java/lotto/UtilsTest.java | 28 ++++---------- 8 files changed, 79 insertions(+), 36 deletions(-) diff --git a/src/main/java/lotto/service/LottoControlService.java b/src/main/java/lotto/service/LottoControlService.java index 3fba147..902d499 100644 --- a/src/main/java/lotto/service/LottoControlService.java +++ b/src/main/java/lotto/service/LottoControlService.java @@ -1,4 +1,12 @@ package lotto.service; -public class LottoControlService { +import java.util.List; +import lotto.model.dto.WinningDataDto; + +public interface LottoControlService { + void buyLotto(int amount); + + List checkWinning(List winningNumbers, int bonusNumber); + + float calculateROI(List winningDataDto); } diff --git a/src/main/java/lotto/service/LottoControlServiceImpl.java b/src/main/java/lotto/service/LottoControlServiceImpl.java index 21c5875..740a9af 100644 --- a/src/main/java/lotto/service/LottoControlServiceImpl.java +++ b/src/main/java/lotto/service/LottoControlServiceImpl.java @@ -1,4 +1,42 @@ package lotto.service; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import lotto.model.Lotto; +import lotto.model.dto.WinningDataDto; +import lotto.utils.LottoPrize; +import lotto.utils.Utility; + public class LottoControlServiceImpl { + List lottoRepository; + + LottoControlServiceImpl() { + this.lottoRepository = new ArrayList<>(); + } + + public void buyLotto(int amount, Supplier> pickFunction) { + lottoRepository.addAll( + IntStream.range(0, amount) + .mapToObj(i -> new Lotto(pickFunction.get())) + .toList() + ); + } + + public List checkWinning(List winningNumbers, int bonusNumber) { + List prizes = lottoRepository.stream() + .map(lotto -> lotto.checkWinning(winningNumbers, bonusNumber)) + .toList(); + return LottoPrize.getPrizeTypes().stream() + .map(lottoPrize -> new WinningDataDto(lottoPrize, (int) Utility.countValue(prizes, lottoPrize))) + .toList(); + } + + public float calculateROI(List winningDataDto, int price) { + long returnValue = winningDataDto.stream() + .mapToInt(dto -> dto.lottoPrize().getPrice() * dto.count()) + .sum(); + return (float) (returnValue / price); + } } diff --git a/src/main/java/lotto/service/RandomService.java b/src/main/java/lotto/service/RandomService.java index 05a2c76..a48bada 100644 --- a/src/main/java/lotto/service/RandomService.java +++ b/src/main/java/lotto/service/RandomService.java @@ -1,4 +1,7 @@ package lotto.service; +import java.util.List; + public interface RandomService { + List getLottoNumbers(); } diff --git a/src/main/java/lotto/service/RandomServiceImpl.java b/src/main/java/lotto/service/RandomServiceImpl.java index 69acc00..913e391 100644 --- a/src/main/java/lotto/service/RandomServiceImpl.java +++ b/src/main/java/lotto/service/RandomServiceImpl.java @@ -1,4 +1,10 @@ package lotto.service; -public class RandomServiceImpl { +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; + +public class RandomServiceImpl implements RandomService { + public List getLottoNumbers() { + return Randoms.pickUniqueNumbersInRange(1, 45, 6); + } } diff --git a/src/main/java/lotto/utils/LottoPrize.java b/src/main/java/lotto/utils/LottoPrize.java index 7b18915..25efb84 100644 --- a/src/main/java/lotto/utils/LottoPrize.java +++ b/src/main/java/lotto/utils/LottoPrize.java @@ -1,7 +1,6 @@ package lotto.utils; import java.util.List; -import java.util.stream.Stream; public enum LottoPrize { NOTHING("꽝", 0), @@ -27,14 +26,11 @@ public int getPrice() { return price; } - public static LottoPrize getLottoPrize(List winningNumber, List lottoNumber, int bonusNumber) { - Stream totalNumber = Stream.concat(lottoNumber.stream(), Stream.of(bonusNumber)); - int matchCount = (int) totalNumber.filter((winningNumber::contains)).count(); - boolean matchBonus = winningNumber.contains(bonusNumber); - return getLottoPrize(matchCount, matchBonus); + public static List getPrizeTypes() { + return List.of(FIFTH_PRICE, FOURTH_PRICE, THIRD_PRICE, SECOND_PRICE, FIRST_PRICE); } - private static LottoPrize getLottoPrize(int matchCount, boolean matchBonus) { + public static LottoPrize getLottoPrize(int matchCount, boolean matchBonus) { if (matchCount == 6) { return getFirstOrSecondPrize(matchBonus); } diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index 54dcc6f..11eab5d 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -16,7 +16,7 @@ public int getPrice() throws Exception { System.out.println(MessageConstants.PURCHASE_GUIDE_MESSAGE.getMessage()); return Integer.parseInt(inputProvider.readLine()); } catch (Exception e) { - throw ExceptionConstants.INVALID_PRICE.getException(); + throw new IllegalArgumentException(ExceptionConstants.INVALID_PRICE.getMessage()); } } @@ -25,7 +25,7 @@ public String getLottoNumber() throws Exception { System.out.println(MessageConstants.LOTTO_NUMBER_GUIDE_MESSAGE.getMessage()); return inputProvider.readLine(); } catch (Exception e) { - throw ExceptionConstants.INVALID_LOTTO_NUMBER_FORM.getException(); + throw new IllegalArgumentException(ExceptionConstants.INVALID_LOTTO_NUMBER_FORM.getMessage()); } } @@ -34,7 +34,7 @@ public int getBonusNumber() throws Exception { System.out.println(MessageConstants.BONUS_NUMBER_GUIDE_MESSAGE.getMessage()); return Integer.parseInt(inputProvider.readLine()); } catch (Exception e) { - throw ExceptionConstants.INVALID_BONUS_NUMBER_FORM.getException(); + throw new IllegalArgumentException(ExceptionConstants.INVALID_BONUS_NUMBER_FORM.getMessage()); } } } diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index c8ca9d6..70d07de 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -1,13 +1,12 @@ package lotto; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; import lotto.model.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개가_넘어가면_예외가_발생한다() { @@ -23,4 +22,9 @@ class LottoTest { } // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + @Test + void 로또_번호에_범위가_맞지_않으면_예외가_발생한다() { + assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 46))) + .isInstanceOf(IllegalArgumentException.class); + } } diff --git a/src/test/java/lotto/UtilsTest.java b/src/test/java/lotto/UtilsTest.java index 8371882..6dc5764 100644 --- a/src/test/java/lotto/UtilsTest.java +++ b/src/test/java/lotto/UtilsTest.java @@ -1,11 +1,8 @@ package lotto; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.List; import java.util.stream.Stream; -import lotto.utils.ExceptionConstants; import lotto.utils.LottoPrize; import lotto.utils.MessageConstants; import org.junit.jupiter.api.Test; @@ -20,29 +17,20 @@ void messageConstantsTest() { assertEquals("구입금액을 입력해 주세요.", result); } - @Test - void exceptionConstantsTest() { - assertThatThrownBy(ExceptionConstants.INDIVISIBLE_PRICE::getException) - .isInstanceOf(IllegalArgumentException.class) - .message().isEqualTo("[ERROR] 1,000원 단위로 입력해 주세요."); - } - @ParameterizedTest @MethodSource("provideLottoNumber") - void lottoPrizeTest(List winningNumber, List lottoNumber, int bonusNumber, LottoPrize prize) { - assertEquals(prize, LottoPrize.getLottoPrize(winningNumber, lottoNumber, bonusNumber)); + void lottoPrizeTest(int matchCount, boolean matchBonus, LottoPrize prize) { + assertEquals(prize, LottoPrize.getLottoPrize(matchCount, matchBonus)); } private static Stream provideLottoNumber() { return Stream.of( - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 5, 6), 45, LottoPrize.FIRST_PRICE), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 5, 45), 6, LottoPrize.SECOND_PRICE), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 5, 44), 45, LottoPrize.THIRD_PRICE), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 44, 45), 5, LottoPrize.THIRD_PRICE), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 4, 43, 44), 45, LottoPrize.FOURTH_PRICE), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 3, 42, 43, 44), 45, LottoPrize.FIFTH_PRICE), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(1, 2, 41, 42, 43, 44), 45, LottoPrize.NOTHING), - Arguments.of(List.of(1, 2, 3, 4, 5, 6), List.of(39, 40, 41, 42, 43, 44), 45, LottoPrize.NOTHING) + Arguments.of(6, false, LottoPrize.FIRST_PRICE), + Arguments.of(6, true, LottoPrize.SECOND_PRICE), + Arguments.of(5, true, LottoPrize.THIRD_PRICE), + Arguments.of(4, true, LottoPrize.FOURTH_PRICE), + Arguments.of(3, true, LottoPrize.FIFTH_PRICE), + Arguments.of(2, true, LottoPrize.NOTHING) ); } } From 652663edece4c982a963ecaff9c18865978c6c31 Mon Sep 17 00:00:00 2001 From: moongua404 Date: Fri, 21 Feb 2025 22:32:11 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=EB=A7=88=EB=AC=B4=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 뭐했는지 기억이 안나서...ㅋㅋㅋㅋ - 하하... --- src/main/java/lotto/AppConfig.java | 38 +++++ src/main/java/lotto/Application.java | 6 + .../lotto/controller/LottoController.java | 131 ++++++++++++++++++ src/main/java/lotto/model/Lotto.java | 19 ++- .../java/lotto/model/LottoRepository.java | 78 +++++++++++ .../lotto/service/LottoControlService.java | 15 +- .../service/LottoControlServiceImpl.java | 47 ++++--- .../java/lotto/service/RandomServiceImpl.java | 6 +- .../java/lotto/utils/ExceptionConstants.java | 3 +- src/main/java/lotto/utils/LottoConstants.java | 10 ++ .../java/lotto/utils/MessageConstants.java | 2 +- src/main/java/lotto/utils/Utility.java | 11 +- src/main/java/lotto/view/InputView.java | 6 +- src/main/java/lotto/view/OutputView.java | 8 ++ src/test/java/lotto/ApplicationTest.java | 112 ++++++++++++++- src/test/java/lotto/LottoTest.java | 6 + 16 files changed, 464 insertions(+), 34 deletions(-) create mode 100644 src/main/java/lotto/model/LottoRepository.java create mode 100644 src/main/java/lotto/utils/LottoConstants.java diff --git a/src/main/java/lotto/AppConfig.java b/src/main/java/lotto/AppConfig.java index c153459..edf23b1 100644 --- a/src/main/java/lotto/AppConfig.java +++ b/src/main/java/lotto/AppConfig.java @@ -1,4 +1,42 @@ package lotto; +import lotto.controller.LottoController; +import lotto.service.LottoControlService; +import lotto.service.LottoControlServiceImpl; +import lotto.service.RandomService; +import lotto.service.RandomServiceImpl; +import lotto.view.InputView; +import lotto.view.OutputView; +import lotto.view.provider.InputProvider; +import lotto.view.provider.WoowaInputProvider; + public class AppConfig { + public InputProvider inputProvider() { + return new WoowaInputProvider(); + } + + public InputView inputView() { + return new InputView(inputProvider()); + } + + public OutputView outputView() { + return new OutputView(); + } + + public LottoControlService lottoControlService() { + return new LottoControlServiceImpl(); + } + + public RandomService randomService() { + return new RandomServiceImpl(); + } + + public LottoController lottoController() { + return new LottoController( + inputView(), + outputView(), + lottoControlService(), + randomService() + ); + } } diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922..3496af7 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,13 @@ package lotto; +import lotto.controller.LottoController; + public class Application { + public static void main(String[] args) { // TODO: 프로그램 구현 + AppConfig appConfig = new AppConfig(); + LottoController lottoController = appConfig.lottoController(); + lottoController.run(); } } diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java index 92ab476..d6fcdf6 100644 --- a/src/main/java/lotto/controller/LottoController.java +++ b/src/main/java/lotto/controller/LottoController.java @@ -1,4 +1,135 @@ package lotto.controller; +import static lotto.utils.LottoConstants.LOTTO_PRICE; + +import java.util.List; +import lotto.model.dto.WinningDataDto; +import lotto.service.LottoControlService; +import lotto.service.RandomService; +import lotto.utils.ExceptionConstants; +import lotto.utils.Utility; +import lotto.view.InputView; +import lotto.view.OutputView; + public class LottoController { + InputView inputView; + OutputView outputView; + LottoControlService lottoControlService; + RandomService randomService; + ErrorHandler errorHandler; + + private static final int MAX_ATTEMPTS = 100; + + public LottoController(InputView inputView, OutputView outputView, LottoControlService lottoControlService, + RandomService randomService) { + this.inputView = inputView; + this.outputView = outputView; + this.lottoControlService = lottoControlService; + this.randomService = randomService; + errorHandler = new ErrorHandler(); + } + + public void run() { + try { + int amount = inputPrice(); + inputLottoNumber(); + outputLottoData(amount); + inputBonusNumber(); + composeLottoSystem(); + printResult(amount); + } catch (Exception e) { + outputView.printException(e); + } + } + + private int inputPrice() { + while (true) { + if (errorHandler.willGenerateError()) { + throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage()); + } + try { + int price = inputView.getPrice(); + validatePrice(price); + lottoControlService.buyLotto(price / LOTTO_PRICE, randomService::getLottoNumbers); + errorHandler.resetAttemptCount(); + return price / LOTTO_PRICE; + } catch (Exception e) { + outputView.printException(e); + } + } + } + + private void validatePrice(int price) { + if (!Utility.isDividedByThousand(price)) { + throw new IllegalArgumentException(ExceptionConstants.INDIVISIBLE_PRICE.getMessage()); + } + } + + private void outputLottoData(int amount) { + outputView.printResultGuide(amount); + lottoControlService.getLotto().forEach(lotto -> outputView.printLotto(lotto)); + } + + private void inputLottoNumber() { + while (true) { + if (errorHandler.willGenerateError()) { + throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage()); + } + try { + String line = inputView.getLottoNumber(); + lottoControlService.setWinningNumbers(line); + errorHandler.resetAttemptCount(); + break; + } catch (Exception e) { + outputView.printException(e); + } + } + } + + private void inputBonusNumber() { + while (true) { + if (errorHandler.willGenerateError()) { + throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage()); + } + try { + int bonusNumber = inputView.getBonusNumber(); + lottoControlService.setBonusNumber(bonusNumber); + errorHandler.resetAttemptCount(); + break; + } catch (Exception e) { + outputView.printException(e); + } + } + } + + private void composeLottoSystem() { + lottoControlService.composeLotto(); + } + + private void printResult(int amount) { + List lottoResult = lottoControlService.checkWinning(); + outputView.printResult(lottoResult); + float result = lottoControlService.calculateROI(lottoResult, amount * LOTTO_PRICE); + outputView.printRateOfReturn(result); + } + + static class ErrorHandler { + private int attempts; + + ErrorHandler() { + this.attempts = 0; + } + + boolean willGenerateError() { + attempts++; + if (attempts > MAX_ATTEMPTS) { + return true; + } + return false; + } + + void resetAttemptCount() { + attempts = 0; + } + } } diff --git a/src/main/java/lotto/model/Lotto.java b/src/main/java/lotto/model/Lotto.java index 6f72594..a376abf 100644 --- a/src/main/java/lotto/model/Lotto.java +++ b/src/main/java/lotto/model/Lotto.java @@ -2,6 +2,8 @@ import static lotto.utils.ExceptionConstants.DUPLICATED_LOTTO_NUMBER; import static lotto.utils.ExceptionConstants.INVALID_LOTTO_RANGE; +import static lotto.utils.LottoConstants.LOWER_BOUND_NUMBER; +import static lotto.utils.LottoConstants.UPPER_BOUND_NUMBER; import java.util.List; import java.util.stream.Stream; @@ -23,16 +25,29 @@ private void validate(List numbers) { if (Utility.hasDuplicatedValue(numbers)) { throw new IllegalArgumentException(DUPLICATED_LOTTO_NUMBER.getMessage()); } - if (Utility.isInRange(numbers, 0, 45)) { + if (!Utility.isInRange(numbers, LOWER_BOUND_NUMBER, UPPER_BOUND_NUMBER)) { throw new IllegalArgumentException(INVALID_LOTTO_RANGE.getMessage()); } } - // TODO: 추가 기능 구현 public LottoPrize checkWinning(List winningNumbers, int bonusNumber) { List totalWinningNumber = Stream.concat(winningNumbers.stream(), Stream.of(bonusNumber)).toList(); int matchCount = (int) numbers.stream().filter(totalWinningNumber::contains).count(); boolean matchBonus = numbers.contains(bonusNumber); return LottoPrize.getLottoPrize(matchCount, matchBonus); } + + public void validateAdditionalNumber(int value) { + List tempNumbers = Stream.concat(numbers.stream(), Stream.of(value)).toList(); + if (Utility.hasDuplicatedValue(tempNumbers)) { + throw new IllegalArgumentException(DUPLICATED_LOTTO_NUMBER.getMessage()); + } + if (!Utility.isInRange(value, LOWER_BOUND_NUMBER, UPPER_BOUND_NUMBER)) { + throw new IllegalArgumentException(INVALID_LOTTO_RANGE.getMessage()); + } + } + + public List getNumbers() { + return numbers; + } } diff --git a/src/main/java/lotto/model/LottoRepository.java b/src/main/java/lotto/model/LottoRepository.java new file mode 100644 index 0000000..35d4c22 --- /dev/null +++ b/src/main/java/lotto/model/LottoRepository.java @@ -0,0 +1,78 @@ +package lotto.model; + +import static lotto.utils.LottoConstants.NUMBER_SEPARATOR; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lotto.model.dto.WinningDataDto; +import lotto.utils.ExceptionConstants; +import lotto.utils.LottoPrize; +import lotto.utils.Utility; + +public class LottoRepository { + private final List purchasedLotto; + private final List winningNumbers; + private int bonusNumber; + + private LottoRepository(LottoRepositoryBuilder lottoRepositoryBuilder) { + purchasedLotto = lottoRepositoryBuilder.purchasedLotto; + winningNumbers = lottoRepositoryBuilder.winningNumbers; + bonusNumber = lottoRepositoryBuilder.bonusNumber; + } + + public List countGrade() { + List prizes = purchasedLotto.stream() + .map(lotto -> lotto.checkWinning(winningNumbers, bonusNumber)) + .toList(); + return LottoPrize.getPrizeTypes().stream() + .map(lottoPrize -> new WinningDataDto(lottoPrize, (int) Utility.countValue(prizes, lottoPrize))) + .toList(); + } + + public static class LottoRepositoryBuilder { + private List purchasedLotto; + private List winningNumbers; + private Integer bonusNumber; + + public LottoRepositoryBuilder setPurchasedLotto(List purchasedLotto) { + this.purchasedLotto = new ArrayList<>(); + this.purchasedLotto.addAll(purchasedLotto); + return this; + } + + public LottoRepositoryBuilder setWinningNumber(String line) { + winningNumbers = new ArrayList<>(); + List numbers = Arrays.stream(line.split(NUMBER_SEPARATOR)) + .map(Integer::parseInt) + .toList(); + setWinningNumbers(numbers); + return this; + } + + private LottoRepositoryBuilder setWinningNumbers(List winningNumbers) { + new Lotto(winningNumbers); + this.winningNumbers.clear(); + this.winningNumbers.addAll(winningNumbers); + return this; + } + + public LottoRepositoryBuilder setBonusNumber(int bonusNumber) { + Lotto tempLotto = new Lotto(winningNumbers); + tempLotto.validateAdditionalNumber(bonusNumber); + this.bonusNumber = bonusNumber; + return this; + } + + public LottoRepository build() { + if (purchasedLotto == null || winningNumbers == null || bonusNumber == null) { + throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage()); + } + return new LottoRepository(this); + } + + public List getPurchasedLotto() { + return purchasedLotto; + } + } +} diff --git a/src/main/java/lotto/service/LottoControlService.java b/src/main/java/lotto/service/LottoControlService.java index 902d499..f2c6588 100644 --- a/src/main/java/lotto/service/LottoControlService.java +++ b/src/main/java/lotto/service/LottoControlService.java @@ -1,12 +1,21 @@ package lotto.service; import java.util.List; +import java.util.function.Supplier; import lotto.model.dto.WinningDataDto; public interface LottoControlService { - void buyLotto(int amount); + void buyLotto(int amount, Supplier> pickFunction); - List checkWinning(List winningNumbers, int bonusNumber); + public void setWinningNumbers(String line); - float calculateROI(List winningDataDto); + public void setBonusNumber(int bonusNumber); + + public void composeLotto(); + + List checkWinning(); + + float calculateROI(List winningDataDto, int price); + + List> getLotto(); } diff --git a/src/main/java/lotto/service/LottoControlServiceImpl.java b/src/main/java/lotto/service/LottoControlServiceImpl.java index 740a9af..0b2c889 100644 --- a/src/main/java/lotto/service/LottoControlServiceImpl.java +++ b/src/main/java/lotto/service/LottoControlServiceImpl.java @@ -1,42 +1,55 @@ package lotto.service; -import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import java.util.stream.IntStream; import lotto.model.Lotto; +import lotto.model.LottoRepository; +import lotto.model.LottoRepository.LottoRepositoryBuilder; import lotto.model.dto.WinningDataDto; -import lotto.utils.LottoPrize; -import lotto.utils.Utility; -public class LottoControlServiceImpl { - List lottoRepository; +public class LottoControlServiceImpl implements LottoControlService { + LottoRepositoryBuilder lottoRepositoryBuilder; + LottoRepository lottoRepository; - LottoControlServiceImpl() { - this.lottoRepository = new ArrayList<>(); + public LottoControlServiceImpl() { + lottoRepositoryBuilder = new LottoRepositoryBuilder(); } public void buyLotto(int amount, Supplier> pickFunction) { - lottoRepository.addAll( + lottoRepositoryBuilder.setPurchasedLotto( IntStream.range(0, amount) .mapToObj(i -> new Lotto(pickFunction.get())) .toList() ); } - public List checkWinning(List winningNumbers, int bonusNumber) { - List prizes = lottoRepository.stream() - .map(lotto -> lotto.checkWinning(winningNumbers, bonusNumber)) - .toList(); - return LottoPrize.getPrizeTypes().stream() - .map(lottoPrize -> new WinningDataDto(lottoPrize, (int) Utility.countValue(prizes, lottoPrize))) - .toList(); + public void setWinningNumbers(String line) { + lottoRepositoryBuilder.setWinningNumber(line); + } + + public void setBonusNumber(int bonusNumber) { + lottoRepositoryBuilder.setBonusNumber(bonusNumber); + } + + public void composeLotto() { + this.lottoRepository = lottoRepositoryBuilder.build(); + } + + public List checkWinning() { + return lottoRepository.countGrade(); } public float calculateROI(List winningDataDto, int price) { long returnValue = winningDataDto.stream() - .mapToInt(dto -> dto.lottoPrize().getPrice() * dto.count()) + .mapToLong(dto -> (long) dto.lottoPrize().getPrice() * dto.count()) .sum(); - return (float) (returnValue / price); + return ((float) returnValue / price) * 100; + } + + public List> getLotto() { + return lottoRepositoryBuilder.getPurchasedLotto().stream() + .map(Lotto::getNumbers) + .toList(); } } diff --git a/src/main/java/lotto/service/RandomServiceImpl.java b/src/main/java/lotto/service/RandomServiceImpl.java index 913e391..530eace 100644 --- a/src/main/java/lotto/service/RandomServiceImpl.java +++ b/src/main/java/lotto/service/RandomServiceImpl.java @@ -1,10 +1,14 @@ package lotto.service; +import static lotto.utils.LottoConstants.LOTTO_NUMBER_COUNT; +import static lotto.utils.LottoConstants.LOWER_BOUND_NUMBER; +import static lotto.utils.LottoConstants.UPPER_BOUND_NUMBER; + import camp.nextstep.edu.missionutils.Randoms; import java.util.List; public class RandomServiceImpl implements RandomService { public List getLottoNumbers() { - return Randoms.pickUniqueNumbersInRange(1, 45, 6); + return Randoms.pickUniqueNumbersInRange(LOWER_BOUND_NUMBER, UPPER_BOUND_NUMBER, LOTTO_NUMBER_COUNT); } } diff --git a/src/main/java/lotto/utils/ExceptionConstants.java b/src/main/java/lotto/utils/ExceptionConstants.java index dc5cf5b..38acdb7 100644 --- a/src/main/java/lotto/utils/ExceptionConstants.java +++ b/src/main/java/lotto/utils/ExceptionConstants.java @@ -7,7 +7,8 @@ public enum ExceptionConstants { INVALID_LOTTO_RANGE("1부터 45 사이의 값을 입력해 주세요."), DUPLICATED_LOTTO_NUMBER("중복되지 않은 번호를 입력해주세요."), INVALID_LOTTO_NUMBER_FORM("당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6"), - INVALID_BONUS_NUMBER_FORM("보너스 번호 하나를 정확히 입력해주세요. ex)7"); + INVALID_BONUS_NUMBER_FORM("보너스 번호 하나를 정확히 입력해주세요. ex)7"), + INTERNAL_SERVER_ERROR("알 수 없는 이유로 프로그램이 정삭 작동하지 않습니다."); private final String message; diff --git a/src/main/java/lotto/utils/LottoConstants.java b/src/main/java/lotto/utils/LottoConstants.java new file mode 100644 index 0000000..b14c8b9 --- /dev/null +++ b/src/main/java/lotto/utils/LottoConstants.java @@ -0,0 +1,10 @@ +package lotto.utils; + +public class LottoConstants { + public static int LOTTO_PRICE = 1000; + public static int UPPER_BOUND_NUMBER = 45; + public static int LOWER_BOUND_NUMBER = 1; + public static int LOTTO_NUMBER_COUNT = 6; + + public static String NUMBER_SEPARATOR = ","; +} diff --git a/src/main/java/lotto/utils/MessageConstants.java b/src/main/java/lotto/utils/MessageConstants.java index 23aa74d..5981d7c 100644 --- a/src/main/java/lotto/utils/MessageConstants.java +++ b/src/main/java/lotto/utils/MessageConstants.java @@ -2,7 +2,7 @@ public enum MessageConstants { PURCHASE_GUIDE_MESSAGE("구입금액을 입력해 주세요."), - PURCHASE_AMOUNT_MESSAGE("8개를 구매했습니다."), + PURCHASE_AMOUNT_MESSAGE("%d개를 구매했습니다."), LOTTO_NUMBER_GUIDE_MESSAGE("당첨 번호를 입력해 주세요."), BONUS_NUMBER_GUIDE_MESSAGE("보너스 번호를 입력해 주세요."), WINNING_RESULTS_MESSAGE("당첨 통계\n---"), diff --git a/src/main/java/lotto/utils/Utility.java b/src/main/java/lotto/utils/Utility.java index cf1f04c..7c54173 100644 --- a/src/main/java/lotto/utils/Utility.java +++ b/src/main/java/lotto/utils/Utility.java @@ -1,10 +1,13 @@ package lotto.utils; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; public class Utility { + private static final String SEPARATOR = ","; + public static long countValue(List items, T target) { return items.stream().filter(item -> item.equals(target)).count(); } @@ -19,7 +22,7 @@ public static boolean isInRange(int value, int min, int max) { } public static boolean isInRange(List values, int min, int max) { - return values.stream() + return !values.stream() .map(value -> isInRange(value, min, max)) .toList() .contains(false); @@ -28,4 +31,10 @@ public static boolean isInRange(List values, int min, int max) { public static boolean isDividedByThousand(int value) { return value % 1000 == 0; } + + public static List parseInt(String line) { + return Arrays.stream(line.trim().split(SEPARATOR)) + .map(Integer::parseInt) + .toList(); + } } diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java index 11eab5d..3cd21b6 100644 --- a/src/main/java/lotto/view/InputView.java +++ b/src/main/java/lotto/view/InputView.java @@ -11,7 +11,7 @@ public InputView(InputProvider inputProvider) { this.inputProvider = inputProvider; } - public int getPrice() throws Exception { + public int getPrice() { try { System.out.println(MessageConstants.PURCHASE_GUIDE_MESSAGE.getMessage()); return Integer.parseInt(inputProvider.readLine()); @@ -20,7 +20,7 @@ public int getPrice() throws Exception { } } - public String getLottoNumber() throws Exception { + public String getLottoNumber() { try { System.out.println(MessageConstants.LOTTO_NUMBER_GUIDE_MESSAGE.getMessage()); return inputProvider.readLine(); @@ -29,7 +29,7 @@ public String getLottoNumber() throws Exception { } } - public int getBonusNumber() throws Exception { + public int getBonusNumber() { try { System.out.println(MessageConstants.BONUS_NUMBER_GUIDE_MESSAGE.getMessage()); return Integer.parseInt(inputProvider.readLine()); diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java index 0512df8..3b75f72 100644 --- a/src/main/java/lotto/view/OutputView.java +++ b/src/main/java/lotto/view/OutputView.java @@ -13,6 +13,10 @@ public void printLotto(List lottoNumber) { System.out.printf(WRAPPER + "%n", String.join(SEPARATOR, numbers)); } + public void printResultGuide(int amount) { + System.out.printf(MessageConstants.PURCHASE_AMOUNT_MESSAGE.getMessage() + "%n", amount); + } + public void printResult(List result) { System.out.println(MessageConstants.WINNING_RESULTS_MESSAGE.getMessage()); result.forEach((data) -> @@ -24,4 +28,8 @@ public void printResult(List result) { public void printRateOfReturn(float rateOfReturn) { System.out.printf(MessageConstants.RATE_OF_RETURN_MESSAGE.getMessage() + "%n", rateOfReturn); } + + public void printException(Exception exception) { + System.out.println(exception.getMessage()); + } } diff --git a/src/test/java/lotto/ApplicationTest.java b/src/test/java/lotto/ApplicationTest.java index a15c7d1..8a2283d 100644 --- a/src/test/java/lotto/ApplicationTest.java +++ b/src/test/java/lotto/ApplicationTest.java @@ -1,14 +1,18 @@ package lotto; -import camp.nextstep.edu.missionutils.test.NsTest; -import org.junit.jupiter.api.Test; - -import java.util.List; - import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest; import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; import static org.assertj.core.api.Assertions.assertThat; +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + class ApplicationTest extends NsTest { private static final String ERROR_MESSAGE = "[ERROR]"; @@ -54,6 +58,104 @@ class ApplicationTest extends NsTest { }); } + @Test + void 로또번호_중복_Error1() { + assertSimpleTest(() -> { + run("5000", "1,2,3,3,5,6", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Test + void 로또번호_중복_Error2() { + assertSimpleTest(() -> { + run("5000", "1,2,3,4,5,6", "4"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Test + void 로또번호_범위_Error() { + assertSimpleTest(() -> { + run("5000", "0,2,3,4,5,6", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + + run("5000", "1,2,3,4,5,46", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Test + void 로또번호_갯수_Error() { + assertSimpleTest(() -> { + runException("5000", "1,2,3,4,5", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + + runException("5000", "1,2,3,4,5,6,7", "7"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @ParameterizedTest + @MethodSource("provideNumbersForTest") + @DisplayName("로또 당첨 결과 테스트") + void 로또_당첨_등수_확인(String purchaseAmount, String winningNumbers, String bonusNumber, String expectedOutput) { + assertRandomUniqueNumbersInRangeTest( + () -> { + run(purchaseAmount, winningNumbers, bonusNumber); + assertThat(output()).contains(expectedOutput); + }, + List.of(1, 2, 3, 4, 5, 6) + ); + } + + static Stream provideNumbersForTest() { + return Stream.of( + Arguments.of("1000", "1,2,3,43,44,45", "7", "3개 일치 (5,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,44,45", "7", "4개 일치 (50,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,5,45", "7", "5개 일치 (1,500,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,5,45", "6", "5개 일치, 보너스 볼 일치 (30,000,000원) - 1개"), + Arguments.of("1000", "1,2,3,4,5,6", "7", "6개 일치 (2,000,000,000원) - 1개") + + ); + } + + @Test + void 수익률_반올림_테스트() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("3000", "1,2,3,4,5,6", "7"); + try { + assertThat(output()).contains(ERROR_MESSAGE); + } catch (AssertionError e) { + assertThat(output()).contains("총 수익률은 166.7%입니다."); + } + }, + List.of(1, 2, 3, 43, 44, 45), + List.of(40, 41, 42, 43, 44, 45), + List.of(40, 41, 42, 43, 44, 45) + ); + } + + @Test + @DisplayName("죽지도 않고 돌아온 정수 오버플로우") + void OverflowTest() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("2000", "1,2,3,4,5,6", "7"); + try { + assertThat(output()).contains(ERROR_MESSAGE); + } catch (AssertionError e) { + assertThat(output()).contains("6개 일치 (2,000,000,000원) - 2개"); + assertThat(output()).contains("총 수익률은 200000000.0%입니다."); + } + }, + List.of(1, 2, 3, 4, 5, 6), + List.of(1, 2, 3, 4, 5, 6), + List.of(1, 2, 3, 4, 5, 6) + ); + } + @Override public void runMain() { Application.main(new String[]{}); diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java index 70d07de..37da87f 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/LottoTest.java @@ -14,6 +14,12 @@ class LottoTest { .isInstanceOf(IllegalArgumentException.class); } + @Test + void 로또_번호의_개수가_6개가_안되면_예외가_발생한다() { + assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5))) + .isInstanceOf(IllegalArgumentException.class); + } + @DisplayName("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.") @Test void 로또_번호에_중복된_숫자가_있으면_예외가_발생한다() {