diff --git a/docs/README.md b/docs/README.md index e69de29bb2d..5eeb9e76c43 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,98 @@ +## ✏️ 구현할 기능 목록 + +___ + +## 입력 + +- [X] `로또 구입 금액` 입력 + + - 비기능적 요구사항 + - null 값이거나 빈 값인지 확인한다. + - 구입 금액은 정수만 가능하다. + - 구입 금액은 1이상의 자연수이다. + + - 기능적 요구사항 + - 구입 금액은 `1,000원 단위`이다. + + +- [X] `당첨 번호` 입력 (번호는 쉼표(,)를 기준으로 구분한다.) + + - 비기능적 요구사항 + ``` + 1,2,3,4,5,6 + ``` + - null 값이거나 빈 값인지 확인한다. + - 당첨 번호는 정수만 가능하다. + - 당첨 번호은 1이상의 자연수이다. + - `,,` 와 같은 잘못된 입력 값 검증 + - 가장 앞과 가장 뒤 입력 값은 숫자여야 한다. + + - 기능적 요구사항 + - 당첨 번호는 6개여야 합니다. + - 중복된 숫자는 입력 불가하다. + - 1 ~ 45 사이 숫자만 입력 가능하다. + +- [X] `보너스 번호` 입력 + + - 비기능적 요구사항 + - null 값이거나 빈 값인지 확인한다. + - 보너스 번호는 정수만 가능하다. + - 보너스 번호은 1이상의 자연수이다. + + - 기능적 요구사항 + - 1 ~ 45 사이 숫자만 입력 가능하다. + - 당첨 번호와 중복 불가하다. + +## 로또 번호 + +- [X] 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다. + - 로또 번호는 랜덤이다. + - 로또 번호는 중복되면 안된다. + - 로또 번호는 오름차순 정렬되어 출력된다. + +## 당첨 통계 + +- [X] 당첨 통계는 다음과 같다. + + ``` + 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%입니다. + + ``` + +- [X] 수익률 계산 + - 수익률은 소수점 둘째 자리에서 반올림한다. + +## 출력 + +- [X] `구입금액을 입력해 주세요.` 출력 + +- [X] `?개를 구매했습니다.` 출력 + - 로또 번호 출력 (랜덤) + +- [X] `당첨 번호를 입력해 주세요.` 출력 + +- [X] `보너스 번호를 입력해 주세요.` 출력 + +- [X] `당첨 통계` 출력 + ``` + --- + 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%입니다. + + ``` + +--- + +## 공통 검증 + +- [X] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 해당 부분부터 다시 입력을 받는다. +- [X] 1,000단위마다 콤마를 찍기 \ No newline at end of file diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922ba44..33533b1f5c3 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,20 @@ package lotto; +import lotto.config.Configuration; +import lotto.controller.BuyLottoController; +import lotto.controller.LottoController; +import lotto.dto.BuyLottoDto; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + Configuration configuration = new Configuration(); + LottoController lottoController = configuration.getLottoController(); + BuyLottoController buyLottoController = configuration.getBuyLottoController(); + executeControllers(buyLottoController, lottoController); + } + + private static void executeControllers(BuyLottoController buyLottoController, LottoController lottoController) { + BuyLottoDto buyLottoDto = buyLottoController.buyLotto(); + lottoController.getStatistics(buyLottoDto); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 519793d1f73..00000000000 --- 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(); - } - } - - // TODO: 추가 기능 구현 -} diff --git a/src/main/java/lotto/config/Configuration.java b/src/main/java/lotto/config/Configuration.java new file mode 100644 index 00000000000..d78f10648f8 --- /dev/null +++ b/src/main/java/lotto/config/Configuration.java @@ -0,0 +1,37 @@ +package lotto.config; + +import lotto.controller.BuyLottoController; +import lotto.controller.LottoController; +import lotto.service.BuyLottoService; +import lotto.service.CalculateStatisticService; +import lotto.validation.BonusNumberValidator; +import lotto.validation.PurchasePriceValidator; +import lotto.validation.WinnerNumberValidator; +import lotto.view.input.InputView; +import lotto.view.input.template.InputValidatorTemplate; +import lotto.view.output.OutputView; + +public class Configuration { + InputView inputView = createInputView(); + OutputView outputView = new OutputView(); + BuyLottoService buyLottoService = new BuyLottoService(outputView); + CalculateStatisticService calculateStatisticService = new CalculateStatisticService(outputView); + BuyLottoController buyLottoController = new BuyLottoController(inputView, outputView, buyLottoService); + LottoController lottoController = new LottoController(inputView, outputView, calculateStatisticService); + + public BuyLottoController getBuyLottoController() { + return buyLottoController; + } + + public LottoController getLottoController() { + return lottoController; + } + + private InputView createInputView() { + PurchasePriceValidator purchasePriceValidator = new PurchasePriceValidator(); + WinnerNumberValidator winnerNumberValidator = new WinnerNumberValidator(); + BonusNumberValidator bonusNumberValidator = new BonusNumberValidator(); + InputValidatorTemplate template = new InputValidatorTemplate(); + return new InputView(purchasePriceValidator, winnerNumberValidator, bonusNumberValidator, template); + } +} diff --git a/src/main/java/lotto/constant/Constant.java b/src/main/java/lotto/constant/Constant.java new file mode 100644 index 00000000000..cc7abcfa222 --- /dev/null +++ b/src/main/java/lotto/constant/Constant.java @@ -0,0 +1,10 @@ +package lotto.constant; + +public class Constant { + public static int MIN_LOTTO_NUMBER = 1; + public static int MAX_LOTTO_NUMBER = 45; + public static int LOTTO_SIZE = 6; + public static int THOUSAND = 1000; + public static int ZERO = 0; + +} diff --git a/src/main/java/lotto/constant/PrintOutMessage.java b/src/main/java/lotto/constant/PrintOutMessage.java new file mode 100644 index 00000000000..8b8e75d6e33 --- /dev/null +++ b/src/main/java/lotto/constant/PrintOutMessage.java @@ -0,0 +1,42 @@ +package lotto.constant; + +import java.text.NumberFormat; +import lotto.model.PurchasePrice; + +public enum PrintOutMessage { + PLZ_INPUT_PURCHASE_PRICE("구입금액을 입력해 주세요."), + LOTTO_COUNT("개를 구매했습니다."), + PLZ_INPUT_WINNER_NUMBER("당첨 번호를 입력해 주세요."), + PLZ_INPUT_BONUS_NUMBER("보너스 번호를 입력해 주세요."), + STATISTIC("당첨 통계"), + DIVISION_LINE("---"), + EARNING_POINT("총 수익률은 %s%%입니다."), + EMPTY_LINE("\n"), + COUNT("개"); + + public final String message; + + PrintOutMessage(String message) { + this.message = message; + } + + public String getMessage(String matchingCount) { + return String.format(message, matchingCount); + } + + public static String printLottoCount(int count) { + return count + LOTTO_COUNT.message; + } + + + public static String calculateEarningRate(long totalPrize, PurchasePrice purchasePrice) { + double earningRate = (double) totalPrize / purchasePrice.getPrice() * 100; + + String formattedRate = String.format("%.2f", earningRate); + + NumberFormat nf = NumberFormat.getInstance(); + String formattedNumber = nf.format(Double.parseDouble(formattedRate)); + + return EARNING_POINT.getMessage(formattedNumber); + } +} diff --git a/src/main/java/lotto/constant/Rank.java b/src/main/java/lotto/constant/Rank.java new file mode 100644 index 00000000000..d1d7249eef5 --- /dev/null +++ b/src/main/java/lotto/constant/Rank.java @@ -0,0 +1,44 @@ +package lotto.constant; + +import java.util.Arrays; + +public enum Rank { + NO_MATCH(0, 0, false, "0개 일치 (0원) - "), + THREE_MATCH(3, 5000, false, "3개 일치 (5,000원) - "), + FOUR_MATCH(4, 50000, false, "4개 일치 (50,000원) - "), + FIVE_MATCH_NOT_BONUS(5, 1500000, false, "5개 일치 (1,500,000원) - "), + FIVE_MATCH_PLUS_BONUS(5, 30000000, true, "5개 일치, 보너스 볼 일치 (30,000,000원) - "), + SIX_MATCH(6, 2000000000, false, "6개 일치 (2,000,000,000원) - "); + + public final int match; + public final int prizeMoney; + public final boolean needBonus; + public final String message; + + Rank(int match, int prizeMoney, boolean needBonus, String message) { + this.match = match; + this.prizeMoney = prizeMoney; + this.needBonus = needBonus; + this.message = message; + } + + private static Rank getInstance(long totalCount) { + return Arrays.stream(Rank.values()) + .filter(rank -> rank.match == totalCount) + .findAny() + .orElse(NO_MATCH); + } + + public static Rank findRank(long winnerMatch, boolean bonusMatch) { + Rank rank = getInstance(winnerMatch); + + if (winnerMatch == FIVE_MATCH_NOT_BONUS.match) { + if (bonusMatch) { + return FIVE_MATCH_PLUS_BONUS; + } + return FIVE_MATCH_NOT_BONUS; + } + return rank; + } +} + diff --git a/src/main/java/lotto/controller/BuyLottoController.java b/src/main/java/lotto/controller/BuyLottoController.java new file mode 100644 index 00000000000..953e9f621c1 --- /dev/null +++ b/src/main/java/lotto/controller/BuyLottoController.java @@ -0,0 +1,72 @@ +package lotto.controller; + +import java.util.List; +import lotto.dto.BuyLottoDto; +import lotto.model.BonusNumber; +import lotto.model.LottoNumbers; +import lotto.model.PurchasePrice; +import lotto.model.WinnerNumber; +import lotto.service.BuyLottoService; +import lotto.view.input.InputView; +import lotto.view.output.OutputView; + +public class BuyLottoController { + private final InputView inputView; + private final OutputView outputView; + private final BuyLottoService buyLottoService; + + public BuyLottoController(InputView inputView, OutputView outputView, BuyLottoService buyLottoService) { + this.inputView = inputView; + this.outputView = outputView; + this.buyLottoService = buyLottoService; + } + + public BuyLottoDto buyLotto() { + PurchasePrice purchasePrice = createPurchasePrice(); // 구입 금액 입력 + + LottoNumbers lottoNumbers = buyLottoService.buyLotto(purchasePrice); // 로또 구입 + + WinnerNumber winnerNumber = createWinnerNumber(); // 당첨 번호 입력 + + BonusNumber bonusNumber = createBonusNumber(winnerNumber); // 보너스 번호 입력 + + return BuyLottoDto.createBuyLottoDto(purchasePrice, lottoNumbers, winnerNumber, bonusNumber); + } + + private BonusNumber createBonusNumber(WinnerNumber winnerNumber) { + while (true) { + try { + outputView.printBonusNumberMessage(); + int bonus = inputView.inputBonusNumber(); + return BonusNumber.createBonusNumber(winnerNumber.getWinnerNumbers(), bonus); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } + + private WinnerNumber createWinnerNumber() { + while (true) { + try { + outputView.printWinnerNumberMessage(); + List winners = inputView.inputWinnerNumber(); + return WinnerNumber.createWinnerNumber(winners); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } + + private PurchasePrice createPurchasePrice() { + while (true) { + try { + outputView.printPurchasePriceMessage(); + int price = inputView.readPrice(); + PurchasePrice purchasePrice = PurchasePrice.createPurchasePrice(price); + return purchasePrice; + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } +} diff --git a/src/main/java/lotto/controller/LottoController.java b/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 00000000000..377ec23eaa8 --- /dev/null +++ b/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,34 @@ +package lotto.controller; + +import lotto.dto.BuyLottoDto; +import lotto.dto.StatisticsDto; +import lotto.model.Statistics; +import lotto.service.CalculateStatisticService; +import lotto.view.input.InputView; +import lotto.view.output.OutputView; + +public class LottoController { + private final InputView inputView; + private final OutputView outputView; + private final CalculateStatisticService calculateStatisticService; + + public LottoController(InputView inputView, OutputView outputView, + CalculateStatisticService calculateStatisticService) { + this.inputView = inputView; + this.outputView = outputView; + this.calculateStatisticService = calculateStatisticService; + } + + public void getStatistics(BuyLottoDto buyLottoDto) { + // 당첨, 보너스 번호 & 로또 번호 비교 + Statistics statistics = buyLottoDto.calculateMatching(); + + StatisticsDto statisticsDto = StatisticsDto.createStatisticsDto(statistics); + outputView.printStatistic(statisticsDto.getStatistics().getResult()); + + long totalPrize = calculateStatisticService.calculatePrizeMoney( + statisticsDto.getStatistics().getResult());// 상금 계산 + outputView.printEarningRate(totalPrize, buyLottoDto.getPurchasePrice()); + } + +} diff --git a/src/main/java/lotto/dto/BuyLottoDto.java b/src/main/java/lotto/dto/BuyLottoDto.java new file mode 100644 index 00000000000..ebd0552535c --- /dev/null +++ b/src/main/java/lotto/dto/BuyLottoDto.java @@ -0,0 +1,40 @@ +package lotto.dto; + +import lotto.model.BonusNumber; +import lotto.model.LottoNumbers; +import lotto.model.PurchasePrice; +import lotto.model.Statistics; +import lotto.model.WinnerNumber; + +public class BuyLottoDto { + private PurchasePrice purchasePrice; + private LottoNumbers lottoNumbers; + private WinnerNumber winnerNumber; + private BonusNumber bonusNumber; + + private BuyLottoDto(PurchasePrice purchasePrice, LottoNumbers lottoNumbers, WinnerNumber winnerNumber, + BonusNumber bonusNumber) { + this.purchasePrice = purchasePrice; + this.lottoNumbers = lottoNumbers; + this.winnerNumber = winnerNumber; + this.bonusNumber = bonusNumber; + } + + public static BuyLottoDto createBuyLottoDto(PurchasePrice purchasePrice, + LottoNumbers lottoNumbers, + WinnerNumber winnerNumber, + BonusNumber bonusNumber) { + return new BuyLottoDto(purchasePrice, lottoNumbers, winnerNumber, bonusNumber); + } + + public PurchasePrice getPurchasePrice() { + return purchasePrice; + } + + public Statistics calculateMatching() { + Statistics statistics = Statistics.createStatistics(); + statistics.calculateMatching(this.lottoNumbers, this.winnerNumber, this.bonusNumber); + return statistics; + } + +} diff --git a/src/main/java/lotto/dto/StatisticsDto.java b/src/main/java/lotto/dto/StatisticsDto.java new file mode 100644 index 00000000000..75754be1548 --- /dev/null +++ b/src/main/java/lotto/dto/StatisticsDto.java @@ -0,0 +1,19 @@ +package lotto.dto; + +import lotto.model.Statistics; + +public class StatisticsDto { + private Statistics statistics; + + private StatisticsDto(Statistics statistics) { + this.statistics = statistics; + } + + public static StatisticsDto createStatisticsDto(Statistics statistics) { + return new StatisticsDto(statistics); + } + + public Statistics getStatistics() { + return statistics; + } +} diff --git a/src/main/java/lotto/exception/ErrorInputException.java b/src/main/java/lotto/exception/ErrorInputException.java new file mode 100644 index 00000000000..c49c6d8efbb --- /dev/null +++ b/src/main/java/lotto/exception/ErrorInputException.java @@ -0,0 +1,51 @@ +package lotto.exception; + +public class ErrorInputException extends IllegalArgumentException { + public enum ErrorMessage { + PURCHASE_PRICE_CANNOT_BE_NULL_OR_EMPTY("구입 금액은 빈 값이거나 null 값 일 수 없습니다. 다시 입력해 주세요."), + PURCHASE_PRICE_IS_NOT_INTEGER("구입 금액은 정수만 입력 가능합니다. 다시 입력해 주세요."), + PURCHASE_PRICE_IS_MORE_THAN_ONE("구입 금액은 1이상의 자연수여야 합니다. 다시 입력해 주세요."), + PURCHASE_PRICE_CAN_DIVIDE_BY_THOUSAND("구입 금액은 1,000원 단위여야 합니다.다시 입력해 주세요."), + + LOTTO_NUMBER_CAN_NOT_BE_DUPLICATE("로또 번호는 중복된 숫자로 구성되면 안됩니다. 다시 입력해 주세요."), + + WINNER_NUMBER_CANNOT_BE_NULL_OR_EMPTY("당첨 번호는 빈 값이거나 null 값 일 수 없습니다. 다시 입력해 주세요."), + WINNER_NUMBER_IS_NOT_INTEGER("당첨 번호는 정수만 입력 가능합니다. 다시 입력해 주세요."), + WINNER_NUMBER_IS_MORE_THAN_ONE("당첨 번호는 1이상의 자연수여야 합니다. 다시 입력해 주세요."), + WINNER_NUMBER_ERROR_INPUT("유효하지 않은 입력 입니다. 다시 입력해 주세요."), + WINNER_NUMBER_SIZE_IS_SIX("당첨 번호는 6개로 구성되어야 합니다. 다시 입력해 주세요."), + WINNER_NUMBER_CAN_NOT_BE_DUPLICATE("당첨 번호는 중복된 숫자로 구성되면 안됩니다. 다시 입력해 주세요."), + WINNER_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE("당첨 번호는 1 ~ 45 사이 숫자여야 합니다. 다시 입력해 주세요."), + + + BONUS_NUMBER_CANNOT_BE_NULL_OR_EMPTY("보너스 번호는 빈 값이거나 null 값 일 수 없습니다. 다시 입력해 주세요."), + BONUS_NUMBER_IS_NOT_INTEGER("보너스 번호는 정수만 입력 가능합니다. 다시 입력해 주세요."), + BONUS_NUMBER_IS_MORE_THAN_ONE("보너스 번호는 1이상의 자연수여야 합니다. 다시 입력해 주세요."), + BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE("보너스 번호는 1 ~ 45 사이 숫자여야 합니다. 다시 입력해 주세요."), + BONUS_NUMBER_CAN_NOT_BE_DUPLICATE("보너스 번호는 당첨 번호와 중복된 숫자이면 안됩니다. 다시 입력해 주세요."), + + CAN_NOT_STATISTIC("당첨 조회가 불가능한 조건입니다."); + + + ErrorMessage(final String message) { + this.message = message; + } + + private final String errorMessage = "[ERROR] "; + + private final String message; + + public String getErrorMessage() { + return errorMessage; + } + + public String getMessage() { + return message; + } + + } + + public ErrorInputException(ErrorMessage message) { + super(message.getErrorMessage() + message.getMessage()); + } +} diff --git a/src/main/java/lotto/model/BonusNumber.java b/src/main/java/lotto/model/BonusNumber.java new file mode 100644 index 00000000000..dcef1404fe5 --- /dev/null +++ b/src/main/java/lotto/model/BonusNumber.java @@ -0,0 +1,40 @@ +package lotto.model; + +import static lotto.constant.Constant.MAX_LOTTO_NUMBER; +import static lotto.constant.Constant.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_CAN_NOT_BE_DUPLICATE; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE; + +import java.util.List; +import lotto.exception.ErrorInputException; + +public class BonusNumber { + + private final int bonusNumber; + + private BonusNumber(List winnerNumber, int bonusNumber) { + this.bonusNumber = bonusNumber; + isDuplicate(winnerNumber); + isBetweenOneAndFortyFive(); + } + + public boolean isSameBonusNumber(BonusNumber other) { + return other.bonusNumber == this.bonusNumber; + } + + public static BonusNumber createBonusNumber(List winnerNumber, int bonusNumber) { + return new BonusNumber(winnerNumber, bonusNumber); + } + + private void isDuplicate(List winnerNumbers) { + if (winnerNumbers.contains(bonusNumber)) { + throw new ErrorInputException(BONUS_NUMBER_CAN_NOT_BE_DUPLICATE); + } + } + + private void isBetweenOneAndFortyFive() { + if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) { + throw new ErrorInputException(BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE); + } + } +} diff --git a/src/main/java/lotto/model/Lotto.java b/src/main/java/lotto/model/Lotto.java new file mode 100644 index 00000000000..0fac512f3d1 --- /dev/null +++ b/src/main/java/lotto/model/Lotto.java @@ -0,0 +1,48 @@ +package lotto.model; + +import static camp.nextstep.edu.missionutils.Randoms.pickUniqueNumbersInRange; +import static lotto.constant.Constant.LOTTO_SIZE; +import static lotto.constant.Constant.MAX_LOTTO_NUMBER; +import static lotto.constant.Constant.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorInputException.ErrorMessage.LOTTO_NUMBER_CAN_NOT_BE_DUPLICATE; + +import java.util.Collections; +import java.util.List; +import lotto.exception.ErrorInputException; + +public class Lotto { + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + isDuplicate(numbers); + sortLotto(numbers); + this.numbers = numbers; + } + + public static Lotto createLotto() { + List pickedNumbers = pickUniqueNumbersInRange(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER, LOTTO_SIZE); + return new Lotto(pickedNumbers); + } + + public List getNumbers() { + return Collections.unmodifiableList(numbers); + } + + private void validate(List numbers) { + if (numbers.size() != LOTTO_SIZE) { + throw new IllegalArgumentException(); + } + } + + private void isDuplicate(List numbers) { + long countDistinct = numbers.stream().distinct().count(); + if (countDistinct < numbers.size()) { + throw new ErrorInputException(LOTTO_NUMBER_CAN_NOT_BE_DUPLICATE); + } + } + + private void sortLotto(List numbers) { + Collections.sort(numbers); + } +} diff --git a/src/main/java/lotto/model/LottoNumbers.java b/src/main/java/lotto/model/LottoNumbers.java new file mode 100644 index 00000000000..acc45cd51ca --- /dev/null +++ b/src/main/java/lotto/model/LottoNumbers.java @@ -0,0 +1,20 @@ +package lotto.model; + +import java.util.Collections; +import java.util.List; + +public class LottoNumbers { + private final List numbers; + + private LottoNumbers(List numbers) { + this.numbers = numbers; + } + + public static LottoNumbers createLottoNumbers(List numbers) { + return new LottoNumbers(numbers); + } + + public List getNumbers() { + return Collections.unmodifiableList(numbers); + } +} diff --git a/src/main/java/lotto/model/PurchasePrice.java b/src/main/java/lotto/model/PurchasePrice.java new file mode 100644 index 00000000000..e512cae4aff --- /dev/null +++ b/src/main/java/lotto/model/PurchasePrice.java @@ -0,0 +1,34 @@ +package lotto.model; + +import static lotto.constant.Constant.THOUSAND; +import static lotto.constant.Constant.ZERO; +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_CAN_DIVIDE_BY_THOUSAND; + +import lotto.exception.ErrorInputException; + +public class PurchasePrice { + private final int price; + + private PurchasePrice(int price) { + this.price = price; + isDividedByThousand(); + } + + public static PurchasePrice createPurchasePrice(int price) { + return new PurchasePrice(price); + } + + public int getPrice() { + return price; + } + + private void isDividedByThousand() { + if (price % THOUSAND != ZERO) { + throw new ErrorInputException(PURCHASE_PRICE_CAN_DIVIDE_BY_THOUSAND); + } + } + + public int calculateLottoCount() { + return price / THOUSAND; + } +} diff --git a/src/main/java/lotto/model/Statistics.java b/src/main/java/lotto/model/Statistics.java new file mode 100644 index 00000000000..ff60ffcbf4e --- /dev/null +++ b/src/main/java/lotto/model/Statistics.java @@ -0,0 +1,57 @@ +package lotto.model; + +import static lotto.constant.Constant.ZERO; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import lotto.constant.Rank; + +public class Statistics { + private final Map result; + + private Statistics() { + result = new EnumMap<>(Rank.class); + for (Rank rank : Rank.values()) { + result.put(rank, ZERO); + } + } + + public static Statistics createStatistics() { + return new Statistics(); + } + + public Map getResult() { + return Collections.unmodifiableMap(result); + } + + public void calculateMatching(LottoNumbers lottoNumbers, WinnerNumber winnerNumber, BonusNumber bonusNumber) { + List lottos = lottoNumbers.getNumbers(); + + for (Lotto lotto : lottos) { + Rank rank = findRank(lotto, winnerNumber, bonusNumber); + int lottoCount = result.get(rank) + 1; + + result.put(rank, lottoCount); + } + } + + private Rank findRank(Lotto lotto, WinnerNumber winnerNumber, BonusNumber bonusNumber) { + long winnerMatch = countMatchingWinnerNumber(lotto, winnerNumber); + boolean bonusMatch = matchingBonusNumber(lotto, bonusNumber); + + return Rank.findRank(winnerMatch, bonusMatch); + } + + private boolean matchingBonusNumber(Lotto lotto, BonusNumber bonusNumber) { + return lotto.getNumbers().stream() + .anyMatch(number -> number.equals(bonusNumber.isSameBonusNumber(bonusNumber))); + } + + private long countMatchingWinnerNumber(Lotto lotto, WinnerNumber winnerNumber) { + return lotto.getNumbers().stream() + .filter(winnerNumber.getWinnerNumbers()::contains) + .count(); + } +} diff --git a/src/main/java/lotto/model/WinnerNumber.java b/src/main/java/lotto/model/WinnerNumber.java new file mode 100644 index 00000000000..2793f43173e --- /dev/null +++ b/src/main/java/lotto/model/WinnerNumber.java @@ -0,0 +1,52 @@ +package lotto.model; + +import static lotto.constant.Constant.LOTTO_SIZE; +import static lotto.constant.Constant.MAX_LOTTO_NUMBER; +import static lotto.constant.Constant.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_CAN_NOT_BE_DUPLICATE; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_SIZE_IS_SIX; + +import java.util.Collections; +import java.util.List; +import lotto.exception.ErrorInputException; + +public class WinnerNumber { + private final List winnerNumbers; + + private WinnerNumber(List winnerNumbers) { + this.winnerNumbers = winnerNumbers; + validate(); + isDuplicate(); + isBetweenOneAndFortyFive(); + } + + public static WinnerNumber createWinnerNumber(List winnerNumbers) { + return new WinnerNumber(winnerNumbers); + } + + public List getWinnerNumbers() { + return Collections.unmodifiableList(winnerNumbers); + } + + private void validate() { + if (winnerNumbers.size() != LOTTO_SIZE) { + throw new ErrorInputException(WINNER_NUMBER_SIZE_IS_SIX); + } + } + + private void isDuplicate() { + long countDistinct = winnerNumbers.stream().distinct().count(); + if (countDistinct < winnerNumbers.size()) { + throw new ErrorInputException(WINNER_NUMBER_CAN_NOT_BE_DUPLICATE); + } + } + + private void isBetweenOneAndFortyFive() { + boolean allBetweenOneAndFortyFive = winnerNumbers.stream() + .allMatch(integer -> integer >= MIN_LOTTO_NUMBER && integer <= MAX_LOTTO_NUMBER); + if (!allBetweenOneAndFortyFive) { + throw new ErrorInputException(WINNER_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE); + } + } +} diff --git a/src/main/java/lotto/service/BuyLottoService.java b/src/main/java/lotto/service/BuyLottoService.java new file mode 100644 index 00000000000..354b56c5a94 --- /dev/null +++ b/src/main/java/lotto/service/BuyLottoService.java @@ -0,0 +1,27 @@ +package lotto.service; + +import java.util.ArrayList; +import java.util.List; +import lotto.model.Lotto; +import lotto.model.LottoNumbers; +import lotto.model.PurchasePrice; +import lotto.view.output.OutputView; + +public class BuyLottoService { + private final OutputView outputView; + + public BuyLottoService(OutputView outputView) { + this.outputView = outputView; + } + + public LottoNumbers buyLotto(PurchasePrice purchasePrice) { + int lottoCount = purchasePrice.calculateLottoCount(); + List lotto = new ArrayList<>(); + for (int i = 0; i < lottoCount; i++) { + lotto.add(Lotto.createLotto()); + } + LottoNumbers lottoNumbers = LottoNumbers.createLottoNumbers(lotto); + outputView.printLottoCount(lottoCount, lottoNumbers); + return lottoNumbers; + } +} diff --git a/src/main/java/lotto/service/CalculateStatisticService.java b/src/main/java/lotto/service/CalculateStatisticService.java new file mode 100644 index 00000000000..d4378c10965 --- /dev/null +++ b/src/main/java/lotto/service/CalculateStatisticService.java @@ -0,0 +1,23 @@ +package lotto.service; + +import static lotto.constant.Constant.ZERO; + +import java.util.Map; +import lotto.constant.Rank; +import lotto.view.output.OutputView; + +public class CalculateStatisticService { + private final OutputView outputView; + + public CalculateStatisticService(OutputView outputView) { + this.outputView = outputView; + } + + public long calculatePrizeMoney(Map statistics) { + long totalPrizeMoney = ZERO; + for (Rank rank : statistics.keySet()) { + totalPrizeMoney += rank.prizeMoney * statistics.getOrDefault(rank, ZERO); + } + return totalPrizeMoney; + } +} diff --git a/src/main/java/lotto/validation/BonusNumberValidator.java b/src/main/java/lotto/validation/BonusNumberValidator.java new file mode 100644 index 00000000000..f0aae943e85 --- /dev/null +++ b/src/main/java/lotto/validation/BonusNumberValidator.java @@ -0,0 +1,40 @@ +package lotto.validation; + +import static lotto.constant.Constant.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_CANNOT_BE_NULL_OR_EMPTY; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_IS_MORE_THAN_ONE; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_IS_NOT_INTEGER; + +import lotto.exception.ErrorInputException; + +public class BonusNumberValidator { + + public static void isBlank(String input) { + if (input == null || input.trim().isEmpty()) { + throw new ErrorInputException(BONUS_NUMBER_CANNOT_BE_NULL_OR_EMPTY); + } + } + + public int isInteger(String input) { + if (!isNumeric(input)) { + throw new ErrorInputException(BONUS_NUMBER_IS_NOT_INTEGER); + } + return Integer.parseInt(input); + } + + private boolean isNumeric(String input) { + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public int moreThanOne(int input) { + if (input < MIN_LOTTO_NUMBER) { + throw new ErrorInputException(BONUS_NUMBER_IS_MORE_THAN_ONE); + } + return input; + } +} diff --git a/src/main/java/lotto/validation/PurchasePriceValidator.java b/src/main/java/lotto/validation/PurchasePriceValidator.java new file mode 100644 index 00000000000..0accb065b6c --- /dev/null +++ b/src/main/java/lotto/validation/PurchasePriceValidator.java @@ -0,0 +1,39 @@ +package lotto.validation; + +import static lotto.constant.Constant.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_CANNOT_BE_NULL_OR_EMPTY; +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_IS_MORE_THAN_ONE; +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_IS_NOT_INTEGER; + +import lotto.exception.ErrorInputException; + +public class PurchasePriceValidator { + public static void isBlank(String input) { + if (input == null || input.trim().isEmpty()) { + throw new ErrorInputException(PURCHASE_PRICE_CANNOT_BE_NULL_OR_EMPTY); + } + } + + public int isInteger(String input) { + if (!isNumeric(input)) { + throw new ErrorInputException(PURCHASE_PRICE_IS_NOT_INTEGER); + } + return Integer.parseInt(input); + } + + private boolean isNumeric(String input) { + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public int moreThanOne(int input) { + if (input < MIN_LOTTO_NUMBER) { + throw new ErrorInputException(PURCHASE_PRICE_IS_MORE_THAN_ONE); + } + return input; + } +} diff --git a/src/main/java/lotto/validation/WinnerNumberValidator.java b/src/main/java/lotto/validation/WinnerNumberValidator.java new file mode 100644 index 00000000000..cd8e8672f0f --- /dev/null +++ b/src/main/java/lotto/validation/WinnerNumberValidator.java @@ -0,0 +1,58 @@ +package lotto.validation; + +import static lotto.constant.Constant.MIN_LOTTO_NUMBER; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_CANNOT_BE_NULL_OR_EMPTY; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_ERROR_INPUT; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_IS_MORE_THAN_ONE; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_IS_NOT_INTEGER; + +import java.util.ArrayList; +import java.util.List; +import lotto.exception.ErrorInputException; + +public class WinnerNumberValidator { + public static void isBlank(String input) { + if (input == null || input.trim().isEmpty()) { + throw new ErrorInputException(WINNER_NUMBER_CANNOT_BE_NULL_OR_EMPTY); + } + } + + public void checkCommaError(String input) { + if (input.contains(",,")) { + throw new ErrorInputException(WINNER_NUMBER_ERROR_INPUT); + } + if (!Character.isDigit(input.charAt(input.length() - 1)) || !Character.isDigit(input.charAt(0))) { + throw new ErrorInputException(WINNER_NUMBER_ERROR_INPUT); + } + } + + public List isInteger(String input) { + String[] split = input.replace(" ", "").split(","); + List numbers = new ArrayList<>(); + for (String s : split) { + if (!isNumeric(s)) { + throw new ErrorInputException(WINNER_NUMBER_IS_NOT_INTEGER); + } + numbers.add(Integer.parseInt(s)); + } + return numbers; + } + + + private boolean isNumeric(String input) { + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public List moreThanOne(List input) { + boolean allMoreThanOne = input.stream().allMatch(integer -> integer >= MIN_LOTTO_NUMBER); + if (!allMoreThanOne) { + throw new ErrorInputException(WINNER_NUMBER_IS_MORE_THAN_ONE); + } + return input; + } +} diff --git a/src/main/java/lotto/view/input/InputView.java b/src/main/java/lotto/view/input/InputView.java new file mode 100644 index 00000000000..307c8397296 --- /dev/null +++ b/src/main/java/lotto/view/input/InputView.java @@ -0,0 +1,55 @@ +package lotto.view.input; + +import java.util.List; +import lotto.validation.BonusNumberValidator; +import lotto.validation.PurchasePriceValidator; +import lotto.validation.WinnerNumberValidator; +import lotto.view.input.template.InputValidatorTemplate; + +public class InputView { + private final PurchasePriceValidator purchasePriceValidator; + private final WinnerNumberValidator winnerNumberValidator; + private final BonusNumberValidator bonusNumberValidator; + private final InputValidatorTemplate template; + + public InputView(PurchasePriceValidator purchasePriceValidator, WinnerNumberValidator winnerNumberValidator, + BonusNumberValidator bonusNumberValidator, InputValidatorTemplate template) { + this.purchasePriceValidator = purchasePriceValidator; + this.winnerNumberValidator = winnerNumberValidator; + this.bonusNumberValidator = bonusNumberValidator; + this.template = template; + } + + + public int readPrice() { + return template.InputWithValidation(this::purchasePriceValidate); + } + + public List inputWinnerNumber() { + return template.InputWithValidation(this::WinnerNumberValidate); + } + + public int inputBonusNumber() { + return template.InputWithValidation(this::BonusNumberValidate); + } + + private int purchasePriceValidate(String input) { + purchasePriceValidator.isBlank(input); + int purchasePrice = purchasePriceValidator.isInteger(input); + return purchasePriceValidator.moreThanOne(purchasePrice); + } + + + private List WinnerNumberValidate(String input) { + winnerNumberValidator.isBlank(input); + winnerNumberValidator.checkCommaError(input); + List winnerNumber = winnerNumberValidator.isInteger(input); + return winnerNumberValidator.moreThanOne(winnerNumber); + } + + private int BonusNumberValidate(String input) { + bonusNumberValidator.isBlank(input); + int bonusNumber = bonusNumberValidator.isInteger(input); + return bonusNumberValidator.moreThanOne(bonusNumber); + } +} diff --git a/src/main/java/lotto/view/input/template/InputValidatorCallback.java b/src/main/java/lotto/view/input/template/InputValidatorCallback.java new file mode 100644 index 00000000000..a0dde238318 --- /dev/null +++ b/src/main/java/lotto/view/input/template/InputValidatorCallback.java @@ -0,0 +1,5 @@ +package lotto.view.input.template; + +public interface InputValidatorCallback { + T validate(String input); +} \ No newline at end of file diff --git a/src/main/java/lotto/view/input/template/InputValidatorTemplate.java b/src/main/java/lotto/view/input/template/InputValidatorTemplate.java new file mode 100644 index 00000000000..3827a7b4af8 --- /dev/null +++ b/src/main/java/lotto/view/input/template/InputValidatorTemplate.java @@ -0,0 +1,16 @@ +package lotto.view.input.template; + +import static camp.nextstep.edu.missionutils.Console.readLine; + +public class InputValidatorTemplate { + public T InputWithValidation(InputValidatorCallback validator) { + while (true) { + try { + String input = readLine(); + return validator.validate(input); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/lotto/view/output/OutputView.java b/src/main/java/lotto/view/output/OutputView.java new file mode 100644 index 00000000000..19eb153e113 --- /dev/null +++ b/src/main/java/lotto/view/output/OutputView.java @@ -0,0 +1,62 @@ +package lotto.view.output; + +import static lotto.constant.PrintOutMessage.COUNT; +import static lotto.constant.PrintOutMessage.DIVISION_LINE; +import static lotto.constant.PrintOutMessage.EMPTY_LINE; +import static lotto.constant.PrintOutMessage.PLZ_INPUT_BONUS_NUMBER; +import static lotto.constant.PrintOutMessage.PLZ_INPUT_PURCHASE_PRICE; +import static lotto.constant.PrintOutMessage.PLZ_INPUT_WINNER_NUMBER; +import static lotto.constant.PrintOutMessage.STATISTIC; +import static lotto.constant.PrintOutMessage.calculateEarningRate; + +import java.util.Map; +import lotto.constant.PrintOutMessage; +import lotto.constant.Rank; +import lotto.model.Lotto; +import lotto.model.LottoNumbers; +import lotto.model.PurchasePrice; + +public class OutputView { + public void printPurchasePriceMessage() { + System.out.println(PLZ_INPUT_PURCHASE_PRICE.message); + } + + public void printLottoCount(int lottoCount, LottoNumbers lottoNumbers) { + System.out.println(PrintOutMessage.printLottoCount(lottoCount)); + for (Lotto number : lottoNumbers.getNumbers()) { + System.out.println(number.getNumbers()); + } + System.out.println(EMPTY_LINE.message); + } + + public void printWinnerNumberMessage() { + System.out.println(PLZ_INPUT_WINNER_NUMBER.message); + } + + public void printBonusNumberMessage() { + System.out.println(PLZ_INPUT_BONUS_NUMBER.message); + } + + public void printStatistic(Map matchingResult) { + System.out.println(STATISTIC.message); + System.out.println(DIVISION_LINE.message); + + for (Map.Entry entry : matchingResult.entrySet()) { + Rank rank = entry.getKey(); + int lottoCount = entry.getValue(); + + if (!rank.equals(Rank.NO_MATCH)) { + System.out.println(rank.message + lottoCount + COUNT.message); + } + } + } + + + public void printEarningRate(long totalPrize, PurchasePrice purchasePrice) { + System.out.println(calculateEarningRate(totalPrize, purchasePrice)); + } + + public void printErrorMessage(final String message) { + System.out.print(message + EMPTY_LINE.message); + } +} diff --git a/src/test/java/lotto/model/BonusNumberTest.java b/src/test/java/lotto/model/BonusNumberTest.java new file mode 100644 index 00000000000..1e9a82430f7 --- /dev/null +++ b/src/test/java/lotto/model/BonusNumberTest.java @@ -0,0 +1,55 @@ +package lotto.model; + +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_CAN_NOT_BE_DUPLICATE; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class BonusNumberTest { + + private List lotto; + + @BeforeEach + void setLotto() { + lotto = List.of(1, 2, 3, 4, 5, 6); + } + + @DisplayName("보너스 번호와 로또 번호가 중복되면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5, 6}) + void isDuplicate_Test(int bonusNumber) { + //when + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> BonusNumber.createBonusNumber(lotto, bonusNumber)); + + //then + assertTrue(exception.getMessage().contains(BONUS_NUMBER_CAN_NOT_BE_DUPLICATE.getMessage())); + } + + @DisplayName("보너스 번호가 1 ~ 45 사이가 아니면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(ints = {-1, 0, 46, 100}) + void isBetweenOneAndFortyFive_Test(int bonusNumber) { + //when + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> BonusNumber.createBonusNumber(lotto, bonusNumber)); + + //then + assertTrue(exception.getMessage().contains(BONUS_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE.getMessage())); + } + + @DisplayName("보너스 번호 정상 생성 테스트") + @ParameterizedTest + @ValueSource(ints = {7, 8, 9, 10, 15, 40}) + void general_Create_BonusNumber(int bonusNumber) { + //then + assertDoesNotThrow(() -> BonusNumber.createBonusNumber(lotto, bonusNumber)); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/model/LottoTest.java similarity index 53% rename from src/test/java/lotto/LottoTest.java rename to src/test/java/lotto/model/LottoTest.java index 9f5dfe7eb83..ff214b8f7f5 100644 --- a/src/test/java/lotto/LottoTest.java +++ b/src/test/java/lotto/model/LottoTest.java @@ -1,13 +1,22 @@ -package lotto; +package lotto.model; +import static lotto.constant.Constant.LOTTO_SIZE; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.List; +class LottoTest { + private Lotto lotto; -import static org.assertj.core.api.Assertions.assertThatThrownBy; + @BeforeEach + void createLotto() { + lotto = Lotto.createLotto(); + } -class LottoTest { @DisplayName("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.") @Test void createLottoByOverSize() { @@ -23,5 +32,19 @@ void createLottoByDuplicatedNumber() { .isInstanceOf(IllegalArgumentException.class); } - // 아래에 추가 테스트 작성 가능 + @DisplayName("로또는 6개이다.") + @Test + void lottoSizeMustBeSix_Test() { + //then + assertEquals(LOTTO_SIZE, lotto.getNumbers().size()); + } + + @DisplayName("로또는 서로 중복된 숫자가 없다.") + @Test + void no_DuplicateNumber_In_Lotto_Test() { + //then + long count = lotto.getNumbers().stream().distinct().count(); + + assertEquals(LOTTO_SIZE, count); + } } \ No newline at end of file diff --git a/src/test/java/lotto/model/PurchasePriceTest.java b/src/test/java/lotto/model/PurchasePriceTest.java new file mode 100644 index 00000000000..3afe740c2e2 --- /dev/null +++ b/src/test/java/lotto/model/PurchasePriceTest.java @@ -0,0 +1,33 @@ +package lotto.model; + +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_CAN_DIVIDE_BY_THOUSAND; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PurchasePriceTest { + + @DisplayName("구입 금액이 1,000원 단위가 아니면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(ints = {999, 10001, 19999, 20001}) + void not_Divided_By_Thousand(int purchasePrice) { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> PurchasePrice.createPurchasePrice(purchasePrice)); + + //then + assertTrue(exception.getMessage().contains(PURCHASE_PRICE_CAN_DIVIDE_BY_THOUSAND.getMessage())); + } + + @DisplayName("구입 금액이 1,000원 단위라면 정상 작동한다.") + @ParameterizedTest + @ValueSource(ints = {1000, 10000, 20000}) + void Divided_By_Thousand(int purchasePrice) { + //then + assertDoesNotThrow(() -> PurchasePrice.createPurchasePrice(purchasePrice)); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/model/WinnerNumberTest.java b/src/test/java/lotto/model/WinnerNumberTest.java new file mode 100644 index 00000000000..22515c4b498 --- /dev/null +++ b/src/test/java/lotto/model/WinnerNumberTest.java @@ -0,0 +1,54 @@ +package lotto.model; + +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_CAN_NOT_BE_DUPLICATE; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_SIZE_IS_SIX; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class WinnerNumberTest { + @DisplayName("당첨 번호가 중복되면 예외가 발생한다.") + @Test + void isDuplicate_Test() { + //when + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> WinnerNumber.createWinnerNumber(List.of(1, 2, 3, 4, 5, 5))); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_CAN_NOT_BE_DUPLICATE.getMessage())); + } + + @DisplayName("당첨 번호가 1 ~ 45 사이가 아니면 예외가 발생한다.") + @Test + void isBetweenOneAndFortyFive_Test() { + //when + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> WinnerNumber.createWinnerNumber(List.of(-1, 0, 46, 100, 55, 60))); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_IS_BETWEEN_ONE_AND_FORTY_FIVE.getMessage())); + } + + @DisplayName("당첨 번호가 6개가 아니면 예외가 발생한다.") + @Test + void winnerNumber_SizeMustBeSix_Test() { + //when + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> WinnerNumber.createWinnerNumber(List.of(1, 2, 3, 4, 5))); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_SIZE_IS_SIX.getMessage())); + } + + @DisplayName("당첨 번호 정상 생성 테스트") + @Test + void general_Create_WinnerNumber() { + //then + assertDoesNotThrow(() -> WinnerNumber.createWinnerNumber(List.of(1, 2, 3, 4, 5, 6))); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/service/BuyLottoServiceTest.java b/src/test/java/lotto/service/BuyLottoServiceTest.java new file mode 100644 index 00000000000..44c2d2587d7 --- /dev/null +++ b/src/test/java/lotto/service/BuyLottoServiceTest.java @@ -0,0 +1,31 @@ +package lotto.service; + +import lotto.model.LottoNumbers; +import lotto.model.PurchasePrice; +import lotto.view.output.OutputView; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class BuyLottoServiceTest { + private final BuyLottoService buyLottoService; + + BuyLottoServiceTest() { + buyLottoService = new BuyLottoService(new OutputView()); + } + + @DisplayName("로또 구입 비즈니스 로직 테스트") + @ParameterizedTest + @ValueSource(ints = {7000, 8000, 20000}) + void buyLotto_Test(int input) { + //given + PurchasePrice purchasePrice = PurchasePrice.createPurchasePrice(input); + //when + LottoNumbers lottoNumbers = buyLottoService.buyLotto(purchasePrice); + + //then + Assertions.assertEquals(input / 1000, lottoNumbers.getNumbers().size()); + } + +} \ No newline at end of file diff --git a/src/test/java/lotto/service/CalculateStatisticServiceTest.java b/src/test/java/lotto/service/CalculateStatisticServiceTest.java new file mode 100644 index 00000000000..fa857ea7255 --- /dev/null +++ b/src/test/java/lotto/service/CalculateStatisticServiceTest.java @@ -0,0 +1,57 @@ +package lotto.service; + +import java.util.ArrayList; +import java.util.List; +import lotto.dto.BuyLottoDto; +import lotto.model.BonusNumber; +import lotto.model.Lotto; +import lotto.model.LottoNumbers; +import lotto.model.PurchasePrice; +import lotto.model.Statistics; +import lotto.model.WinnerNumber; +import lotto.view.output.OutputView; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CalculateStatisticServiceTest { + private final CalculateStatisticService calculateStatisticService; + + CalculateStatisticServiceTest() { + calculateStatisticService = new CalculateStatisticService(new OutputView()); + } + + @DisplayName("총 수익금 계산") + @Test + void calculate_Total_Money_Test() { + //given + PurchasePrice purchasePrice = PurchasePrice.createPurchasePrice(8000); + List lottos = createLotto(); + + LottoNumbers lottoNumbers = LottoNumbers.createLottoNumbers(lottos); + WinnerNumber winnerNumber = WinnerNumber.createWinnerNumber(List.of(1, 2, 3, 4, 5, 6)); + BonusNumber bonusNumber = BonusNumber.createBonusNumber(winnerNumber.getWinnerNumbers(), 7); + BuyLottoDto buyLottoDto = BuyLottoDto.createBuyLottoDto(purchasePrice, lottoNumbers, winnerNumber, bonusNumber); + Statistics statistics = buyLottoDto.calculateMatching(); + + //when + long totalMoney = calculateStatisticService.calculatePrizeMoney(statistics.getResult()); + + //then + Assertions.assertEquals(5000, totalMoney); + } + + private static List createLotto() { + List lottos = new ArrayList<>(); + lottos.add(new Lotto(List.of(1, 2, 3, 7, 8, 9))); + lottos.add(new Lotto(List.of(11, 12, 13, 17, 18, 19))); + lottos.add(new Lotto(List.of(21, 22, 23, 27, 28, 29))); + lottos.add(new Lotto(List.of(31, 32, 33, 37, 38, 39))); + lottos.add(new Lotto(List.of(41, 42, 43, 45, 34, 40))); + lottos.add(new Lotto(List.of(12, 22, 33, 37, 28, 19))); + lottos.add(new Lotto(List.of(13, 22, 39, 17, 28, 12))); + lottos.add(new Lotto(List.of(14, 32, 31, 17, 18, 19))); + return lottos; + } + +} \ No newline at end of file diff --git a/src/test/java/lotto/validation/BonusNumberValidatorTest.java b/src/test/java/lotto/validation/BonusNumberValidatorTest.java new file mode 100644 index 00000000000..cdd102e2b07 --- /dev/null +++ b/src/test/java/lotto/validation/BonusNumberValidatorTest.java @@ -0,0 +1,54 @@ +package lotto.validation; + +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_CANNOT_BE_NULL_OR_EMPTY; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_IS_MORE_THAN_ONE; +import static lotto.exception.ErrorInputException.ErrorMessage.BONUS_NUMBER_IS_NOT_INTEGER; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +class BonusNumberValidatorTest { + private final BonusNumberValidator bonusNumberValidator; + + BonusNumberValidatorTest() { + bonusNumberValidator = new BonusNumberValidator(); + } + + @DisplayName("보너스 번호는 빈 값이거나 null이면 예외가 발생한다.") + @Test + void isNullOrEmptyTest() { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> bonusNumberValidator.isBlank(null)); + + //then + assertTrue(exception.getMessage().contains(BONUS_NUMBER_CANNOT_BE_NULL_OR_EMPTY.getMessage())); + } + + @DisplayName("보너스 번호로 정수가 아닌 값을 입력하면 예외가 발생한다.") + @Test + void isIntegerTest() { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> bonusNumberValidator.isInteger("Not Integer")); + + //then + assertTrue(exception.getMessage().contains(BONUS_NUMBER_IS_NOT_INTEGER.getMessage())); + } + + @DisplayName("보너스 번호는 1보다 큰 자연수가 아니면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(ints = {-1, 0, -100}) + void bonusNumber_Greater_Than_One(int input) { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> bonusNumberValidator.moreThanOne(input)); + + //then + assertTrue(exception.getMessage().contains(BONUS_NUMBER_IS_MORE_THAN_ONE.getMessage())); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/validation/PurchasePriceValidatorTest.java b/src/test/java/lotto/validation/PurchasePriceValidatorTest.java new file mode 100644 index 00000000000..e0b16554883 --- /dev/null +++ b/src/test/java/lotto/validation/PurchasePriceValidatorTest.java @@ -0,0 +1,55 @@ +package lotto.validation; + +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_CANNOT_BE_NULL_OR_EMPTY; +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_IS_MORE_THAN_ONE; +import static lotto.exception.ErrorInputException.ErrorMessage.PURCHASE_PRICE_IS_NOT_INTEGER; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +class PurchasePriceValidatorTest { + private final PurchasePriceValidator purchasePriceValidator; + + PurchasePriceValidatorTest() { + purchasePriceValidator = new PurchasePriceValidator(); + } + + @DisplayName("구입 금액은 빈 값이거나 null이면 예외가 발생한다.") + @Test + void isNullOrEmptyTest() { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> purchasePriceValidator.isBlank(null)); + + //then + assertTrue(exception.getMessage().contains(PURCHASE_PRICE_CANNOT_BE_NULL_OR_EMPTY.getMessage())); + } + + @DisplayName("구입 금액으로 정수가 아닌 값을 입력하면 예외가 발생한다.") + @Test + void isIntegerTest() { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> purchasePriceValidator.isInteger("Not Integer")); + + //then + assertTrue(exception.getMessage().contains(PURCHASE_PRICE_IS_NOT_INTEGER.getMessage())); + } + + @DisplayName("구입 금액은 1보다 큰 자연수가 아니면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(ints = {-1, 0, -100}) + void purchasePrice_Greater_Than_One(int input) { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> purchasePriceValidator.moreThanOne(input)); + + //then + assertTrue(exception.getMessage().contains(PURCHASE_PRICE_IS_MORE_THAN_ONE.getMessage())); + } + +} \ No newline at end of file diff --git a/src/test/java/lotto/validation/WinnerNumberValidatorTest.java b/src/test/java/lotto/validation/WinnerNumberValidatorTest.java new file mode 100644 index 00000000000..b5e5ac1b606 --- /dev/null +++ b/src/test/java/lotto/validation/WinnerNumberValidatorTest.java @@ -0,0 +1,70 @@ +package lotto.validation; + +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_CANNOT_BE_NULL_OR_EMPTY; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_ERROR_INPUT; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_IS_MORE_THAN_ONE; +import static lotto.exception.ErrorInputException.ErrorMessage.WINNER_NUMBER_IS_NOT_INTEGER; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +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; + +class WinnerNumberValidatorTest { + private final WinnerNumberValidator winnerNumberValidator; + + WinnerNumberValidatorTest() { + winnerNumberValidator = new WinnerNumberValidator(); + } + + @DisplayName("당첨 번호는 빈 값이거나 null이면 예외가 발생한다.") + @Test + void isNullOrEmptyTest() { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> winnerNumberValidator.isBlank(null)); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_CANNOT_BE_NULL_OR_EMPTY.getMessage())); + } + + @DisplayName("당첨 번호로 정수가 아닌 값을 입력하면 예외가 발생한다.") + @Test + void isIntegerTest() { + //given + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> winnerNumberValidator.isInteger("Not Integer")); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_IS_NOT_INTEGER.getMessage())); + } + + @DisplayName("당첨 번호는 1보다 큰 자연수가 아니면 예외가 발생한다.") + @Test + void bonusNumber_Greater_Than_One() { + //given + List input = List.of(-1, 0, 1, 2, 3, 4); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> winnerNumberValidator.moreThanOne(input)); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_IS_MORE_THAN_ONE.getMessage())); + } + + @DisplayName("당첨 번호 입력시 ,,를 입력하거나 가장 앞과 뒤는 숫자로 입력하지 않으면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"1,2,3,4,,5,6", "1,2,3,4,5,6,", ",1,2,3,4,5,6"}) + void double_Comma_Test(String input) { + //given + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> winnerNumberValidator.checkCommaError(input)); + + //then + assertTrue(exception.getMessage().contains(WINNER_NUMBER_ERROR_INPUT.getMessage())); + } +} \ No newline at end of file