diff --git a/docs/README.md b/docs/README.md index e69de29..b7e5778 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,90 @@ +## Feature + +- [X] User는 `1000원` 단위로 로또 N장을 구매한다 + - 구매 후 로또 생성기로부터 N장의 로또를 생성한다 + - `1000원` 단위로 입력하지 않을 경우 `IllegalArgumentException`를 발생시키고 `[Error]`로 시작하는 에러 메시지를 출력 후 `종료`한다 + +- [X] LottoWinningMachine은 로또 당첨 번호를 생성한다 + - 총 7개의 중복되지 않는 숫자 (기본 당첨 번호 6개 + 보너스 번호 1개) + +- [X] LottoStatistics는 N장의 로또에 대한 당첨 통계를 산출한다 + +
+
+ +## Model + +### `Lotto` + +- 로또 번호 List를 추상화시킨 모델 + - [X] 로또 번호는 1..45 범위 안에 존재해야 한다 + - [X] 각 번호들은 중복되지 않아야 한다 + +### `UserLotto` + +- `1000원` 단위의 로또 N장을 구매하는 컴포넌트 + - `List` + +### `LottoWinningMachine` + +- 로또 당첨 번호 6개 + 보너스 번호를 생성하는 컴포넌트 + +### `BonusNumber` + +- `보너스 번호`에 대한 컴포넌트 + +### `WinningRank` + +- 당첨과 관련된 순위를 표현하는 컴포넌트 + +### `LottoStatistics` + +- 로또 당첨 내역 및 수익률을 산출하기 위한 통계용 컴포넌트 + +
+
+ +## Utils + +### `ExceptionConstants` + +- 전역 예외 메시지 통합 컴포넌트 + +### `LottoConstants` + +- Lotto 숫자 범위 & 전체 사이즈 관련 상수 전용 컴포넌트 + +### `LottoRandomGenerator` + +- 로또 번호 자동 생성기 + +### `Validator` + +- 사용자 Input에 대한 기본 검증 컴포넌트 + - `LottoPurchaseAmountValidator` -> 로또 구입금액 검증 컴포넌트 + - `WinningNumberValidator` -> 로또 당첨번호 검증 컴포넌트 + - `BonusNumberValidator` -> 보너스 번호 검증 컴포넌트 + +
+
+ +## View + +### `InputView` + +- 사용자 Input을 받기 위한 컴포넌트 + +### `OutputView` + +- 로또 게임 진행과 관련된 출력 컴포넌트 + +
+
+ +## Controller + +### `GameController` + +- 로또 게임 진행과 관련된 컨트롤러 + +
diff --git a/src/main/java/lotto/Application.java b/src/main/java/lotto/Application.java index d190922..8fb5a65 100644 --- a/src/main/java/lotto/Application.java +++ b/src/main/java/lotto/Application.java @@ -1,7 +1,9 @@ package lotto; +import lotto.controller.GameController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + new GameController().run(); } } diff --git a/src/main/java/lotto/Lotto.java b/src/main/java/lotto/Lotto.java deleted file mode 100644 index 519793d..0000000 --- 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/controller/GameController.java b/src/main/java/lotto/controller/GameController.java new file mode 100644 index 0000000..fdbd937 --- /dev/null +++ b/src/main/java/lotto/controller/GameController.java @@ -0,0 +1,41 @@ +package lotto.controller; + +import lotto.model.LottoStatistics; +import lotto.model.LottoWinningMachine; +import lotto.model.UserLotto; +import lotto.view.InputView; +import lotto.view.OutputView; + +import java.util.List; + +public class GameController { + private static UserLotto userLotto; + private static LottoWinningMachine lottoWinningMachine; + + public void run() { + try { + buyLotto(); + initLottoWinningMachine(); + displayLottoResult(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + } + } + + private void buyLotto() { + final int lottoPurchaseCount = InputView.readLottoPurchaseCount(); + userLotto = UserLotto.issueLottoByPurchaseCount(lottoPurchaseCount); + OutputView.printPurchaseInformation(userLotto.getUserLottos()); + } + + private void initLottoWinningMachine() { + final List winingNumbers = InputView.readWiningNumbers(); + final int bonusNumber = InputView.readBonusNumber(); + lottoWinningMachine = LottoWinningMachine.drawWinningLottery(winingNumbers, bonusNumber); + } + + private void displayLottoResult() { + final LottoStatistics lottoStatistics = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLotto); + OutputView.printWinningStatistics(lottoStatistics); + } +} diff --git a/src/main/java/lotto/model/BonusNumber.java b/src/main/java/lotto/model/BonusNumber.java new file mode 100644 index 0000000..e0cf256 --- /dev/null +++ b/src/main/java/lotto/model/BonusNumber.java @@ -0,0 +1,28 @@ +package lotto.model; + +import static lotto.utils.ExceptionConstants.LottoMachineException.BONUS_NUMBER_IS_NOT_IN_RANGE; +import static lotto.utils.LottoConstants.MAX_VALUE; +import static lotto.utils.LottoConstants.MIN_VALUE; + +public class BonusNumber { + private final int value; + + public BonusNumber(final int value) { + validateBonusNumberIsInRange(value); + this.value = value; + } + + private void validateBonusNumberIsInRange(final int value) { + if (isOutOfRange(value)) { + throw new IllegalArgumentException(BONUS_NUMBER_IS_NOT_IN_RANGE.message); + } + } + + private boolean isOutOfRange(final int value) { + return value < MIN_VALUE || value > MAX_VALUE; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/lotto/model/Lotto.java b/src/main/java/lotto/model/Lotto.java new file mode 100644 index 0000000..63c26b0 --- /dev/null +++ b/src/main/java/lotto/model/Lotto.java @@ -0,0 +1,58 @@ +package lotto.model; + +import java.util.Collections; +import java.util.List; + +import static java.util.Collections.sort; +import static lotto.utils.ExceptionConstants.LottoException.*; +import static lotto.utils.LottoConstants.*; + +public class Lotto { + private final List numbers; + + public Lotto(final List numbers) { + validateEachLottoElementIsInRange(numbers); + validateTotalLottoSize(numbers); + validateLottoHasDuplicateElement(numbers); + sort(numbers); + this.numbers = numbers; + } + + private void validateEachLottoElementIsInRange(final List numbers) { + if (hasOutOfRange(numbers)) { + throw new IllegalArgumentException(LOTTO_NUMBER_IS_NOT_IN_RANGE.message); + } + } + + private boolean hasOutOfRange(final List numbers) { + return numbers.stream() + .anyMatch(number -> number < MIN_VALUE || number > MAX_VALUE); + } + + private void validateTotalLottoSize(final List numbers) { + if (numbers.size() != LOTTO_SIZE) { + throw new IllegalArgumentException(LOTTO_SIZE_IS_NOT_FULFILL.message); + } + } + + private void validateLottoHasDuplicateElement(final List numbers) { + if (hasDuplicateNumber(numbers)) { + throw new IllegalArgumentException(LOTTO_NUMBER_MUST_BE_UNIQUE.message); + } + } + + private boolean hasDuplicateNumber(final List number) { + return number.stream() + .distinct() + .count() != LOTTO_SIZE; + } + + public List getNumbers() { + return Collections.unmodifiableList(numbers); + } + + @Override + public String toString() { + return numbers.toString(); + } +} diff --git a/src/main/java/lotto/model/LottoStatistics.java b/src/main/java/lotto/model/LottoStatistics.java new file mode 100644 index 0000000..1ed9a59 --- /dev/null +++ b/src/main/java/lotto/model/LottoStatistics.java @@ -0,0 +1,104 @@ +package lotto.model; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class LottoStatistics { + private final LottoWinningMachine lottoWinningMachine; + private final UserLotto userLotto; + private final Map winningResult = new EnumMap<>(WinningRank.class); + + private LottoStatistics( + final LottoWinningMachine lottoWinningMachine, + final UserLotto userLotto + ) { + this.lottoWinningMachine = lottoWinningMachine; + this.userLotto = userLotto; + initWinningResult(); + calculateLotteryWinningResult(); + } + + public static LottoStatistics checkLotteryResult( + final LottoWinningMachine lottoWinningMachine, + final UserLotto userLotto + ) { + return new LottoStatistics(lottoWinningMachine, userLotto); + } + + private void initWinningResult() { + for (WinningRank winningRank : WinningRank.values()) { + winningResult.put(winningRank, 0); + } + } + + private void calculateLotteryWinningResult() { + List winningLotteryNumbers = lottoWinningMachine.getWinningLotteryNumbers(); + int bonusNumber = lottoWinningMachine.getBonusNumber(); + + for (Lotto lotto : userLotto.getUserLottos()) { + List lottoNumbers = lotto.getNumbers(); + int matchCount = getLottoMatchCount(lottoNumbers, winningLotteryNumbers); + boolean hasBonus = isBonusNumberExists(lottoNumbers, bonusNumber); + + WinningRank winningRank = WinningRank.of(matchCount, hasBonus); + updateWinningResult(winningRank); + } + } + + private int getLottoMatchCount( + final List lottoNumbers, + final List winningLotteryNumbers + ) { + return (int) lottoNumbers.stream() + .filter(winningLotteryNumbers::contains) + .count(); + } + + private boolean isBonusNumberExists( + final List lottoNumbers, + final int bonusNumber + ) { + return lottoNumbers.contains(bonusNumber); + } + + private void updateWinningResult(final WinningRank winningRank) { + winningResult.put(winningRank, winningResult.get(winningRank) + 1); + } + + public Map getWinningResult() { + return Collections.unmodifiableMap(winningResult); + } + + public int getWinningCountByRank(final WinningRank winningRank) { + return winningResult.getOrDefault(winningRank, 0); + } + + public BigDecimal getEarningRate() { + final BigDecimal lottoPurchaseAmount = userLotto.getLottoPurchaseAmount(); + final BigDecimal totalWinningAmount = calculateTotalWinningAmount(); + + return totalWinningAmount + .multiply(BigDecimal.valueOf(100)) // 백분율 + .divide(lottoPurchaseAmount, 1, RoundingMode.HALF_UP); + } + + private BigDecimal calculateTotalWinningAmount() { + BigDecimal amount = BigDecimal.ZERO; + for (WinningRank winningRank : winningResult.keySet()) { + final BigDecimal addPrize = getAddPrize(winningRank); + amount = amount.add(addPrize); + } + return amount; + } + + private BigDecimal getAddPrize(final WinningRank winningRank) { + final int reward = winningRank.getReward(); + final int count = winningResult.get(winningRank); + + return BigDecimal.valueOf((long) reward * count); + } +} diff --git a/src/main/java/lotto/model/LottoWinningMachine.java b/src/main/java/lotto/model/LottoWinningMachine.java new file mode 100644 index 0000000..5d32380 --- /dev/null +++ b/src/main/java/lotto/model/LottoWinningMachine.java @@ -0,0 +1,43 @@ +package lotto.model; + +import java.util.List; + +import static lotto.utils.ExceptionConstants.LottoMachineException.BONUS_NUMBER_MUST_BE_UNIQUE; + +public class LottoWinningMachine { + private final Lotto winningLottery; + private final BonusNumber bonusNumber; + + private LottoWinningMachine( + final List winningNumbers, + final int bonusNumber + ) { + validateBonusNumberIsDuplicate(winningNumbers, bonusNumber); + this.winningLottery = new Lotto(winningNumbers); + this.bonusNumber = new BonusNumber(bonusNumber); + } + + public static LottoWinningMachine drawWinningLottery( + final List winningNumbers, + final int bonusNumber + ) { + return new LottoWinningMachine(winningNumbers, bonusNumber); + } + + private void validateBonusNumberIsDuplicate( + final List winningNumbers, + final int bonusNumber + ) { + if (winningNumbers.contains(bonusNumber)) { + throw new IllegalArgumentException(BONUS_NUMBER_MUST_BE_UNIQUE.message); + } + } + + public List getWinningLotteryNumbers() { + return winningLottery.getNumbers(); + } + + public int getBonusNumber() { + return bonusNumber.getValue(); + } +} diff --git a/src/main/java/lotto/model/UserLotto.java b/src/main/java/lotto/model/UserLotto.java new file mode 100644 index 0000000..ca016e6 --- /dev/null +++ b/src/main/java/lotto/model/UserLotto.java @@ -0,0 +1,45 @@ +package lotto.model; + +import lotto.utils.LottoRandomGenerator; +import org.assertj.core.util.VisibleForTesting; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class UserLotto { + private final List userLottos; + + private UserLotto(final int lottoPurchaseCount) { + this.userLottos = createLottoByPurchaseCount(lottoPurchaseCount); + } + + @VisibleForTesting + public UserLotto(final List userLottos) { + this.userLottos = userLottos; + } + + public static UserLotto issueLottoByPurchaseCount(final int lottoPurchaseCount) { + return new UserLotto(lottoPurchaseCount); + } + + private List createLottoByPurchaseCount(final int lottoPurchaseCount) { + return Stream.generate(() -> new Lotto(LottoRandomGenerator.generate())) + .limit(lottoPurchaseCount) + .collect(Collectors.toList()); + } + + public List getUserLottos() { + return Collections.unmodifiableList(userLottos); + } + + public int getLottoPurchaseCount() { + return userLottos.size(); + } + + public BigDecimal getLottoPurchaseAmount() { + return BigDecimal.valueOf(1000).multiply(BigDecimal.valueOf(getLottoPurchaseCount())); + } +} diff --git a/src/main/java/lotto/model/WinningRank.java b/src/main/java/lotto/model/WinningRank.java new file mode 100644 index 0000000..1773381 --- /dev/null +++ b/src/main/java/lotto/model/WinningRank.java @@ -0,0 +1,50 @@ +package lotto.model; + +import java.util.Arrays; +import java.util.List; + +public enum WinningRank { + FIRST(6, List.of(false), 2_000_000_000, "6개 일치"), + SECOND(5, List.of(true), 30_000_000, "5개 일치, 보너스 볼 일치"), + THIRD(5, List.of(false), 1_500_000, "5개 일치"), + FOURTH(4, List.of(true, false), 50_000, "4개 일치"), + FIFTH(3, List.of(true, false), 5_000, "3개 일치"), + NONE(0, List.of(false), 0, "NONE..."), + ; + + private final int matchCount; + private final List hasBonus; + private final int reward; + private final String description; + + WinningRank( + final int matchCount, + final List hasBonus, + final int reward, + final String description + ) { + this.matchCount = matchCount; + this.hasBonus = hasBonus; + this.reward = reward; + this.description = description; + } + + public static WinningRank of( + final int matchCount, + final boolean hasBonus + ) { + return Arrays.stream(values()) + .filter(winningRank -> winningRank.matchCount == matchCount) + .filter(winningRank -> winningRank.hasBonus.contains(hasBonus)) + .findFirst() + .orElse(NONE); + } + + public int getReward() { + return reward; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/lotto/utils/ExceptionConstants.java b/src/main/java/lotto/utils/ExceptionConstants.java new file mode 100644 index 0000000..339c422 --- /dev/null +++ b/src/main/java/lotto/utils/ExceptionConstants.java @@ -0,0 +1,43 @@ +package lotto.utils; + +public interface ExceptionConstants { + enum LottoException { + LOTTO_NUMBER_IS_NOT_IN_RANGE("로또 번호는 1부터 45 사이의 숫자여야 합니다."), + LOTTO_SIZE_IS_NOT_FULFILL("구매한 로또는 총 6개의 번호가 기입되어야 합니다."), + LOTTO_NUMBER_MUST_BE_UNIQUE("중복된 번호는 허용하지 않습니다."), + ; + + public final String message; + + LottoException(final String message) { + this.message = message; + } + } + + enum LottoMachineException { + BONUS_NUMBER_IS_NOT_IN_RANGE("보너스 번호는 1부터 45 사이의 숫자여야 합니다."), + BONUS_NUMBER_MUST_BE_UNIQUE("보너스 번호는 당첨 번호와 중복되지 않아야 합니다."), + ; + + public final String message; + + LottoMachineException(final String message) { + this.message = message; + } + } + + enum InputException { + INPUT_MUST_NOT_CONTAINS_SPACE("공백없이 입력해주세요."), + INPUT_MUST_BE_NUMERIC("숫자를 입력해주세요."), + PURCHASE_AMOUNT_MUST_BE_POSITIVE("구입 금액은 음수가 될 수 없습니다."), + PURCHASE_AMOUNT_MUST_BE_THOUSAND_UNIT("천원 단위로 구입금액을 입력해주세요."), + WINNING_NUMBER_MUST_BE_SPLIT_BY_COMMA("당첨번호는 콤마(,)로 구분해서 공백없이 입력해주세요."), + ; + + public final String message; + + InputException(final String message) { + this.message = message; + } + } +} diff --git a/src/main/java/lotto/utils/LottoConstants.java b/src/main/java/lotto/utils/LottoConstants.java new file mode 100644 index 0000000..374e16c --- /dev/null +++ b/src/main/java/lotto/utils/LottoConstants.java @@ -0,0 +1,7 @@ +package lotto.utils; + +public interface LottoConstants { + int MIN_VALUE = 1; + int MAX_VALUE = 45; + int LOTTO_SIZE = 6; +} diff --git a/src/main/java/lotto/utils/LottoRandomGenerator.java b/src/main/java/lotto/utils/LottoRandomGenerator.java new file mode 100644 index 0000000..dda9cdc --- /dev/null +++ b/src/main/java/lotto/utils/LottoRandomGenerator.java @@ -0,0 +1,20 @@ +package lotto.utils; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.List; + +import static lotto.utils.LottoConstants.*; + +public class LottoRandomGenerator { + public static List generate() { + return new ArrayList<>( + Randoms.pickUniqueNumbersInRange( + MIN_VALUE, + MAX_VALUE, + LOTTO_SIZE + ) + ); + } +} diff --git a/src/main/java/lotto/utils/validator/BonusNumberValidator.java b/src/main/java/lotto/utils/validator/BonusNumberValidator.java new file mode 100644 index 0000000..c860a97 --- /dev/null +++ b/src/main/java/lotto/utils/validator/BonusNumberValidator.java @@ -0,0 +1,9 @@ +package lotto.utils.validator; + +public class BonusNumberValidator extends Validator { + @Override + public void validate(final String userInput) { + validateInputHasSpace(userInput); + validateInputIsNumeric(userInput); + } +} diff --git a/src/main/java/lotto/utils/validator/LottoPurchaseAmountValidator.java b/src/main/java/lotto/utils/validator/LottoPurchaseAmountValidator.java new file mode 100644 index 0000000..061f837 --- /dev/null +++ b/src/main/java/lotto/utils/validator/LottoPurchaseAmountValidator.java @@ -0,0 +1,32 @@ +package lotto.utils.validator; + +import static lotto.utils.ExceptionConstants.InputException.PURCHASE_AMOUNT_MUST_BE_POSITIVE; +import static lotto.utils.ExceptionConstants.InputException.PURCHASE_AMOUNT_MUST_BE_THOUSAND_UNIT; + +public class LottoPurchaseAmountValidator extends Validator { + private static final int PURCHASE_UNIT = 1000; + + @Override + public void validate(final String userInput) { + validateInputHasSpace(userInput); + validateInputIsNumeric(userInput); + validatePurchaseAmountIsPositive(userInput); + validateUnitOfAmountIsThousand(userInput); + } + + private void validatePurchaseAmountIsPositive(final String userInput) { + if (isUserInputNegative(userInput)) { + throw new IllegalArgumentException(PURCHASE_AMOUNT_MUST_BE_POSITIVE.message); + } + } + + private boolean isUserInputNegative(final String userInput) { + return Integer.parseInt(userInput) < 0; + } + + private void validateUnitOfAmountIsThousand(final String userInput) { + if (Integer.parseInt(userInput) % PURCHASE_UNIT != 0) { + throw new IllegalArgumentException(PURCHASE_AMOUNT_MUST_BE_THOUSAND_UNIT.message); + } + } +} diff --git a/src/main/java/lotto/utils/validator/Validator.java b/src/main/java/lotto/utils/validator/Validator.java new file mode 100644 index 0000000..3170014 --- /dev/null +++ b/src/main/java/lotto/utils/validator/Validator.java @@ -0,0 +1,27 @@ +package lotto.utils.validator; + +import static lotto.utils.ExceptionConstants.InputException.INPUT_MUST_BE_NUMERIC; +import static lotto.utils.ExceptionConstants.InputException.INPUT_MUST_NOT_CONTAINS_SPACE; + +public abstract class Validator { + abstract void validate(final String userInput); + + public void validateInputHasSpace(final String userInput) { + if (hasSpace(userInput)) { + throw new IllegalArgumentException(INPUT_MUST_NOT_CONTAINS_SPACE.message); + } + } + + private boolean hasSpace(final String userInput) { + return userInput.chars() + .anyMatch(Character::isWhitespace); + } + + public void validateInputIsNumeric(final String userInput) { + try { + Integer.parseInt(userInput); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException(INPUT_MUST_BE_NUMERIC.message); + } + } +} diff --git a/src/main/java/lotto/utils/validator/WinningNumberValidator.java b/src/main/java/lotto/utils/validator/WinningNumberValidator.java new file mode 100644 index 0000000..fd0b65c --- /dev/null +++ b/src/main/java/lotto/utils/validator/WinningNumberValidator.java @@ -0,0 +1,31 @@ +package lotto.utils.validator; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static lotto.utils.ExceptionConstants.InputException.WINNING_NUMBER_MUST_BE_SPLIT_BY_COMMA; + +public class WinningNumberValidator extends Validator { + private static final String COMMA = ","; + + @Override + public void validate(final String userInput) { + validateInputHasSpace(userInput); + inputSplitByComma(userInput) + .forEach(this::validateInputElementIsNumeric); + } + + private List inputSplitByComma(final String userInput) { + return Arrays.stream(userInput.split(COMMA)) + .collect(Collectors.toList()); + } + + private void validateInputElementIsNumeric(final String userInput) { + try { + Integer.parseInt(userInput); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException(WINNING_NUMBER_MUST_BE_SPLIT_BY_COMMA.message); + } + } +} diff --git a/src/main/java/lotto/view/InputView.java b/src/main/java/lotto/view/InputView.java new file mode 100644 index 0000000..0566fb5 --- /dev/null +++ b/src/main/java/lotto/view/InputView.java @@ -0,0 +1,54 @@ +package lotto.view; + +import camp.nextstep.edu.missionutils.Console; +import lotto.utils.validator.BonusNumberValidator; +import lotto.utils.validator.LottoPurchaseAmountValidator; +import lotto.utils.validator.WinningNumberValidator; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class InputView { + private static final int PURCHASE_UNIT = 1000; + private static final String COMMA = ","; + private static final LottoPurchaseAmountValidator LOTTO_PURCHASE_AMOUNT_VALIDATOR + = new LottoPurchaseAmountValidator(); + private static final WinningNumberValidator WINNING_NUMBER_VALIDATOR + = new WinningNumberValidator(); + private static final BonusNumberValidator BONUS_NUMBER_VALIDATOR + = new BonusNumberValidator(); + + public static int readLottoPurchaseCount() { + System.out.println("구입금액을 입력해 주세요."); + + final String userInput = Console.readLine(); + LOTTO_PURCHASE_AMOUNT_VALIDATOR.validate(userInput); + + return Integer.parseInt(userInput) / PURCHASE_UNIT; + } + + public static List readWiningNumbers() { + System.out.println("당첨 번호를 입력해 주세요."); + + final String userInput = Console.readLine(); + WINNING_NUMBER_VALIDATOR.validate(userInput); + + return convertUserInputToIntegerList(userInput); + } + + private static List convertUserInputToIntegerList(final String userInput) { + return Arrays.stream(userInput.split(COMMA)) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + public static int readBonusNumber() { + System.out.println("보너스 번호를 입력해 주세요."); + + final String userInput = Console.readLine(); + BONUS_NUMBER_VALIDATOR.validate(userInput); + + return Integer.parseInt(userInput); + } +} diff --git a/src/main/java/lotto/view/OutputView.java b/src/main/java/lotto/view/OutputView.java new file mode 100644 index 0000000..7c8bbdb --- /dev/null +++ b/src/main/java/lotto/view/OutputView.java @@ -0,0 +1,70 @@ +package lotto.view; + +import lotto.model.Lotto; +import lotto.model.LottoStatistics; +import lotto.model.WinningRank; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class OutputView { + private static final String ENTER = "\n"; + private static final String ERROR_MESSAGE_FORMAT = "[ERROR] %s"; + private static final String WINNING_FORMAT = "%s (%s원) - %d개"; + private static final String REWARD_FORMAT = "#,###"; + private static final String EARNING_FORMAT = "총 수익률은 %s%%입니다."; + + public static void printPurchaseInformation(final List userLottos) { + System.out.printf("%d개를 구매했습니다." + ENTER, userLottos.size()); + userLottos.forEach(System.out::println); + } + + public static void printWinningStatistics(final LottoStatistics lottoStatistics) { + StringBuilder result = new StringBuilder("당첨 통계" + ENTER + "---" + ENTER); + addWinningStatistics(lottoStatistics, result); + addEarningRate(lottoStatistics, result); + System.out.println(result); + } + + private static void addWinningStatistics( + final LottoStatistics lottoStatistics, + final StringBuilder result + ) { + List filteredWinningRank = getFilteredWinningRank(); + for (WinningRank winningRank : filteredWinningRank) { + result.append( + String.format( + WINNING_FORMAT, + winningRank.getDescription(), + refineReward(winningRank.getReward()), + lottoStatistics.getWinningCountByRank(winningRank) + ) + ).append(ENTER); + } + } + + private static List getFilteredWinningRank() { + return Arrays.stream(WinningRank.values()) + .filter(winningRank -> winningRank != WinningRank.NONE) + .sorted(Collections.reverseOrder()) // Enum Position DESC + .collect(Collectors.toList()); + } + + private static String refineReward(final int reward) { + return new DecimalFormat(REWARD_FORMAT).format(reward); + } + + private static void addEarningRate( + final LottoStatistics lottoStatistics, + final StringBuilder result + ) { + result.append(String.format(EARNING_FORMAT, lottoStatistics.getEarningRate())); + } + + public static void printErrorMessage(final String message) { + System.out.printf(ERROR_MESSAGE_FORMAT, message); + } +} diff --git a/src/test/java/lotto/LottoTest.java b/src/test/java/lotto/LottoTest.java deleted file mode 100644 index 0f3af0f..0000000 --- a/src/test/java/lotto/LottoTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package lotto; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class LottoTest { - @DisplayName("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.") - @Test - void createLottoByOverSize() { - assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 6, 7))) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.") - @Test - void createLottoByDuplicatedNumber() { - // TODO: 이 테스트가 통과할 수 있게 구현 코드 작성 - assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5))) - .isInstanceOf(IllegalArgumentException.class); - } - - // 아래에 추가 테스트 작성 가능 -} diff --git a/src/test/java/lotto/model/BonusNumberTest.java b/src/test/java/lotto/model/BonusNumberTest.java new file mode 100644 index 0000000..4b0c873 --- /dev/null +++ b/src/test/java/lotto/model/BonusNumberTest.java @@ -0,0 +1,45 @@ +package lotto.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static lotto.utils.ExceptionConstants.LottoMachineException.BONUS_NUMBER_IS_NOT_IN_RANGE; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class BonusNumberTest { + @ParameterizedTest + @MethodSource("invalidRange") + @DisplayName("보너스 번호의 범위가 1..45 이외라면 예외가 발생한다") + void throwExceptionByBonusNumberIsNotInRange(int value) { + assertThatThrownBy(() -> new BonusNumber(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BONUS_NUMBER_IS_NOT_IN_RANGE.message); + } + + private static Stream invalidRange() { + return Stream.of( + Arguments.of(0), + Arguments.of(46) + ); + } + + @ParameterizedTest + @MethodSource("validRange") + @DisplayName("BonusNumber를 생성한다") + void success(int value) { + assertDoesNotThrow(() -> new BonusNumber(value)); + } + + private static Stream validRange() { + return Stream.of( + Arguments.of(1), + Arguments.of(10), + Arguments.of(45) + ); + } +} diff --git a/src/test/java/lotto/model/LottoStatisticsTest.java b/src/test/java/lotto/model/LottoStatisticsTest.java new file mode 100644 index 0000000..996cb7a --- /dev/null +++ b/src/test/java/lotto/model/LottoStatisticsTest.java @@ -0,0 +1,165 @@ +package lotto.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class LottoStatisticsTest { + @Test + @DisplayName("구매한 로또 N장에 대한 당첨 통계를 조회한다") + void getWinningResult() { + // given + final LottoWinningMachine lottoWinningMachine = createLottoWinningMachine(); + final UserLotto userLottoCaseA = createUserLottosCaseA(); + final UserLotto userLottoCaseB = createUserLottosCaseB(); + final UserLotto userLottoCaseC = createUserLottosCaseB(); + + // when + final LottoStatistics caseA = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLottoCaseA); + final LottoStatistics caseB = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLottoCaseB); + final LottoStatistics caseC = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLottoCaseC); + + // then + final Map winningResultA = caseA.getWinningResult(); + assertAll( + () -> assertThat(winningResultA.get(WinningRank.FIRST)).isEqualTo(1), + () -> assertThat(winningResultA.get(WinningRank.SECOND)).isEqualTo(1), + () -> assertThat(winningResultA.get(WinningRank.THIRD)).isEqualTo(1), + () -> assertThat(winningResultA.get(WinningRank.FOURTH)).isEqualTo(2), + () -> assertThat(winningResultA.get(WinningRank.FIFTH)).isEqualTo(2), + () -> assertThat(winningResultA.get(WinningRank.NONE)).isEqualTo(5) + ); + + final Map winningResultB = caseB.getWinningResult(); + assertAll( + () -> assertThat(winningResultB.get(WinningRank.FIRST)).isEqualTo(0), + () -> assertThat(winningResultB.get(WinningRank.SECOND)).isEqualTo(0), + () -> assertThat(winningResultB.get(WinningRank.THIRD)).isEqualTo(0), + () -> assertThat(winningResultB.get(WinningRank.FOURTH)).isEqualTo(0), + () -> assertThat(winningResultB.get(WinningRank.FIFTH)).isEqualTo(3), + () -> assertThat(winningResultB.get(WinningRank.NONE)).isEqualTo(14) + ); + + final Map winningResultC = caseC.getWinningResult(); + assertAll( + () -> assertThat(winningResultC.get(WinningRank.FIRST)).isEqualTo(0), + () -> assertThat(winningResultC.get(WinningRank.SECOND)).isEqualTo(0), + () -> assertThat(winningResultC.get(WinningRank.THIRD)).isEqualTo(0), + () -> assertThat(winningResultC.get(WinningRank.FOURTH)).isEqualTo(0), + () -> assertThat(winningResultC.get(WinningRank.FIFTH)).isEqualTo(3), + () -> assertThat(winningResultC.get(WinningRank.NONE)).isEqualTo(14) + ); + } + + @Test + @DisplayName("구매한 로또 N장에 대한 수익률을 조회한다") + void getEarningRate() { + // given + final LottoWinningMachine lottoWinningMachine = createLottoWinningMachine(); + final UserLotto userLottoCaseA = createUserLottosCaseA(); + final UserLotto userLottoCaseB = createUserLottosCaseB(); + final UserLotto userLottoCaseC = createUserLottosCaseC(); + + // when + final LottoStatistics caseA = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLottoCaseA); + final LottoStatistics caseB = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLottoCaseB); + final LottoStatistics caseC = LottoStatistics.checkLotteryResult(lottoWinningMachine, userLottoCaseC); + + // then + assertAll( + () -> assertThat(caseA.getEarningRate()).isEqualTo(BigDecimal.valueOf(16930083.3)), + () -> assertThat(caseB.getEarningRate()).isEqualTo(BigDecimal.valueOf(88.2)), + () -> assertThat(caseC.getEarningRate()).isEqualTo(BigDecimal.valueOf(62.5)) + ); + } + + private LottoWinningMachine createLottoWinningMachine() { + return LottoWinningMachine.drawWinningLottery( + Arrays.asList(1, 2, 3, 4, 5, 6), + 7 + ); + } + + private UserLotto createUserLottosCaseA() { + return new UserLotto( + List.of( + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(1, 2, 3, 11, 12, 13)), // 5등 (당첨 3개) + new Lotto(Arrays.asList(1, 2, 3, 7, 12, 13)), // 5등 (당첨 3개 + 보너스 1개) + new Lotto(Arrays.asList(1, 2, 3, 4, 12, 13)), // 4등 (당첨 4개) + new Lotto(Arrays.asList(1, 2, 3, 4, 7, 13)), // 4등 (당첨 4개 + 보너스 1개) + new Lotto(Arrays.asList(1, 2, 3, 4, 5, 13)), // 3등 (당첨 5개) + new Lotto(Arrays.asList(1, 2, 3, 4, 5, 7)), // 2등 (당첨 5개 + 보너스 1개) + new Lotto(Arrays.asList(1, 2, 3, 4, 5, 6)) // 1등 (당첨 6개) + ) + ); + + /** + * 구매 금액 = 12_000 + * 당첨 금액 = 2,031,610,000 + * -> 수익률 = 169,300.83333333333333333333333333... = 16930083.33% = 16930083.3% + */ + } + + private UserLotto createUserLottosCaseB() { + return new UserLotto( + List.of( + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(8, 9, 10, 11, 12, 13)), // None + new Lotto(Arrays.asList(1, 2, 3, 11, 12, 13)), // 5등 (당첨 3개) + new Lotto(Arrays.asList(1, 2, 3, 11, 12, 13)), // 5등 (당첨 3개) + new Lotto(Arrays.asList(1, 2, 3, 7, 12, 13)) // 5등 (당첨 3개 + 보너스 1개) + ) + ); + + /** + * 구매 금액 = 17000 + * 당첨 금액 = 15000 + * -> 수익률 = 0.88235294117647058823529411764706... = 88.23% = 88.2% + */ + } + + private UserLotto createUserLottosCaseC() { + return new UserLotto( + List.of( + new Lotto(Arrays.asList(8, 21, 23, 41, 42, 43)), // None + new Lotto(Arrays.asList(3, 5, 11, 16, 32, 38)), // None + new Lotto(Arrays.asList(7, 11, 16, 35, 36, 44)), // None + new Lotto(Arrays.asList(1, 8, 11, 31, 41, 42)), // None + new Lotto(Arrays.asList(13, 14, 16, 38, 42, 45)), // None + new Lotto(Arrays.asList(7, 11, 30, 40, 42, 43)), // None + new Lotto(Arrays.asList(2, 13, 22, 32, 38, 45)), // None + new Lotto(Arrays.asList(1, 3, 5, 14, 22, 45)) // 5등 (당첨 3개) + ) + ); + + /** + * 구매 금액 = 8000 + * 당첨 금액 = 5000 + * -> 수익률 = 0.625 = 62.5% + */ + } +} diff --git a/src/test/java/lotto/model/LottoTest.java b/src/test/java/lotto/model/LottoTest.java new file mode 100644 index 0000000..407eff2 --- /dev/null +++ b/src/test/java/lotto/model/LottoTest.java @@ -0,0 +1,87 @@ +package lotto.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static lotto.utils.ExceptionConstants.LottoException.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +class LottoTest { + @ParameterizedTest + @MethodSource("invalidRange") + @DisplayName("로또 번호의 범위가 1..45 이외라면 예외가 발생한다") + void throwExceptionByLottoNumberIsNotInRange(List numbers) { + assertThatThrownBy(() -> new Lotto(numbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(LOTTO_NUMBER_IS_NOT_IN_RANGE.message); + } + + private static Stream invalidRange() { + return Stream.of( + Arguments.of(List.of(0, 1, 2, 3, 4, 5)), + Arguments.of(List.of(1, 2, 3, 4, 5, 46)) + ); + } + + @ParameterizedTest + @MethodSource("invalidSize") + @DisplayName("로또 번호의 개수가 6개가 아니면 예외가 발생한다") + void throwExceptionByLottoSizeNotFulfill(List numbers) { + assertThatThrownBy(() -> new Lotto(numbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(LOTTO_SIZE_IS_NOT_FULFILL.message); + } + + private static Stream invalidSize() { + return Stream.of( + Arguments.of(List.of()), + Arguments.of(List.of(1)), + Arguments.of(List.of(1, 2)), + Arguments.of(List.of(1, 2, 3)), + Arguments.of(List.of(1, 2, 3, 4)), + Arguments.of(List.of(1, 2, 3, 4, 5)) + ); + } + + @ParameterizedTest + @MethodSource("duplicateNumber") + @DisplayName("로또 번호에 중복된 숫자가 있으면 예외가 발생한다") + void throwExceptionByLottoNumberIsNotUnique(List numbers) { + assertThatThrownBy(() -> new Lotto(numbers)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(LOTTO_NUMBER_MUST_BE_UNIQUE.message); + } + + private static Stream duplicateNumber() { + return Stream.of( + Arguments.of(List.of(1, 1, 2, 3, 4, 5)), + Arguments.of(List.of(1, 1, 1, 2, 3, 4)), + Arguments.of(List.of(1, 1, 1, 1, 2, 3)), + Arguments.of(List.of(1, 1, 1, 1, 1, 2)), + Arguments.of(List.of(1, 1, 1, 1, 1, 1)) + ); + } + + @Test + @DisplayName("Lotto를 생성한다") + void construct() { + // when + final Lotto lottoA = new Lotto(Arrays.asList(1, 3, 2, 4, 5, 6)); + final Lotto lottoB = new Lotto(Arrays.asList(44, 1, 10, 23, 18, 6)); + + // then + assertAll( + () -> assertThat(lottoA.getNumbers()).containsExactly(1, 2, 3, 4, 5, 6), + () -> assertThat(lottoB.getNumbers()).containsExactly(1, 6, 10, 18, 23, 44) + ); + } +} diff --git a/src/test/java/lotto/model/LottoWinningMachineTest.java b/src/test/java/lotto/model/LottoWinningMachineTest.java new file mode 100644 index 0000000..01264b3 --- /dev/null +++ b/src/test/java/lotto/model/LottoWinningMachineTest.java @@ -0,0 +1,44 @@ +package lotto.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static lotto.utils.ExceptionConstants.LottoMachineException.BONUS_NUMBER_MUST_BE_UNIQUE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +class LottoWinningMachineTest { + @Test + @DisplayName("보너스 번호가 당첨 번호에 중복되면 예외가 발생한다") + void throwExceptionByBonusNumberIsNotUnique() { + // given + final List winningNumbers = Arrays.asList(1, 2, 3, 4, 5, 6); + final int bonusNumber = 1; + + // when - then + assertThatThrownBy(() -> LottoWinningMachine.drawWinningLottery(winningNumbers, bonusNumber)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(BONUS_NUMBER_MUST_BE_UNIQUE.message); + } + + @Test + @DisplayName("LottoWinningMachine을 생성한다") + void success() { + // given + final List winningNumbers = Arrays.asList(1, 2, 3, 4, 5, 6); + final int bonusNumber = 7; + + // when + final LottoWinningMachine lottoWinningMachine = LottoWinningMachine.drawWinningLottery(winningNumbers, bonusNumber); + + // then + assertAll( + () -> assertThat(lottoWinningMachine.getWinningLotteryNumbers()).containsExactlyInAnyOrderElementsOf(winningNumbers), + () -> assertThat(lottoWinningMachine.getBonusNumber()).isEqualTo(bonusNumber) + ); + } +} diff --git a/src/test/java/lotto/model/UserLottoTest.java b/src/test/java/lotto/model/UserLottoTest.java new file mode 100644 index 0000000..0c45ab2 --- /dev/null +++ b/src/test/java/lotto/model/UserLottoTest.java @@ -0,0 +1,36 @@ +package lotto.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigDecimal; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class UserLottoTest { + @ParameterizedTest + @MethodSource("issueCase") + @DisplayName("구매한 개수만큼 UseLotto를 발급받는다") + void issueLottoByPurchaseCount(int lottoPurchaseCount, BigDecimal purchaseAmount) { + // when + final UserLotto userLotto = UserLotto.issueLottoByPurchaseCount(lottoPurchaseCount); + + // then + assertAll( + () -> assertThat(userLotto.getLottoPurchaseCount()).isEqualTo(lottoPurchaseCount), + () -> assertThat(userLotto.getLottoPurchaseAmount()).isEqualTo(purchaseAmount) + ); + } + + private static Stream issueCase() { + return Stream.of( + Arguments.of(5, BigDecimal.valueOf(5_000)), + Arguments.of(10, BigDecimal.valueOf(10_000)), + Arguments.of(1_000_000, BigDecimal.valueOf(1_000_000_000)) + ); + } +} diff --git a/src/test/java/lotto/model/WinningRankTest.java b/src/test/java/lotto/model/WinningRankTest.java new file mode 100644 index 0000000..aa7346d --- /dev/null +++ b/src/test/java/lotto/model/WinningRankTest.java @@ -0,0 +1,37 @@ +package lotto.model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static lotto.model.WinningRank.*; +import static org.assertj.core.api.Assertions.assertThat; + +class WinningRankTest { + @ParameterizedTest + @MethodSource("winningCase") + @DisplayName("로또 번호 당첨 개수 + 보너스 볼 일치 여부에 따른 WinningRank를 배정받는다") + void getWinningRank(int matchCount, boolean hasBonus, WinningRank expect) { + assertThat(WinningRank.of(matchCount, hasBonus)).isEqualTo(expect); + } + + private static Stream winningCase() { + return Stream.of( + Arguments.of(6, false, FIRST), + Arguments.of(5, true, SECOND), + Arguments.of(5, false, THIRD), + Arguments.of(4, true, FOURTH), + Arguments.of(4, false, FOURTH), + Arguments.of(3, true, FIFTH), + Arguments.of(3, false, FIFTH), + Arguments.of(2, true, NONE), + Arguments.of(2, false, NONE), + Arguments.of(1, true, NONE), + Arguments.of(1, false, NONE), + Arguments.of(0, false, NONE) + ); + } +} diff --git a/src/test/java/lotto/utils/validator/BonusNumberValidatorTest.java b/src/test/java/lotto/utils/validator/BonusNumberValidatorTest.java new file mode 100644 index 0000000..9189cbe --- /dev/null +++ b/src/test/java/lotto/utils/validator/BonusNumberValidatorTest.java @@ -0,0 +1,36 @@ +package lotto.utils.validator; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static lotto.utils.ExceptionConstants.InputException.INPUT_MUST_BE_NUMERIC; +import static lotto.utils.ExceptionConstants.InputException.INPUT_MUST_NOT_CONTAINS_SPACE; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class BonusNumberValidatorTest { + private static final BonusNumberValidator BONUS_NUMBER_VALIDATOR + = new BonusNumberValidator(); + + @Test + @DisplayName("보너스 번호에 공백이 존재하면 예외가 발생한다") + void throwExceptionByInputHasSpace() { + assertThatThrownBy(() -> BONUS_NUMBER_VALIDATOR.validate("7 ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INPUT_MUST_NOT_CONTAINS_SPACE.message); + } + + @Test + @DisplayName("보너스 번호가 숫자가 아니면 예외가 발생한다") + void throwExceptionByInputIsNotNumeric() { + assertThatThrownBy(() -> BONUS_NUMBER_VALIDATOR.validate("a")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INPUT_MUST_BE_NUMERIC.message); + } + + @Test + @DisplayName("보너스 번호 검증에 성공한다") + void success() { + assertDoesNotThrow(() -> BONUS_NUMBER_VALIDATOR.validate("7")); + } +} diff --git a/src/test/java/lotto/utils/validator/LottoPurchaseAmountValidatorTest.java b/src/test/java/lotto/utils/validator/LottoPurchaseAmountValidatorTest.java new file mode 100644 index 0000000..5a538da --- /dev/null +++ b/src/test/java/lotto/utils/validator/LottoPurchaseAmountValidatorTest.java @@ -0,0 +1,51 @@ +package lotto.utils.validator; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static lotto.utils.ExceptionConstants.InputException.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class LottoPurchaseAmountValidatorTest { + private static final LottoPurchaseAmountValidator LOTTO_PURCHASE_AMOUNT_VALIDATOR + = new LottoPurchaseAmountValidator(); + + @Test + @DisplayName("로또 구입금액에 공백이 존재하면 예외가 발생한다") + void throwExceptionByInputHasSpace() { + assertThatThrownBy(() -> LOTTO_PURCHASE_AMOUNT_VALIDATOR.validate("8000 ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INPUT_MUST_NOT_CONTAINS_SPACE.message); + } + + @Test + @DisplayName("로또 구입금액이 숫자가 아니면 예외가 발생한다") + void throwExceptionByInputIsNotNumeric() { + assertThatThrownBy(() -> LOTTO_PURCHASE_AMOUNT_VALIDATOR.validate("abcde")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INPUT_MUST_BE_NUMERIC.message); + } + + @Test + @DisplayName("로또 구입금액이 음수면 예외가 발생한다") + void throwExceptionByPurchaseAmountIsNegative() { + assertThatThrownBy(() -> LOTTO_PURCHASE_AMOUNT_VALIDATOR.validate("-8000")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(PURCHASE_AMOUNT_MUST_BE_POSITIVE.message); + } + + @Test + @DisplayName("로또 구입금액이 1000원 단위가 아니면 예외가 발생한다") + void throwExceptionByUnitOfAmountIsNotThousand() { + assertThatThrownBy(() -> LOTTO_PURCHASE_AMOUNT_VALIDATOR.validate("800")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(PURCHASE_AMOUNT_MUST_BE_THOUSAND_UNIT.message); + } + + @Test + @DisplayName("로또 구입금액 검증에 성공한다") + void success() { + assertDoesNotThrow(() -> LOTTO_PURCHASE_AMOUNT_VALIDATOR.validate("8000")); + } +} diff --git a/src/test/java/lotto/utils/validator/WinningNumberValidatorTest.java b/src/test/java/lotto/utils/validator/WinningNumberValidatorTest.java new file mode 100644 index 0000000..8e91451 --- /dev/null +++ b/src/test/java/lotto/utils/validator/WinningNumberValidatorTest.java @@ -0,0 +1,36 @@ +package lotto.utils.validator; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static lotto.utils.ExceptionConstants.InputException.INPUT_MUST_NOT_CONTAINS_SPACE; +import static lotto.utils.ExceptionConstants.InputException.WINNING_NUMBER_MUST_BE_SPLIT_BY_COMMA; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class WinningNumberValidatorTest { + private static final WinningNumberValidator WINNING_NUMBER_VALIDATOR + = new WinningNumberValidator(); + + @Test + @DisplayName("당첨번호에 공백이 존재하면 예외가 발생한다") + void throwExceptionByInputHasSpace() { + assertThatThrownBy(() -> WINNING_NUMBER_VALIDATOR.validate("1,2,3,4, 5, 6, ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INPUT_MUST_NOT_CONTAINS_SPACE.message); + } + + @Test + @DisplayName("당첨번호의 각 번호들이 숫자가 아니면 예외가 발생한다") + void throwExceptionByInputIsNotNumeric() { + assertThatThrownBy(() -> WINNING_NUMBER_VALIDATOR.validate("1,2,3,a,4,5,b,6")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(WINNING_NUMBER_MUST_BE_SPLIT_BY_COMMA.message); + } + + @Test + @DisplayName("당첨번호 검증에 성공한다") + void success() { + assertDoesNotThrow(() -> WINNING_NUMBER_VALIDATOR.validate("1,2,3,4,5,6")); + } +}