diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..58939acae8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,20 @@ +# 기능 요구사항 +### 로또 구매 +* 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발급한다. +* 로또 1장의 가격은 1000원이다. + +### 지난 주 당첨 번호 비교 +* 지난 주 당첨 번호와 보너스 볼 번호를 입력받는다. +* 발급된 로또와 비교하여 당첨 통계를 생성한다. + * 3개 일치 (5000원) + * 4개 일치 (50000원) + * 5개 일치 (1500000원) + * 5개 일치, 보너스 볼 일치(30000000원) + * 6개 일치 (2000000000원) +* 총 수익률을 계산해서 출력해준다. + * 수익률은 구매한 금액 / 총 당첨 금액 계산값을 소수점 두 자리 수까지 나타낸 수이다. + +### 수동 로또 구매 +* 로또 구입 금액 입력 후 수동 구매 수량을 입력받는다. +* 수동 구매 수량을 입력받은 후 수동으로 구매할 로또 번호를 구매 수량만큼 입력받는다. +* 자동 구매권 수량은 총 구매한 개수 - 수동 구매 수량이다. \ No newline at end of file diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 0000000000..3985ba6971 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,37 @@ +import controller.AmountRequest; +import controller.BonusNumberRequest; +import controller.ManualNumberRequest; +import controller.WinningNumberRequest; +import domain.PhraseLottoExecutor; +import domain.RateCalculateExecutor; +import domain.WinningConfirmExecutor; +import view.InputView; +import vo.LottoNumberCollectionList; +import vo.WinningTypeCollection; + +public class Application { + + public static void main(String[] args) { + final AmountRequest amountRequest = InputView.inputAmount(); + final ManualNumberRequest manualNumberRequest = InputView.inputManual(amountRequest.getLottoAmount()); + final WinningTypeCollection winningTypeCollection = retrieveWinning( + receivePhraseLotto(amountRequest, manualNumberRequest) + ); + final RateCalculateExecutor rateCalculateExecutor = new RateCalculateExecutor(amountRequest, winningTypeCollection); + rateCalculateExecutor.calculateRate(); + } + + private static LottoNumberCollectionList receivePhraseLotto(final AmountRequest amountRequest, final ManualNumberRequest manualNumberRequest) { + PhraseLottoExecutor phraseLottoExecutor = new PhraseLottoExecutor(amountRequest, manualNumberRequest); + phraseLottoExecutor.phraseLotto(); + return phraseLottoExecutor.pickLottoNumber(); + } + + private static WinningTypeCollection retrieveWinning(final LottoNumberCollectionList lottoNumberCollectionList) { + WinningNumberRequest winningNumberRequest = InputView.inputWinningNumber(); + BonusNumberRequest bonusNumberRequest = InputView.inputBonusNumber(); + WinningConfirmExecutor winningConfirmExecutor = new WinningConfirmExecutor(lottoNumberCollectionList, winningNumberRequest, bonusNumberRequest); + return winningConfirmExecutor.confirmWinningType(); + } + +} \ No newline at end of file diff --git a/src/main/java/controller/AmountRequest.java b/src/main/java/controller/AmountRequest.java new file mode 100644 index 0000000000..883d47451a --- /dev/null +++ b/src/main/java/controller/AmountRequest.java @@ -0,0 +1,27 @@ +package controller; + +import validator.AmountValidator; + +public class AmountRequest { + private final int lottoAmount; + + private static final int LOTTO_PRICE = 1000; + + private AmountRequest(final int lottoAmount) { + this.lottoAmount = lottoAmount; + } + + public static AmountRequest from(final String lottoAmountInput) { + final int lottoAmount = Integer.parseInt(lottoAmountInput); + AmountValidator.validateAmountNegative(lottoAmount); + return new AmountRequest(lottoAmount); + } + + public int getLottoAmount() { + return this.lottoAmount; + } + + public int fetchPhraseLottoCount() { + return (int) this.lottoAmount / LOTTO_PRICE; + } +} diff --git a/src/main/java/controller/BonusNumberRequest.java b/src/main/java/controller/BonusNumberRequest.java new file mode 100644 index 0000000000..7335bbaef4 --- /dev/null +++ b/src/main/java/controller/BonusNumberRequest.java @@ -0,0 +1,21 @@ +package controller; + +public class BonusNumberRequest { + private final int bonusNumber; + + private BonusNumberRequest(final int bonusNumber) { + this.bonusNumber = bonusNumber; + } + + public static BonusNumberRequest from(final String bonusNumberInput) { + return new BonusNumberRequest(generateBonusNumber(bonusNumberInput)); + } + + private static int generateBonusNumber(final String bonusNumberInput) { + return Integer.parseInt(bonusNumberInput); + } + + public int getBonusNumber() { + return this.bonusNumber; + } +} diff --git a/src/main/java/controller/ManualNumberRequest.java b/src/main/java/controller/ManualNumberRequest.java new file mode 100644 index 0000000000..de0fe6b539 --- /dev/null +++ b/src/main/java/controller/ManualNumberRequest.java @@ -0,0 +1,41 @@ +package controller; + +import vo.LottoNumberCollection; +import vo.LottoNumberCollectionList; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ManualNumberRequest { + private final int manualCount; + private final LottoNumberCollectionList lottoNumberCollectionList; + + private static final String LOTTO_NUMBER_SPLIT_REGEX = ","; + + private ManualNumberRequest(final int manualCount, final LottoNumberCollectionList lottoNumberCollectionList) { + this.manualCount = manualCount; + this.lottoNumberCollectionList = lottoNumberCollectionList; + } + + public static ManualNumberRequest of(final int manualCount, final List manualNumberInputs) { + final List lottoNumberCollectionList = manualNumberInputs.stream() + .map(manualInput -> { + String[] splitInput = manualInput.replaceAll(" ", "").split(LOTTO_NUMBER_SPLIT_REGEX); + return LottoNumberCollection.from(Arrays.stream(splitInput) + .map(Integer::parseInt) + .collect(Collectors.toList())); + }) + .collect(Collectors.toList()); + + return new ManualNumberRequest(manualCount, LottoNumberCollectionList.from(lottoNumberCollectionList)); + } + + public int getManualCount() { + return manualCount; + } + + public LottoNumberCollectionList getLottoNumberCollectionList() { + return lottoNumberCollectionList; + } +} diff --git a/src/main/java/controller/WinningNumberRequest.java b/src/main/java/controller/WinningNumberRequest.java new file mode 100644 index 0000000000..9c78880809 --- /dev/null +++ b/src/main/java/controller/WinningNumberRequest.java @@ -0,0 +1,32 @@ +package controller; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class WinningNumberRequest { + private final List winningNumbers; + + private static final String WINNING_NUMBER_SPLIT_REGEX = ","; + + private WinningNumberRequest(final List winningNumbers) { + this.winningNumbers = winningNumbers; + } + + public static WinningNumberRequest from(final String winningNumberInput) { + return new WinningNumberRequest(generateWinningNumber(winningNumberInput)); + } + + private static List generateWinningNumber(final String winningNumberInput) { + String[] splitInput = winningNumberInput + .replaceAll(" ", "") + .split(WINNING_NUMBER_SPLIT_REGEX); + return Arrays.stream(splitInput) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + public List getWinningNumbers() { + return List.copyOf(this.winningNumbers); + } +} diff --git a/src/main/java/domain/PhraseLottoExecutor.java b/src/main/java/domain/PhraseLottoExecutor.java new file mode 100644 index 0000000000..8a79bcdc16 --- /dev/null +++ b/src/main/java/domain/PhraseLottoExecutor.java @@ -0,0 +1,64 @@ +package domain; + +import controller.AmountRequest; +import controller.ManualNumberRequest; +import view.OutputView; +import vo.LottoNumberCollection; +import vo.LottoNumberCollectionList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; + +public class PhraseLottoExecutor { + private final AmountRequest amountRequest; + private final ManualNumberRequest manualNumberRequest; + + private static final List LOTTO_NUMBERS = new ArrayList<>(); + private final static int LOTTO_NUMBER_COUNT = 6; + + static { + for (int i = 1; i <= 45; i++) { + LOTTO_NUMBERS.add(i); + } + } + + public PhraseLottoExecutor(final AmountRequest amountRequest, final ManualNumberRequest manualNumberRequest) { + this.amountRequest = amountRequest; + this.manualNumberRequest = manualNumberRequest; + } + + public void phraseLotto() { + OutputView.outputPhraseLotto(this.manualNumberRequest.getManualCount(), calculateRandomPickCount()); + } + + public LottoNumberCollectionList pickLottoNumber() { + List lottoNumberCollectionRequest = fetchRandomLottoNumberCollectionList(); + + LottoNumberCollectionList lottoNumberCollectionList = this.manualNumberRequest.getLottoNumberCollectionList(); + lottoNumberCollectionList.addAllLottoNumberCollection(LottoNumberCollectionList.from(lottoNumberCollectionRequest)); + OutputView.outputPickedLottoNumber(lottoNumberCollectionList); + return lottoNumberCollectionList; + } + + private List fetchRandomLottoNumberCollectionList() { + List lottoNumberCollectionRequest = new ArrayList<>(); + + for (int i = 0; i < calculateRandomPickCount(); i++) { + List pickLottoNumbers = new ArrayList<>(); + Collections.shuffle(LOTTO_NUMBERS); + IntStream.range(0, LOTTO_NUMBER_COUNT).forEach(index -> { + pickLottoNumbers.add(LOTTO_NUMBERS.get(index)); + }); + + lottoNumberCollectionRequest.add(LottoNumberCollection.from(pickLottoNumbers)); + } + return lottoNumberCollectionRequest; + } + + private int calculateRandomPickCount() { + return this.amountRequest.fetchPhraseLottoCount() - this.manualNumberRequest.getManualCount(); + } + +} diff --git a/src/main/java/domain/RateCalculateExecutor.java b/src/main/java/domain/RateCalculateExecutor.java new file mode 100644 index 0000000000..4f68312ffd --- /dev/null +++ b/src/main/java/domain/RateCalculateExecutor.java @@ -0,0 +1,22 @@ +package domain; + +import controller.AmountRequest; +import view.OutputView; +import vo.WinningTypeCollection; + +public class RateCalculateExecutor { + private final AmountRequest amountRequest; + private final WinningTypeCollection winningTypeCollection; + + public RateCalculateExecutor(final AmountRequest amountRequest, final WinningTypeCollection winningTypeCollection) { + this.amountRequest = amountRequest; + this.winningTypeCollection = winningTypeCollection; + } + + public void calculateRate() { + // 수익률은 소수점 둘째 자리까지 출력 + final double rate = Math.round(winningTypeCollection.calculateTotalWinningPrice() / amountRequest.getLottoAmount()); + OutputView.outputRateOfReturn(rate); + + } +} diff --git a/src/main/java/domain/WinningConfirmExecutor.java b/src/main/java/domain/WinningConfirmExecutor.java new file mode 100644 index 0000000000..4976820b65 --- /dev/null +++ b/src/main/java/domain/WinningConfirmExecutor.java @@ -0,0 +1,47 @@ +package domain; + +import controller.BonusNumberRequest; +import controller.WinningNumberRequest; +import strategy.WinningPriceStrategy; +import strategy.WinningPriceStrategyFactory; +import view.OutputView; +import vo.LottoNumberCollection; +import vo.LottoNumberCollectionList; +import vo.WinningTypeCollection; +import vo.param.WinningMatchParam; + +import java.util.List; +import java.util.Optional; + +public class WinningConfirmExecutor { + private final LottoNumberCollectionList lottoNumberCollectionList; + private final WinningNumberRequest winningNumberRequest; + private final BonusNumberRequest bonusNumberRequest; + + public WinningConfirmExecutor(final LottoNumberCollectionList lottoNumberCollectionList, + final WinningNumberRequest winningNumberRequest, + final BonusNumberRequest bonusNumberRequest) { + this.lottoNumberCollectionList = lottoNumberCollectionList; + this.winningNumberRequest = winningNumberRequest; + this.bonusNumberRequest = bonusNumberRequest; + } + + public WinningTypeCollection confirmWinningType() { + final WinningTypeCollection winningTypeCollection = new WinningTypeCollection(); + final List pickedLottoNumberCollectionList = this.lottoNumberCollectionList.getLottoNumberCollectionList(); + + for (LottoNumberCollection pickedLottoNumberCollection : pickedLottoNumberCollectionList) { + int winningMatchCount = pickedLottoNumberCollection.countWinningMatch(this.winningNumberRequest.getWinningNumbers()); + boolean isMatchBonus = pickedLottoNumberCollection.isMatchBonus(this.bonusNumberRequest.getBonusNumber()); + + Optional optionalStrategy = Optional.ofNullable( + WinningPriceStrategyFactory.getInstance().create(WinningMatchParam.from(winningMatchCount, isMatchBonus)) + ); + optionalStrategy.ifPresent(winningPriceStrategy -> winningTypeCollection.addWinningType(winningPriceStrategy.fetchWinningType())); + } + + OutputView.outputLottoWinningPrice(winningTypeCollection); + return winningTypeCollection; + } + +} diff --git a/src/main/java/strategy/FiveBonusMatchWinningStrategy.java b/src/main/java/strategy/FiveBonusMatchWinningStrategy.java new file mode 100644 index 0000000000..2242a2604a --- /dev/null +++ b/src/main/java/strategy/FiveBonusMatchWinningStrategy.java @@ -0,0 +1,10 @@ +package strategy; + +import vo.enums.WinningType; + +public class FiveBonusMatchWinningStrategy implements WinningPriceStrategy { + @Override + public WinningType fetchWinningType() { + return WinningType.FIVE_BONUS_MATCH; + } +} diff --git a/src/main/java/strategy/FiveMatchWinningStrategy.java b/src/main/java/strategy/FiveMatchWinningStrategy.java new file mode 100644 index 0000000000..c9f54b7ebf --- /dev/null +++ b/src/main/java/strategy/FiveMatchWinningStrategy.java @@ -0,0 +1,10 @@ +package strategy; + +import vo.enums.WinningType; + +public class FiveMatchWinningStrategy implements WinningPriceStrategy { + @Override + public WinningType fetchWinningType() { + return WinningType.FIVE_MATCH; + } +} diff --git a/src/main/java/strategy/FourMatchWinningStrategy.java b/src/main/java/strategy/FourMatchWinningStrategy.java new file mode 100644 index 0000000000..3505a0274a --- /dev/null +++ b/src/main/java/strategy/FourMatchWinningStrategy.java @@ -0,0 +1,10 @@ +package strategy; + +import vo.enums.WinningType; + +public class FourMatchWinningStrategy implements WinningPriceStrategy { + @Override + public WinningType fetchWinningType() { + return WinningType.FOUR_MATCH; + } +} diff --git a/src/main/java/strategy/SixMatchWinningStrategy.java b/src/main/java/strategy/SixMatchWinningStrategy.java new file mode 100644 index 0000000000..d4f9634b75 --- /dev/null +++ b/src/main/java/strategy/SixMatchWinningStrategy.java @@ -0,0 +1,10 @@ +package strategy; + +import vo.enums.WinningType; + +public class SixMatchWinningStrategy implements WinningPriceStrategy { + @Override + public WinningType fetchWinningType() { + return WinningType.SIX_MATCH; + } +} diff --git a/src/main/java/strategy/ThreeMatchWinningStrategy.java b/src/main/java/strategy/ThreeMatchWinningStrategy.java new file mode 100644 index 0000000000..962996d0c8 --- /dev/null +++ b/src/main/java/strategy/ThreeMatchWinningStrategy.java @@ -0,0 +1,10 @@ +package strategy; + +import vo.enums.WinningType; + +public class ThreeMatchWinningStrategy implements WinningPriceStrategy { + @Override + public WinningType fetchWinningType() { + return WinningType.THREE_MATCH; + } +} diff --git a/src/main/java/strategy/WinningPriceStrategy.java b/src/main/java/strategy/WinningPriceStrategy.java new file mode 100644 index 0000000000..fbf475af7c --- /dev/null +++ b/src/main/java/strategy/WinningPriceStrategy.java @@ -0,0 +1,7 @@ +package strategy; + +import vo.enums.WinningType; + +public interface WinningPriceStrategy { + WinningType fetchWinningType(); +} diff --git a/src/main/java/strategy/WinningPriceStrategyFactory.java b/src/main/java/strategy/WinningPriceStrategyFactory.java new file mode 100644 index 0000000000..89b53005de --- /dev/null +++ b/src/main/java/strategy/WinningPriceStrategyFactory.java @@ -0,0 +1,22 @@ +package strategy; + +import vo.param.WinningMatchParam; + +public class WinningPriceStrategyFactory { + private static final WinningPriceStrategyFactory INSTANCE = new WinningPriceStrategyFactory(); + + private WinningPriceStrategyFactory() {} + + public static WinningPriceStrategyFactory getInstance() { + return INSTANCE; + } + + public WinningPriceStrategy create(final WinningMatchParam winningMatchParam) { + if (winningMatchParam.isThreeMatch()) return new ThreeMatchWinningStrategy(); + if (winningMatchParam.isFourMatch()) return new FourMatchWinningStrategy(); + if (winningMatchParam.isFiveMatch()) return new FiveMatchWinningStrategy(); + if (winningMatchParam.isFiveBonusMatch()) return new FiveBonusMatchWinningStrategy(); + if (winningMatchParam.isSixMatch()) return new SixMatchWinningStrategy(); + return null; + } +} diff --git a/src/main/java/utils/Console.java b/src/main/java/utils/Console.java new file mode 100644 index 0000000000..10db2f888f --- /dev/null +++ b/src/main/java/utils/Console.java @@ -0,0 +1,27 @@ +package utils; + +import java.util.Scanner; + +public class Console { + private static Scanner scanner; + + private Console() {} + + public static String readLine() { + return getInstance().nextLine(); + } + + public static void close() { + if (scanner != null) { + scanner.close(); + scanner = null; + } + } + + private static Scanner getInstance() { + if (scanner == null) { + scanner = new Scanner(System.in); + } + return scanner; + } +} \ No newline at end of file diff --git a/src/main/java/validator/AmountValidator.java b/src/main/java/validator/AmountValidator.java new file mode 100644 index 0000000000..b76579d0d6 --- /dev/null +++ b/src/main/java/validator/AmountValidator.java @@ -0,0 +1,12 @@ +package validator; + +public class AmountValidator { + + private static final String NEGATIVE_EXCEPTION_MESSAGE = "[ERROR] 금액은 음수로 입력할 수 없습니다."; + + public static void validateAmountNegative(final int lottoAmount) { + if (lottoAmount < 0) { + throw new IllegalArgumentException(NEGATIVE_EXCEPTION_MESSAGE); + } + } +} diff --git a/src/main/java/validator/InputValidator.java b/src/main/java/validator/InputValidator.java new file mode 100644 index 0000000000..5de886af8f --- /dev/null +++ b/src/main/java/validator/InputValidator.java @@ -0,0 +1,123 @@ +package validator; + +import java.util.Arrays; +import java.util.List; + +public class InputValidator { + private static final String AMOUNT_NOT_NUMBER_EXCEPTION_MESSAGE = "[ERROR] 금액의 입력은 0 이상의 정수여야 합니다."; + private static final String MANUAL_COUNT_NOT_NUMBER_EXCEPTION_MESSAGE = "[ERROR] 수동으로 구매할 로또 수의 입력은 0 이상의 정수여야 합니다."; + private static final String MANUAL_COUNT_NOT_BIGGER_EXCEPTION_MESSAGE = "[ERROR] 수동으로 구매할 로또 수는 구매한 로또 수보다 클 수 없습니다."; + private static final String MANUAL_NUMBER_COUNT_EXCEPTION_MESSAGE = "[ERROR] 수동으로 구매할 번호는 6개여야 합니다."; + private static final String MANUAL_NUMBER_NOT_NUMBER_MESSAGE = "[ERROR] 수동으로 구매할 번호는 1부터 45까지의 숫자로 이루어져야 합니다."; + private static final String WINNING_NUMBER_COUNT_EXCEPTION_MESSAGE = "[ERROR] 당첨 번호는 6개여야 합니다."; + private static final String WINNING_NUMBER_NOT_NUMBER_MESSAGE = "[ERROR] 당첨 번호는 1부터 45까지의 숫자로 이루어져야 합니다."; + private static final String BONUS_NUMBER_NOT_NUMBER_MESSAGE = "[ERROR] 보너스 볼 번호는 1부터 45까지의 숫자로 이루어져야 합니다."; + private static final String NUMBER_INPUT_SPLIT_REGEX = ","; + private static final int LOTTO_NUMBER_COUNT = 6; + + public static void validateAmountNotNumber(final String amountInput) { + try { + Integer.parseInt(amountInput); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(AMOUNT_NOT_NUMBER_EXCEPTION_MESSAGE); + } + } + + public static void validateManualCountNotNumber(final String manualCountInput, final int lottoAmount) { + try { + int manualCount = Integer.parseInt(manualCountInput); + validateManualCountLeastThanLottoAmount(lottoAmount, manualCount); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(MANUAL_COUNT_NOT_NUMBER_EXCEPTION_MESSAGE); + } + } + + private static void validateManualCountLeastThanLottoAmount(int lottoAmount, int manualCount) { + if (manualCount > lottoAmount) { + throw new IllegalArgumentException(MANUAL_COUNT_NOT_BIGGER_EXCEPTION_MESSAGE); + } + } + + public static void validateManualNumberInput(final List manualNumberInputs) { + manualNumberInputs.forEach(manualNumberInput -> { + String[] splitInput = manualNumberInput + .replaceAll(" ", "") + .split(NUMBER_INPUT_SPLIT_REGEX); + + if (splitInput.length != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException(MANUAL_NUMBER_COUNT_EXCEPTION_MESSAGE); + } + + validateManualNumber(splitInput); + }); + } + + private static void validateManualNumber(String[] splitInput) { + try { + Arrays.stream(splitInput) + .map(Integer::parseInt) + .forEach(InputValidator::validateManualNumberRange); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(MANUAL_NUMBER_NOT_NUMBER_MESSAGE); + } + } + + private static void validateManualNumberRange(final int manualNumber) { + if (!isLottoNumberRange(manualNumber)) { + throw new IllegalArgumentException(MANUAL_NUMBER_NOT_NUMBER_MESSAGE); + } + } + + public static void validateWinningNumberInput(final String winningNumberInput) { + String[] splitInput = winningNumberInput + .replaceAll(" ", "") + .split(NUMBER_INPUT_SPLIT_REGEX); + + validateWinningNumberCount(splitInput); + + for (String input : splitInput) { + validateWinningNumber(input); + } + } + + private static void validateWinningNumberCount(final String[] splitInput) { + if (splitInput.length != LOTTO_NUMBER_COUNT) { + throw new IllegalArgumentException(WINNING_NUMBER_COUNT_EXCEPTION_MESSAGE); + } + } + + private static void validateWinningNumber(final String input) { + try { + int winningNumber = Integer.parseInt(input); + validateWinningNumberRange(winningNumber); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(WINNING_NUMBER_NOT_NUMBER_MESSAGE); + } + } + + private static void validateWinningNumberRange(final int winningNumber) { + if (!isLottoNumberRange(winningNumber)) { + throw new IllegalArgumentException(WINNING_NUMBER_NOT_NUMBER_MESSAGE); + } + } + + private static boolean isLottoNumberRange(final int number) { + return number > 0 && number < 46; + } + + public static void validateBonusNumberInput(final String bonusNumberInput) { + try { + int bonusNumber = Integer.parseInt(bonusNumberInput); + + validateBonusNumberRange(bonusNumber); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(BONUS_NUMBER_NOT_NUMBER_MESSAGE); + } + } + + private static void validateBonusNumberRange(final int bonusNumber) { + if (!isLottoNumberRange(bonusNumber)) { + throw new IllegalArgumentException(BONUS_NUMBER_NOT_NUMBER_MESSAGE); + } + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 0000000000..2a1b50d98b --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,79 @@ +package view; + +import controller.AmountRequest; +import controller.BonusNumberRequest; +import controller.ManualNumberRequest; +import controller.WinningNumberRequest; +import utils.Console; +import validator.InputValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +public class InputView { + private static final String INPUT_AMOUNT_MESSAGE = "구입금액을 입력해 주세요."; + private static final String INPUT_MANUAL_COUNT_MESSAGE = "수동으로 구매할 로또 수를 입력해 주세요."; + private static final String INPUT_MANUAL_NUMBER_MESSAGE = "수동으로 구매할 번호를 입력해 주세요."; + private static final String INPUT_WINNING_NUMBER_MESSAGE = "지난 주 당첨 번호를 입력해 주세요."; + private static final String INPUT_BONUS_NUMBER_MESSAGE = "보너스 볼을 입력해 주세요."; + + public static AmountRequest inputAmount() { + System.out.println(INPUT_AMOUNT_MESSAGE); + try { + final String lottoAmountInput = Console.readLine(); + InputValidator.validateAmountNotNumber(lottoAmountInput); + return AmountRequest.from(lottoAmountInput); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return inputAmount(); + } + } + + public static ManualNumberRequest inputManual(final int lottoAmount) { + System.out.println(INPUT_MANUAL_COUNT_MESSAGE); + try { + return fetchManualNumberRequest(lottoAmount); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return inputManual(lottoAmount); + } + } + + private static ManualNumberRequest fetchManualNumberRequest(final int lottoAmount) { + List manualNumberInputs = new ArrayList<>(); + + final String manualCountInput = Console.readLine(); + InputValidator.validateManualCountNotNumber(manualCountInput, lottoAmount); + final int manualCount = Integer.parseInt(manualCountInput); + + System.out.println(INPUT_MANUAL_NUMBER_MESSAGE); + IntStream.range(0, manualCount).forEach(value -> manualNumberInputs.add(Console.readLine())); + InputValidator.validateManualNumberInput(manualNumberInputs); + return ManualNumberRequest.of(manualCount, manualNumberInputs); + } + + public static WinningNumberRequest inputWinningNumber() { + System.out.println(INPUT_WINNING_NUMBER_MESSAGE); + try { + final String winningNumberInput = Console.readLine(); + InputValidator.validateWinningNumberInput(winningNumberInput); + return WinningNumberRequest.from(winningNumberInput); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return inputWinningNumber(); + } + } + + public static BonusNumberRequest inputBonusNumber() { + System.out.println(INPUT_BONUS_NUMBER_MESSAGE); + try { + final String bonusNumberInput = Console.readLine(); + InputValidator.validateBonusNumberInput(bonusNumberInput); + return BonusNumberRequest.from(bonusNumberInput); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return inputBonusNumber(); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000000..dde4f0085e --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,48 @@ +package view; + +import vo.LottoNumberCollection; +import vo.LottoNumberCollectionList; +import vo.WinningTypeCollection; +import vo.enums.WinningType; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class OutputView { + private static final String PHRASE_LOTTO_MESSAGE = "수동으로 %d개, 자동으로 %d개를 구매했습니다."; + private static final String WINNING_STATISTICS_MESSAGE = "당첨 통계\n---------"; + private static final String TOTAL_RATE_MESSAGE = "총 수익률은 %.2f입니다."; + private static final String SQUARE_BRACKETS_FOR_LOTTO_NUMBER = "[%s]"; + private static final String REST_DELIMITER = ", "; + private static final String WINNING_FORMAT = "%s - %d개"; + + public static void outputPhraseLotto(final int manualCount, final int randomCount) { + System.out.println(String.format(PHRASE_LOTTO_MESSAGE, manualCount, randomCount)); + } + + public static void outputPickedLottoNumber(final LottoNumberCollectionList lottoNumberCollectionList) { + List lottoNumberCollections = lottoNumberCollectionList.getLottoNumberCollectionList(); + lottoNumberCollections.forEach(lottoNumberCollection -> { + String collectPickLottoNumber = lottoNumberCollection.getPickLottoNumbers().stream() + .map(Object::toString) + .collect(Collectors.joining(REST_DELIMITER)); + + System.out.println(String.format(SQUARE_BRACKETS_FOR_LOTTO_NUMBER, collectPickLottoNumber)); + }); + } + + public static void outputLottoWinningPrice(final WinningTypeCollection winningTypeCollection) { + Map winningTypeVsMatchCountMap = winningTypeCollection.fetchMapOfWinningTypeVsMatchCount(); + System.out.println(WINNING_STATISTICS_MESSAGE); + + for (WinningType winningType : winningTypeCollection.getAllWinningType()) { + System.out.println(String.format(WINNING_FORMAT, winningType.getTitle(), winningTypeVsMatchCountMap.get(winningType))); + } + } + + public static void outputRateOfReturn(final double rate) { + System.out.println(String.format(TOTAL_RATE_MESSAGE, rate)); + } + +} diff --git a/src/main/java/vo/LottoNumberCollection.java b/src/main/java/vo/LottoNumberCollection.java new file mode 100644 index 0000000000..72ad830991 --- /dev/null +++ b/src/main/java/vo/LottoNumberCollection.java @@ -0,0 +1,32 @@ +package vo; + +import java.util.ArrayList; +import java.util.List; + +public class LottoNumberCollection { + + private final List pickLottoNumbers = new ArrayList<>(); + + private LottoNumberCollection(final List pickLottoNumbers) { + this.pickLottoNumbers.addAll(pickLottoNumbers); + } + + public static LottoNumberCollection from(final List pickLottoNumbers) { + return new LottoNumberCollection(pickLottoNumbers); + } + + public List getPickLottoNumbers() { + return List.copyOf(this.pickLottoNumbers); + } + + public int countWinningMatch(final List winningNumbers) { + return (int) this.pickLottoNumbers.stream() + .filter(winningNumbers::contains) + .count(); + } + + public boolean isMatchBonus(final int bonusNumber) { + return this.pickLottoNumbers.stream() + .anyMatch(pickNumber -> pickNumber == bonusNumber); + } +} diff --git a/src/main/java/vo/LottoNumberCollectionList.java b/src/main/java/vo/LottoNumberCollectionList.java new file mode 100644 index 0000000000..a0434a4210 --- /dev/null +++ b/src/main/java/vo/LottoNumberCollectionList.java @@ -0,0 +1,28 @@ +package vo; + +import java.util.ArrayList; +import java.util.List; + +public class LottoNumberCollectionList { + private final List lottoNumberCollectionList = new ArrayList<>(); + + private LottoNumberCollectionList(final List lottoNumberCollectionList) { + this.lottoNumberCollectionList.addAll(lottoNumberCollectionList); + } + + public static LottoNumberCollectionList from(final List lottoNumberCollectionList) { + return new LottoNumberCollectionList(lottoNumberCollectionList); + } + + public void addLottoNumberCollection(final LottoNumberCollection lottoNumberCollection) { + this.lottoNumberCollectionList.add(lottoNumberCollection); + } + + public void addAllLottoNumberCollection(final LottoNumberCollectionList lottoNumberCollectionList) { + this.lottoNumberCollectionList.addAll(lottoNumberCollectionList.getLottoNumberCollectionList()); + } + + public List getLottoNumberCollectionList() { + return List.copyOf(this.lottoNumberCollectionList); + } +} diff --git a/src/main/java/vo/WinningTypeCollection.java b/src/main/java/vo/WinningTypeCollection.java new file mode 100644 index 0000000000..7f064a7f05 --- /dev/null +++ b/src/main/java/vo/WinningTypeCollection.java @@ -0,0 +1,44 @@ +package vo; + +import vo.enums.WinningType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class WinningTypeCollection { + private final List winningTypes = new ArrayList<>(); + + private static final List allWinningType = List.of( + WinningType.THREE_MATCH, + WinningType.FOUR_MATCH, + WinningType.FIVE_MATCH, + WinningType.FIVE_BONUS_MATCH, + WinningType.SIX_MATCH + ); + + public WinningTypeCollection() {} + + public void addWinningType(final WinningType winningType) { + this.winningTypes.add(winningType); + } + + public Map fetchMapOfWinningTypeVsMatchCount() { + return allWinningType.stream() + .collect(Collectors.toMap( + winningType -> winningType, + winningType -> winningTypes.stream().filter(winningType::equals).count() + )); + } + + public int calculateTotalWinningPrice() { + return winningTypes.stream() + .mapToInt(WinningType::getWinningPrice) + .sum(); + } + + public List getAllWinningType() { + return List.copyOf(allWinningType); + } +} diff --git a/src/main/java/vo/enums/WinningType.java b/src/main/java/vo/enums/WinningType.java new file mode 100644 index 0000000000..ef42d0c2b1 --- /dev/null +++ b/src/main/java/vo/enums/WinningType.java @@ -0,0 +1,25 @@ +package vo.enums; + +public enum WinningType { + THREE_MATCH("3개 일치 (5000원)", 5000), + FOUR_MATCH("4개 일치 (50000원)", 50000), + FIVE_MATCH("5개 일치 (1500000원)", 1500000), + FIVE_BONUS_MATCH("5개 일치, 보너스 볼 일치(30000000원)", 30000000), + SIX_MATCH("6개 일치 (2000000000원)", 2000000000); + + private String title; + private int winningPrice; + + WinningType(final String title, final int winningPrice) { + this.title = title; + this.winningPrice =winningPrice; + } + + public String getTitle() { + return this.title; + } + + public int getWinningPrice() { + return this.winningPrice; + } +} diff --git a/src/main/java/vo/param/WinningMatchParam.java b/src/main/java/vo/param/WinningMatchParam.java new file mode 100644 index 0000000000..18dd4b4b0d --- /dev/null +++ b/src/main/java/vo/param/WinningMatchParam.java @@ -0,0 +1,35 @@ +package vo.param; + +public class WinningMatchParam { + private final int winningMatchCount; + private final boolean isMatchBonus; + + private WinningMatchParam(final int winningMatchCount, final boolean isMatchBonus) { + this.winningMatchCount = winningMatchCount; + this.isMatchBonus = isMatchBonus; + } + + public static WinningMatchParam from(final int winningMatchCount, final boolean isMatchBonus) { + return new WinningMatchParam(winningMatchCount, isMatchBonus); + } + + public boolean isThreeMatch() { + return this.winningMatchCount == 3; + } + + public boolean isFourMatch() { + return this.winningMatchCount == 4; + } + + public boolean isFiveMatch() { + return this.winningMatchCount == 5 && !isMatchBonus; + } + + public boolean isFiveBonusMatch() { + return this.winningMatchCount == 5 && isMatchBonus; + } + + public boolean isSixMatch() { + return this.winningMatchCount == 6; + } +} diff --git a/src/test/java/domain/IOTest.java b/src/test/java/domain/IOTest.java new file mode 100644 index 0000000000..06f1725834 --- /dev/null +++ b/src/test/java/domain/IOTest.java @@ -0,0 +1,25 @@ +package domain; + +import org.junit.jupiter.api.BeforeEach; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public abstract class IOTest { + private ByteArrayOutputStream byteArrayOutputStream; + + protected void systemIn(final String input) { + System.setIn(new ByteArrayInputStream(input.getBytes())); + } + + @BeforeEach + void setUp() { + byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + } + + protected String fetchOutput() { + return byteArrayOutputStream.toString(); + } +} diff --git a/src/test/java/domain/PhraseLottoExecutorTest.java b/src/test/java/domain/PhraseLottoExecutorTest.java new file mode 100644 index 0000000000..aac05b65dd --- /dev/null +++ b/src/test/java/domain/PhraseLottoExecutorTest.java @@ -0,0 +1,33 @@ +package domain; + +import controller.AmountRequest; +import controller.ManualNumberRequest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class PhraseLottoExecutorTest extends IOTest { + private static final String PHRASE_LOTTO_MESSAGE = "수동으로 %d개, 자동으로 %d개를 구매했습니다."; + + @Test + void 로또_구입_메시지_출력을_확인한다() { + PhraseLottoExecutor sut = new PhraseLottoExecutor(createAmountRequest(), createManualNumberRequest()); + + sut.phraseLotto(); + + assertThat(fetchOutput()).contains(String.format(PHRASE_LOTTO_MESSAGE, 1, 4)); + } + + private AmountRequest createAmountRequest() { + String amountInput = "5000"; + return AmountRequest.from(amountInput); + } + + private ManualNumberRequest createManualNumberRequest() { + int manualCount = 1; + List manualNumberInputs = List.of("1, 2, 3, 4, 5, 6"); + return ManualNumberRequest.of(manualCount, manualNumberInputs); + } +} \ No newline at end of file diff --git a/src/test/java/domain/WinningConfirmExecutorTest.java b/src/test/java/domain/WinningConfirmExecutorTest.java new file mode 100644 index 0000000000..77f28d8aa2 --- /dev/null +++ b/src/test/java/domain/WinningConfirmExecutorTest.java @@ -0,0 +1,59 @@ +package domain; + +import controller.BonusNumberRequest; +import controller.WinningNumberRequest; +import org.junit.jupiter.api.Test; +import vo.LottoNumberCollection; +import vo.LottoNumberCollectionList; +import vo.enums.WinningType; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class WinningConfirmExecutorTest extends IOTest { + + private static final String WINNING_FORMAT = "%s - %d개"; + + @Test + void 당첨_금액_통계_출력_확인하기() { + WinningConfirmExecutor sut = new WinningConfirmExecutor(createLottoNumberCollectionList(), + createWinningNumberRequest(), + createBonusNumberRequest()); + + sut.confirmWinningType(); + + assertAll( + () -> assertThat(fetchOutput()).contains(String.format(WINNING_FORMAT, WinningType.THREE_MATCH.getTitle(), 1)), + () -> assertThat(fetchOutput()).contains(String.format(WINNING_FORMAT, WinningType.FOUR_MATCH.getTitle(), 1)), + () -> assertThat(fetchOutput()).contains(String.format(WINNING_FORMAT, WinningType.FIVE_BONUS_MATCH.getTitle(), 1)) + ); + } + + private LottoNumberCollectionList createLottoNumberCollectionList() { + return LottoNumberCollectionList.from(createLottoNumberCollection()); + } + + private List createLottoNumberCollection() { + List pickLottoNumbers1 = List.of(1, 2, 3, 8, 9, 10); + List pickLottoNumbers2 = List.of(1, 2, 3, 4, 8, 9); + List pickLottoNumbers3 = List.of(1, 2, 3, 4, 5, 7); + + List> pickLottoNumberList = List.of(pickLottoNumbers1, pickLottoNumbers2, pickLottoNumbers3); + return pickLottoNumberList.stream() + .map(LottoNumberCollection::from) + .collect(Collectors.toList()); + } + + private WinningNumberRequest createWinningNumberRequest() { + String winningNumberInput = "1, 2, 3, 4, 5, 6"; + return WinningNumberRequest.from(winningNumberInput); + } + + private BonusNumberRequest createBonusNumberRequest() { + String bonusNumberInput = "7"; + return BonusNumberRequest.from(bonusNumberInput); + } +} \ No newline at end of file diff --git a/src/test/java/validator/InputValidatorTest.java b/src/test/java/validator/InputValidatorTest.java new file mode 100644 index 0000000000..f1f131a192 --- /dev/null +++ b/src/test/java/validator/InputValidatorTest.java @@ -0,0 +1,116 @@ +package validator; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class InputValidatorTest { + private static final String AMOUNT_NOT_NUMBER_EXCEPTION_MESSAGE = "[ERROR] 금액의 입력은 0 이상의 정수여야 합니다."; + private static final String MANUAL_COUNT_NOT_NUMBER_EXCEPTION_MESSAGE = "[ERROR] 수동으로 구매할 로또 수의 입력은 0 이상의 정수여야 합니다."; + private static final String MANUAL_COUNT_NOT_BIGGER_EXCEPTION_MESSAGE = "[ERROR] 수동으로 구매할 로또 수는 구매한 로또 수보다 클 수 없습니다."; + private static final String MANUAL_NUMBER_COUNT_EXCEPTION_MESSAGE = "[ERROR] 수동으로 구매할 번호는 6개여야 합니다."; + private static final String MANUAL_NUMBER_NOT_NUMBER_MESSAGE = "[ERROR] 수동으로 구매할 번호는 1부터 45까지의 숫자로 이루어져야 합니다."; + private static final String WINNING_NUMBER_COUNT_EXCEPTION_MESSAGE = "[ERROR] 당첨 번호는 6개여야 합니다."; + private static final String WINNING_NUMBER_NOT_NUMBER_MESSAGE = "[ERROR] 당첨 번호는 1부터 45까지의 숫자로 이루어져야 합니다."; + private static final String BONUS_NUMBER_NOT_NUMBER_MESSAGE = "[ERROR] 보너스 볼 번호는 1부터 45까지의 숫자로 이루어져야 합니다."; + + @Test + void 구매한_로또_개수_입력_유효성을_검사한다() { + String alphabetInput = "a"; + String koreanInput = "가"; + + IllegalArgumentException actual1 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateAmountNotNumber(alphabetInput)); + IllegalArgumentException actual2 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateAmountNotNumber(koreanInput)); + + assertAll( + () -> assertThat(actual1.getMessage()).isEqualTo(AMOUNT_NOT_NUMBER_EXCEPTION_MESSAGE), + () -> assertThat(actual2.getMessage()).isEqualTo(AMOUNT_NOT_NUMBER_EXCEPTION_MESSAGE) + ); + } + + @Test + void 수동_구매_개수_입력_유효성을_검사한다() { + String alphabetInput = "a"; + String koreanInput = "가"; + String manualCount = "6"; + int lottoAmount = 5; + + IllegalArgumentException actual1 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualCountNotNumber(alphabetInput, lottoAmount)); + IllegalArgumentException actual2 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualCountNotNumber(koreanInput, lottoAmount)); + IllegalArgumentException actual3 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualCountNotNumber(manualCount, lottoAmount)); + + assertAll( + () -> assertThat(actual1.getMessage()).isEqualTo(MANUAL_COUNT_NOT_NUMBER_EXCEPTION_MESSAGE), + () -> assertThat(actual2.getMessage()).isEqualTo(MANUAL_COUNT_NOT_NUMBER_EXCEPTION_MESSAGE), + () -> assertThat(actual3.getMessage()).isEqualTo(MANUAL_COUNT_NOT_BIGGER_EXCEPTION_MESSAGE) + ); + } + + @Test + void 수동으로_구매한_로또_번호_유효성을_검사한다() { + List alphabetInputs = List.of("a, b, c, d, e, f"); + List koreanInputs = List.of("가, 나, 다, 라, 마, 바"); + List manyNumberInputs = List.of("1, 2, 3, 4, 5, 6, 7"); + List fewNumberInputs = List.of("1, 2, 3, 4, 5"); + List outRangeNumberInputs = List.of("41, 42, 43 ,44 ,45 ,46"); + + IllegalArgumentException actual1 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualNumberInput(alphabetInputs)); + IllegalArgumentException actual2 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualNumberInput(koreanInputs)); + IllegalArgumentException actual3 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualNumberInput(manyNumberInputs)); + IllegalArgumentException actual4 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualNumberInput(fewNumberInputs)); + IllegalArgumentException actual5 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateManualNumberInput(outRangeNumberInputs)); + + assertAll( + () -> assertThat(actual1.getMessage()).isEqualTo(MANUAL_NUMBER_NOT_NUMBER_MESSAGE), + () -> assertThat(actual2.getMessage()).isEqualTo(MANUAL_NUMBER_NOT_NUMBER_MESSAGE), + () -> assertThat(actual3.getMessage()).isEqualTo(MANUAL_NUMBER_COUNT_EXCEPTION_MESSAGE), + () -> assertThat(actual4.getMessage()).isEqualTo(MANUAL_NUMBER_COUNT_EXCEPTION_MESSAGE), + () -> assertThat(actual5.getMessage()).isEqualTo(MANUAL_NUMBER_NOT_NUMBER_MESSAGE) + ); + } + + @Test + void 저번_당첨번호_입력_유효성을_검사한다() { + String alphabetInput = "a, b, c, d, e, f"; + String koreanInput = "가, 나, 다, 라, 마, 바"; + String manyNumberInput = "1, 2, 3, 4, 5, 6, 7"; + String fewNumberInput = "1, 2, 3, 4, 5"; + String outRangeInput = "41, 42, 43, 44, 45, 46"; + + IllegalArgumentException actual1 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateWinningNumberInput(alphabetInput)); + IllegalArgumentException actual2 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateWinningNumberInput(koreanInput)); + IllegalArgumentException actual3 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateWinningNumberInput(manyNumberInput)); + IllegalArgumentException actual4 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateWinningNumberInput(fewNumberInput)); + IllegalArgumentException actual5 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateWinningNumberInput(outRangeInput)); + + assertAll( + () -> assertThat(actual1.getMessage()).isEqualTo(WINNING_NUMBER_NOT_NUMBER_MESSAGE), + () -> assertThat(actual2.getMessage()).isEqualTo(WINNING_NUMBER_NOT_NUMBER_MESSAGE), + () -> assertThat(actual3.getMessage()).isEqualTo(WINNING_NUMBER_COUNT_EXCEPTION_MESSAGE), + () -> assertThat(actual4.getMessage()).isEqualTo(WINNING_NUMBER_COUNT_EXCEPTION_MESSAGE), + () -> assertThat(actual5.getMessage()).isEqualTo(WINNING_NUMBER_NOT_NUMBER_MESSAGE) + ); + } + + @Test + void 보너스_번호_입력_유효성을_검사한다() { + String alphabetInput = "a"; + String koreanInput = "가"; + String outRangeInput = "46"; + + IllegalArgumentException actual1 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateBonusNumberInput(alphabetInput)); + IllegalArgumentException actual2 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateBonusNumberInput(koreanInput)); + IllegalArgumentException actual3 = assertThrows(IllegalArgumentException.class, () -> InputValidator.validateBonusNumberInput(outRangeInput)); + + assertAll( + () -> assertThat(actual1.getMessage()).isEqualTo(BONUS_NUMBER_NOT_NUMBER_MESSAGE), + () -> assertThat(actual2.getMessage()).isEqualTo(BONUS_NUMBER_NOT_NUMBER_MESSAGE), + () -> assertThat(actual3.getMessage()).isEqualTo(BONUS_NUMBER_NOT_NUMBER_MESSAGE) + ); + } + +} \ No newline at end of file