diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java index f6a8d8e4..f01defa5 100644 --- a/src/main/java/controller/LottoController.java +++ b/src/main/java/controller/LottoController.java @@ -1,54 +1,70 @@ package controller; -import model.Lotto; -import model.LottoTickets; +import model.*; import view.ErrorView; import view.InputView; import view.ResultView; import java.util.List; -import java.util.stream.Collectors; +import java.util.Map; + public class LottoController { public void run() { try { - int purchaseAmount = InputView.getPurchaseAmount(); - - validatePurchaseAmount(purchaseAmount); - - int ticketCount = LottoTickets.getTicketCount(purchaseAmount); - - LottoTickets lottoTickets = new LottoTickets(ticketCount); - - ResultView.printOrderTickets(ticketCount); - - ResultView.printPurchasedLottoTickets(formatTickets(lottoTickets.getTickets())); - + LottoPurchaseInfo lottoPurchaseInfo = getPurchaseAmount(); + int manualCount = getManualCount(lottoPurchaseInfo); + LottoTickets lottoTickets = generateLottoTickets(lottoPurchaseInfo, manualCount); + ResultView.printOrderTickets(manualCount, lottoPurchaseInfo.getTicketCount() - manualCount); + ResultView.printPurchasedLottoTickets(lottoTickets.getFormattedTicketNumbers()); + processWinningResults(lottoTickets, lottoPurchaseInfo); + InputView.closeScanner(); } catch (IllegalArgumentException e) { ErrorView.printErrorMessage(e.getMessage()); } } - private List formatTickets(List tickets) { - return tickets.stream() - .map(this::convertLottoToString) - .toList(); + private LottoPurchaseInfo getPurchaseAmount() { + return new LottoPurchaseInfo(InputView.getPurchaseAmount()); } - private String convertLottoToString(Lotto lotto) { - return lotto.getSortedNumbers().stream() - .map(String::valueOf) - .collect(Collectors.joining(",", "[", "]")); + private int getManualCount(LottoPurchaseInfo lottoPurchaseInfo) { + int manualCount = InputView.getManualTicketCount(); + validateManualCount(manualCount, lottoPurchaseInfo); + return manualCount; } - private void validatePurchaseAmount(int purchaseAmount) { - if (purchaseAmount < LottoTickets.LOTTO_PRICE) { - throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); - } + private LottoTickets generateLottoTickets(LottoPurchaseInfo lottoPurchaseInfo, int manualCount) { + List> manualNumbers = InputView.getManualNumbers(manualCount); + int autoCount = lottoPurchaseInfo.getTicketCount() - manualCount; + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(autoCount); + return LottoTickets.merge(manualTickets, autoTickets); + } - if (purchaseAmount % LottoTickets.LOTTO_PRICE != 0) { - throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); + public void validateManualCount(int manualCount, LottoPurchaseInfo lottoPurchaseInfo) { + if (manualCount > lottoPurchaseInfo.getTicketCount()) { + throw new IllegalArgumentException("수동 구매 개수가 구매 가능한 개수를 초과할 수 없습니다."); } } + + private void processWinningResults(LottoTickets lottoTickets, LottoPurchaseInfo lottoPurchaseInfo) { + LottoResult lottoResult = createLottoResult(lottoTickets, getWinningNumbers()); + + Map formattedWinningDetails = lottoResult.getFormattedWinningDetails(); + double profitRate = lottoResult.calculateProfitRate(lottoPurchaseInfo.getAmount()); + + ResultView.printWinningStatistics(formattedWinningDetails, profitRate); + } + + private WinningNumbers getWinningNumbers() { + List winningNumbers = InputView.getWinningNumbers(); + int bonusNumber = InputView.getBonusNumber(); + return new WinningNumbers(winningNumbers, bonusNumber); + } + + private LottoResult createLottoResult(LottoTickets lottoTickets, WinningNumbers winningNumbers) { + return new LottoResult(lottoTickets.getTickets(), winningNumbers); + } } diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java index 0d9f6daa..c900649f 100644 --- a/src/main/java/model/Lotto.java +++ b/src/main/java/model/Lotto.java @@ -1,38 +1,22 @@ package model; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; +//로또 한 장 public class Lotto { + private final LottoNumbers lottoNumbers; - // 로또 번호 관련 상수 선언 - public static final int LOTTO_MIN_NUMBER = 1; - public static final int LOTTO_MAX_NUMBER = 45; - public static final int LOTTO_CREATE_SIZE = 6; - public static final List LOTTO_NUMBER_POOL = - IntStream.rangeClosed(LOTTO_MIN_NUMBER,LOTTO_MAX_NUMBER) - .boxed() - .collect(Collectors.toList()); - - private List numbers; - - public Lotto(){ - this.numbers = createLottoNumbers(); + //수동 로또 + public Lotto(List numbers) { + this.lottoNumbers = new LottoNumbers(numbers); } - private List createLottoNumbers(){ - List shuffledNumbers = new ArrayList<>(LOTTO_NUMBER_POOL); - Collections.shuffle(shuffledNumbers); - numbers = shuffledNumbers.subList(0, LOTTO_CREATE_SIZE); - - return numbers; + //자동 로또 + public Lotto(LottoNumbers lottoNumbers) { + this.lottoNumbers = lottoNumbers; } - public List getSortedNumbers() { - List sortedNumbers = new ArrayList<>(numbers); - Collections.sort(sortedNumbers); - - return sortedNumbers; + public List getNumbers() { + return lottoNumbers.getNumbers(); } } diff --git a/src/main/java/model/LottoNumbers.java b/src/main/java/model/LottoNumbers.java new file mode 100644 index 00000000..7a27db9a --- /dev/null +++ b/src/main/java/model/LottoNumbers.java @@ -0,0 +1,53 @@ +package model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class LottoNumbers { + private static final int LOTTO_MIN_NUMBER = 1; + private static final int LOTTO_MAX_NUMBER = 45; + private static final int LOTTO_CREATE_SIZE = 6; + + private final List numbers; + + public LottoNumbers() { + this.numbers = sortedNumbers(generateAutoLottoNumbers()); + } + + public LottoNumbers(List numbers) { + validate(numbers); + this.numbers = sortedNumbers(numbers); + } + + private void validate(List numbers) { + if (numbers.size() != LOTTO_CREATE_SIZE) { + throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); + } + if (numbers.stream().distinct().count() != LOTTO_CREATE_SIZE) { + throw new IllegalArgumentException("중복된 숫자가 있습니다."); + } + if (numbers.stream().anyMatch(n -> n < LOTTO_MIN_NUMBER || n > LOTTO_MAX_NUMBER)) { + throw new IllegalArgumentException("로또 번호는 1~45 사이여야 합니다."); + } + } + + private List generateAutoLottoNumbers() { + List shuffledNumbers = IntStream.rangeClosed(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER) + .boxed() + .collect(Collectors.toList()); + Collections.shuffle(shuffledNumbers); + + return shuffledNumbers.subList(0,LOTTO_CREATE_SIZE); + } + + private List sortedNumbers(List numbers) { + return List.copyOf(numbers.stream().sorted().toList()); + } + + public List getNumbers() { + return numbers; + } +} diff --git a/src/main/java/model/LottoPurchaseInfo.java b/src/main/java/model/LottoPurchaseInfo.java new file mode 100644 index 00000000..cebd3754 --- /dev/null +++ b/src/main/java/model/LottoPurchaseInfo.java @@ -0,0 +1,34 @@ +package model; + +public class LottoPurchaseInfo { + private static final int LOTTO_PRICE = 1000; + private final int amount; + + public LottoPurchaseInfo(int amount) { + validatePurchaseAmount(amount); + this.amount = amount; + } + + private void validatePurchaseAmount(int amount) { + if (amount < LOTTO_PRICE) { + throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); + } + if (amount % LOTTO_PRICE != 0) { + throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); + } + } + + public int getTicketCount() { + return amount / LOTTO_PRICE; + } + + public int getAmount() { + return amount; + } + + public void validateManualCount(int manualCount) { + if (manualCount > getTicketCount()) { + throw new IllegalArgumentException("수동 구매 개수가 구매 가능한 개수를 초과할 수 없습니다."); + } + } +} diff --git a/src/main/java/model/LottoResult.java b/src/main/java/model/LottoResult.java new file mode 100644 index 00000000..ffead3ac --- /dev/null +++ b/src/main/java/model/LottoResult.java @@ -0,0 +1,92 @@ +package model; + +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class LottoResult { + private final Map matchCountMap = new EnumMap<>(Rank.class); + + public LottoResult(List tickets, WinningNumbers winningNumbers) { + for (Lotto ticket : tickets) { + Rank rank = determineRank(ticket, winningNumbers); + matchCountMap.put(rank, matchCountMap.getOrDefault(rank, 0) + 1); + } + } + + private Rank determineRank(Lotto ticket, WinningNumbers winningNumbers) { + int matchCount = countMatchingNumbers(ticket, winningNumbers); + boolean hasBonus = hasBonusNumber(ticket, winningNumbers); + + return getRank(matchCount, hasBonus); + } + + // 일치하는 숫자 개수 계산 + private int countMatchingNumbers(Lotto ticket, WinningNumbers winningNumbers) { + return (int) ticket.getNumbers().stream() + .filter(winningNumbers.getNumbers()::contains) + .count(); + } + + // 보너스 번호 포함 여부 확인 + private boolean hasBonusNumber(Lotto ticket, WinningNumbers winningNumbers) { + return ticket.getNumbers().contains(winningNumbers.getBonusNumber()); + } + + private Rank getRank(int matchCount, boolean hasBonus) { + if (matchCount == 6) return Rank.FIRST; + if (matchCount == 5 && hasBonus) return Rank.SECOND; + if (matchCount == 5) return Rank.THIRD; + if (matchCount == 4) return Rank.FOURTH; + if (matchCount == 3) return Rank.FIFTH; + return Rank.NONE; + } + + public Map getMatchCountMap() { + return matchCountMap; + } + + public double calculateProfitRate(int totalCost) { + List prizeAmounts = convertToPrizeAmounts(); + int totalPrize = sumPrizeAmounts(prizeAmounts); + return (double) totalPrize / totalCost; + } + + // 데이터 변환 + private List convertToPrizeAmounts() { + return matchCountMap.entrySet().stream() + .map(this::convertToPrizeAmount) + .toList(); + } + + // Rank 데이터를 변환 + private int convertToPrizeAmount(Map.Entry rankEntry) { + Rank rank = rankEntry.getKey(); + int count = rankEntry.getValue(); + return rank.getPrizeMoney() * count; + } + + // 변환된 데이터를 계산 + private int sumPrizeAmounts(List prizeAmounts) { + return prizeAmounts.stream() + .mapToInt(Integer::intValue) + .sum(); + } + + public Map getFormattedWinningDetails() { + Map formattedDetails = new LinkedHashMap<>(); + + for (Rank rank : Rank.values()) { + addRankIfValid(formattedDetails, rank); + } + + return formattedDetails; + } + + private void addRankIfValid(Map formattedDetails, Rank rank) { + if (rank != Rank.NONE) { + formattedDetails.put(rank.getDescription(), matchCountMap.getOrDefault(rank, 0)); + } + } +} diff --git a/src/main/java/model/LottoTickets.java b/src/main/java/model/LottoTickets.java index 9ed8f200..42892faa 100644 --- a/src/main/java/model/LottoTickets.java +++ b/src/main/java/model/LottoTickets.java @@ -4,27 +4,51 @@ import java.util.List; public class LottoTickets { - private final List tickets; - public static final int LOTTO_PRICE = 1000; - public LottoTickets(int ticketCount) { - this.tickets = generateLottoTickets(ticketCount); + private LottoTickets(List tickets, boolean unused) { + this.tickets = List.copyOf(tickets); + } + + public LottoTickets(List> manualNumbers) { + List manualTickets = new ArrayList<>(); + addManualTickets(manualTickets, manualNumbers); + this.tickets = List.copyOf(manualTickets); + } + + public LottoTickets(int autoTicketCount) { + List autoTickets = new ArrayList<>(); + addAutoTickets(autoTickets, autoTicketCount); + this.tickets = List.copyOf(autoTickets); + } + + public static LottoTickets merge(LottoTickets manualTickets, LottoTickets autoTickets) { + List mergedTickets = new ArrayList<>(manualTickets.tickets); + mergedTickets.addAll(autoTickets.tickets); + return new LottoTickets(mergedTickets, true); } - private List generateLottoTickets(int ticketCount) { - List tickets = new ArrayList<>(); - for (int i = 0; i < ticketCount; i++) { - tickets.add(new Lotto()); + private static void addManualTickets(List tickets, List> manualNumbers) { + for (List numbers : manualNumbers) { + tickets.add(new Lotto(numbers)); + } + } + + private static void addAutoTickets(List tickets, int autoTicketCount) { + for (int i = 0; i < autoTicketCount; i++) { + tickets.add(new Lotto(new LottoNumbers())); } - return tickets; } public List getTickets() { - return new ArrayList<>(tickets); + return tickets; } - public static int getTicketCount(int purchaseAmount){ - return purchaseAmount / LOTTO_PRICE; + public List> getFormattedTicketNumbers() { + List> ticketNumbers = new ArrayList<>(); + for (Lotto ticket : tickets) { + ticketNumbers.add(ticket.getNumbers()); + } + return ticketNumbers; } } diff --git a/src/main/java/model/Rank.java b/src/main/java/model/Rank.java new file mode 100644 index 00000000..c537e4e6 --- /dev/null +++ b/src/main/java/model/Rank.java @@ -0,0 +1,38 @@ +package model; + +public enum Rank { + FIFTH(3, false, 5_000, "3개 일치 (5000원)"), + FOURTH(4, false, 50_000, "4개 일치 (50000원)"), + THIRD(5, false, 1_500_000, "5개 일치 (150000원)"), + SECOND(5, true, 30_000_000, "5개 일치, 보너스 볼 일치(30000000원)"), + FIRST(6, false, 2_000_000_000, "6개 일치 (2000000000원)"), + NONE(0, false, 0, "꽝"); + + private final int matchCount; + private final boolean bonusMatch; + private final int prizeMoney; + private final String description; + + Rank(int matchCount, boolean bonusMatch, int prizeMoney, String description) { + this.matchCount = matchCount; + this.bonusMatch = bonusMatch; + this.prizeMoney = prizeMoney; + this.description = description; + } + + public int getMatchCount() { + return matchCount; + } + + public int getPrizeMoney() { + return prizeMoney; + } + + public boolean isBonusMatch() { + return bonusMatch; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/model/WinningNumbers.java b/src/main/java/model/WinningNumbers.java new file mode 100644 index 00000000..69076088 --- /dev/null +++ b/src/main/java/model/WinningNumbers.java @@ -0,0 +1,64 @@ +package model; + +import java.util.List; + +public class WinningNumbers { + private static final int SIZE = 6; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 45; + private final List numbers; + private final int bonusNumber; + + public WinningNumbers(List numbers, int bonusNumber) { + validate(numbers, bonusNumber); + this.numbers = List.copyOf(numbers); + this.bonusNumber = bonusNumber; + } + + public void validate(List numbers, int bonusNumber) { + validateSize(numbers); + validateDuplicates(numbers); + validateNumberRange(numbers); + validateBonusNumber(bonusNumber); + validateBonusNotInWinningNumbers(numbers, bonusNumber); + } + + private void validateSize(List numbers) { + if (numbers.size() != SIZE) { + throw new IllegalArgumentException("당첨 번호는 6개의 숫자로 입력해야 합니다."); + } + } + + private void validateDuplicates(List numbers) { + if (numbers.stream().distinct().count() != SIZE) { + throw new IllegalArgumentException("중복된 숫자가 있습니다."); + } + } + + private void validateNumberRange(List numbers) { + if (numbers.stream().anyMatch(n -> n < MIN_NUMBER || n > MAX_NUMBER)) { + throw new IllegalArgumentException("당첨 번호는 1부터 45 사이여야 합니다."); + } + } + + private void validateBonusNumber(int bonusNumber) { + if (bonusNumber < MIN_NUMBER || bonusNumber > MAX_NUMBER) { + throw new IllegalArgumentException("보너스 볼은 1부터 45 사이여야 합니다."); + } + } + + private void validateBonusNotInWinningNumbers(List numbers, int bonusNumber) { + if (numbers.contains(bonusNumber)) { + throw new IllegalArgumentException("보너스 볼은 당첨 번호와 중복될 수 없습니다."); + } + } + + + public List getNumbers() { + return numbers; + } + + public int getBonusNumber() { + return bonusNumber; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 539b51e2..8138634c 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,6 +1,9 @@ package view; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; +import java.util.stream.Collectors; public class InputView { @@ -9,8 +12,48 @@ public class InputView { public static int getPurchaseAmount(){ System.out.println("구입금액을 입력해 주세요."); int purchaseAmount = scanner.nextInt(); - scanner.close(); + scanner.nextLine(); return purchaseAmount; } + + public static int getManualTicketCount() { + System.out.println("\n수동으로 구매할 로또 수를 입력해 주세요."); + return scanner.nextInt(); + } + + public static List> getManualNumbers(int count) { + System.out.println("\n수동으로 구매할 번호를 입력해 주세요."); + scanner.nextLine(); // 개행 문자 처리 + List> manualNumbers = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String input = scanner.nextLine(); + List numbers = parseNumbers(input); + manualNumbers.add(numbers); + } + return manualNumbers; + } + + public static List getWinningNumbers() { + System.out.println("\n지난 주 당첨 번호를 입력해 주세요."); + String input = scanner.nextLine(); + return parseNumbers(input); + } + + private static List parseNumbers(String input) { + return List.of(input.split(",")).stream() + .map(String::trim) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + public static int getBonusNumber() { + System.out.println("\n보너스 볼을 입력해 주세요."); + + return scanner.nextInt(); + } + + public static void closeScanner() { + scanner.close(); + } } diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java index b67592c2..9e756319 100644 --- a/src/main/java/view/ResultView.java +++ b/src/main/java/view/ResultView.java @@ -1,16 +1,50 @@ package view; + import java.util.List; +import java.util.Map; public class ResultView { - public static void printOrderTickets(int ticketCount){ - System.out.println(); - System.out.println(ticketCount + "개를 구매했습니다."); + public static void printOrderTickets(int manualCount, int autoCount) { + System.out.printf("\n수동으로 %d장, 자동으로 %d개를 구매했습니다.%n", manualCount, autoCount); } - public static void printPurchasedLottoTickets(List formattedTickets){ - for (String ticket : formattedTickets) { + public static void printPurchasedLottoTickets(List> lottoTickets) { + for (List ticket : lottoTickets) { System.out.println(ticket); } } + + public static void printWinningStatistics(Map winningDetails, double profitRate) { + System.out.println("\n당첨 통계"); + System.out.println("---------"); + + printWinningDetails(winningDetails); + printProfitRate(profitRate); + } + + private static void printWinningDetails(Map winningDetails) { + for (Map.Entry entry : winningDetails.entrySet()) { + printRankDetails(entry.getKey(), entry.getValue()); + } + } + + private static void printRankDetails(String rankDescription, int count) { + System.out.printf("%s- %d개%n", rankDescription, count); + } + + private static void printProfitRate(double profitRate) { + String profitResult = getProfitResult(profitRate); + + System.out.printf("총 수익률은 %.2f입니다. (기준이 1이기 때문에 결과적으로 %s)%n", + profitRate, profitResult); + } + + private static String getProfitResult(double profitRate) { + if (profitRate >= 1) { + return "이득이라는 의미임"; + } else { + return "손해라는 의미임"; + } + } } diff --git a/src/test/java/controller/LottoControllerTest.java b/src/test/java/controller/LottoControllerTest.java new file mode 100644 index 00000000..e3ed2a11 --- /dev/null +++ b/src/test/java/controller/LottoControllerTest.java @@ -0,0 +1,84 @@ +package controller; + +import model.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class LottoControllerTest { + + private LottoController lottoController; + + @BeforeEach + void setUp() { + lottoController = new LottoController(); + } + + @Test + @DisplayName("구매 금액이 1000원 단위일 때 LottoPurchaseInfo가 정상 생성되어야 한다.") + void validPurchaseAmountShouldCreateLottoPurchaseInfo() { + int validAmount = 5000; + + LottoPurchaseInfo purchaseInfo = new LottoPurchaseInfo(validAmount); + + assertEquals(validAmount, purchaseInfo.getAmount(), "구매 금액이 일치하지 않습니다."); + assertEquals(5, purchaseInfo.getTicketCount(), "구매 가능한 티켓 수가 올바르지 않습니다."); + } + + @Test + @DisplayName("수동 로또 개수가 구매 가능한 개수를 초과하면 예외 발생") + void validateManualCountThrowsException() { + LottoPurchaseInfo purchaseInfo = new LottoPurchaseInfo(5000); + int manualCount = 6; + + assertThrows(IllegalArgumentException.class, () -> + lottoController.validateManualCount(manualCount, purchaseInfo), + "수동 구매 개수가 초과했는데 예외가 발생하지 않았습니다." + ); + } + + @Test + @DisplayName("수동 및 자동 로또가 정상적으로 생성되고 합쳐져야 한다.") + void generateLottoTicketsShouldWorkCorrectly() { + LottoPurchaseInfo purchaseInfo = new LottoPurchaseInfo(5000); + int manualCount = 2; + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(7, 8, 9, 10, 11, 12) + ); + + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(purchaseInfo.getTicketCount() - manualCount); + LottoTickets lottoTickets = LottoTickets.merge(manualTickets, autoTickets); + + assertEquals(5, lottoTickets.getTickets().size(), "전체 로또 티켓 개수가 올바르지 않습니다."); + assertEquals(manualNumbers, lottoTickets.getFormattedTicketNumbers().subList(0, 2), + "수동 로또 번호가 일치하지 않습니다."); + } + + @Test + @DisplayName("당첨 결과가 정상적으로 계산되어야 한다.") + void processWinningResultsShouldCalculateCorrectly() { + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6) + ); + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(2); + LottoTickets lottoTickets = LottoTickets.merge(manualTickets, autoTickets); + + WinningNumbers winningNumbers = new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5, 6), 7); + LottoPurchaseInfo purchaseInfo = new LottoPurchaseInfo(5000); + + LottoResult lottoResult = new LottoResult(lottoTickets.getTickets(), winningNumbers); + + assertEquals(1, lottoResult.getMatchCountMap().getOrDefault(Rank.FIRST, 0), + "6개 일치(1등) 당첨 개수가 맞지 않습니다."); + assertTrue(lottoResult.calculateProfitRate(purchaseInfo.getAmount()) > 1, + "수익률이 올바르게 계산되지 않았습니다."); + } +} diff --git a/src/test/java/model/LottoPurchaseInfoTest.java b/src/test/java/model/LottoPurchaseInfoTest.java new file mode 100644 index 00000000..f266b62f --- /dev/null +++ b/src/test/java/model/LottoPurchaseInfoTest.java @@ -0,0 +1,40 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LottoPurchaseInfoTest { + + @Test + @DisplayName("LottoPurchaseInfo 객체가 정상적으로 생성되고 금액을 반환할 수 있다.") + void createMoneyAndGetAmount() { + int amount = 5000; + + LottoPurchaseInfo lottoPurchaseInfo = new LottoPurchaseInfo(amount); + + assertEquals(amount, lottoPurchaseInfo.getAmount(), "금액이 올바르게 저장되지 않았습니다."); + } + + @Test + @DisplayName("로또 구매 가능 개수를 정확히 반환한다.") + void getCorrectTicketCount() { + LottoPurchaseInfo lottoPurchaseInfo = new LottoPurchaseInfo(5000); + + int ticketCount = lottoPurchaseInfo.getTicketCount(); + + assertEquals(5, ticketCount, "로또 티켓 개수가 잘못 계산되었습니다."); + } + + @Test + @DisplayName("구매 금액이 1000원 단위가 아니거나 1000원 미만인 경우 예외가 발생한다.") + void validatePurchaseAmountTest() { + assertThrows(IllegalArgumentException.class, () -> new LottoPurchaseInfo(900), + "1000원 미만일 때 예외가 발생해야 합니다."); + assertThrows(IllegalArgumentException.class, () -> new LottoPurchaseInfo(1500), + "1000원 단위가 아닐 때 예외가 발생해야 합니다."); + assertThrows(IllegalArgumentException.class, () -> new LottoPurchaseInfo(0), + "0원일 때 예외가 발생해야 합니다."); + } +} diff --git a/src/test/java/model/LottoTest.java b/src/test/java/model/LottoTest.java new file mode 100644 index 00000000..a2e5d8a7 --- /dev/null +++ b/src/test/java/model/LottoTest.java @@ -0,0 +1,121 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class LottoTest { + + @Test + @DisplayName("로또 번호가 6개보다 작거나 많으면 예외가 발생한다.") + void invalidSizeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5)) // 5개 → 예외 발생 + ); + + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 6, 7)) // 7개 → 예외 발생 + ); + } + + @Test + @DisplayName("로또 번호가 중복되면 예외가 발생한다.") + void duplicateNumbersThrowException() { + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 5)) // 중복된 숫자 → 예외 발생 + ); + } + + @Test + @DisplayName("로또 번호가 1~45 사이가 아니면 예외 발생") + void outOfRangeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(0, 1, 2, 3, 4, 5)) // 0 포함 → 예외 발생 + ); + + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 46)) // 46 포함 → 예외 발생 + ); + } + + @Test + @DisplayName("자동으로 로또 번호를 생성할 수 있다.") + void createAutoLottoNumbers() { + LottoNumbers autoLottoNumbers = new LottoNumbers(); // 자동 생성 + assertNotNull(autoLottoNumbers, "자동 생성된 로또 번호가 null입니다."); + assertEquals(6, autoLottoNumbers.getNumbers().size(), "자동 생성된 로또 번호 개수가 6개가 아닙니다."); + } + + @Test + @DisplayName("수동으로 로또 번호를 생성할 수 있다.") + void createManualLottoNumbers() { + List numbers = Arrays.asList(1, 2, 3, 4, 5, 6); + LottoNumbers manualLottoNumbers = new LottoNumbers(numbers); // 수동 생성 + assertNotNull(manualLottoNumbers, "수동 생성된 로또 번호가 null입니다."); + assertEquals(numbers, manualLottoNumbers.getNumbers(), "수동 생성된 로또 번호가 입력과 다릅니다."); + } + + @Test + @DisplayName("수동으로 생성된 로또 번호는 정렬되어야 한다.") + void manualLottoNumbersShouldBeSorted() { + List manualNumbers = Arrays.asList(6, 3, 1, 5, 4, 2); + LottoNumbers manualLottoNumbers = new LottoNumbers(manualNumbers); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), manualLottoNumbers.getNumbers(), "수동 로또 번호가 정렬되지 않았습니다."); + } + + @Test + @DisplayName("자동으로 생성된 로또 번호는 정렬되어야 한다.") + void autoLottoNumbersShouldBeSorted() { + LottoNumbers autoLottoNumbers = new LottoNumbers(); + List autoNumbers = autoLottoNumbers.getNumbers(); + + assertEquals(autoNumbers, autoNumbers.stream().sorted().toList(), "자동 생성된 로또 번호가 정렬되지 않았습니다."); + } + + @Test + @DisplayName("로또 객체는 LottoNumbers를 포함해야 한다.") + void lottoShouldContainLottoNumbers() { + LottoNumbers lottoNumbers = new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 6)); + Lotto lotto = new Lotto(lottoNumbers); + + assertEquals(lottoNumbers.getNumbers(), lotto.getNumbers(), "Lotto 객체의 번호가 LottoNumbers와 다릅니다."); + } + + @Test + @DisplayName("로또 티켓을 수동으로 여러 장 생성할 수 있다.") + void createManualLottoTickets() { + List> manualNumbers = List.of( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(10, 11, 12, 13, 14, 15) + ); + LottoTickets manualTickets = new LottoTickets(manualNumbers); // 수동 티켓 생성 + + assertEquals(2, manualTickets.getTickets().size(), "수동 로또 티켓 개수가 맞지 않습니다."); + } + + @Test + @DisplayName("로또 티켓을 자동으로 여러 장 생성할 수 있다.") + void createAutoLottoTickets() { + LottoTickets autoTickets = new LottoTickets(3); // 자동 티켓 3장 생성 + + assertEquals(3, autoTickets.getTickets().size(), "자동 로또 티켓 개수가 맞지 않습니다."); + } + + @Test + @DisplayName("수동과 자동 티켓을 합칠 수 있다.") + void mergeManualAndAutoLottoTickets() { + List> manualNumbers = List.of( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(10, 11, 12, 13, 14, 15) + ); + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(2); + + LottoTickets mergedTickets = LottoTickets.merge(manualTickets, autoTickets); + assertEquals(4, mergedTickets.getTickets().size(), "수동 + 자동 로또 티켓 개수가 맞지 않습니다."); + } +} diff --git a/src/test/java/model/LottoTicketsTest.java b/src/test/java/model/LottoTicketsTest.java new file mode 100644 index 00000000..5d8edab9 --- /dev/null +++ b/src/test/java/model/LottoTicketsTest.java @@ -0,0 +1,67 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class LottoTicketsTest { + + @Test + @DisplayName("수동 로또 티켓이 정상적으로 생성되어야 한다.") + void createManualTickets() { + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(7, 8, 9, 10, 11, 12) + ); + + LottoTickets manualTickets = new LottoTickets(manualNumbers); + + assertEquals(2, manualTickets.getTickets().size(), "수동 로또 티켓 개수가 일치하지 않습니다."); + assertEquals(manualNumbers, manualTickets.getFormattedTicketNumbers(), "수동 로또 번호가 일치하지 않습니다."); + } + + @Test + @DisplayName("자동 로또 티켓이 정상적으로 생성되어야 한다.") + void createAutoTickets() { + int autoTicketCount = 3; + + LottoTickets autoTickets = new LottoTickets(autoTicketCount); + + assertEquals(autoTicketCount, autoTickets.getTickets().size(), "자동 로또 티켓 개수가 일치하지 않습니다."); + autoTickets.getTickets().forEach(ticket -> + assertEquals(6, ticket.getNumbers().size(), "자동 생성된 로또 번호 개수가 6개가 아닙니다.") + ); + } + + @Test + @DisplayName("수동 + 자동 로또 티켓이 정상적으로 합쳐져야 한다.") + void mergeTickets() { + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6) + ); + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(2); + + LottoTickets mergedTickets = LottoTickets.merge(manualTickets, autoTickets); + + assertEquals(3, mergedTickets.getTickets().size(), "병합된 티켓 개수가 맞지 않습니다."); + } + + @Test + @DisplayName("LottoTickets는 불변 객체여야 한다.") + void ticketsShouldBeImmutable() { + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6) + ); + LottoTickets lottoTickets = new LottoTickets(manualNumbers); + + manualNumbers.get(0).set(0, 99); + + assertNotEquals(99, lottoTickets.getTickets().get(0).getNumbers().get(0), + "LottoTickets 내부 데이터가 변경되었습니다. 불변성이 깨졌습니다."); + } +} diff --git a/src/test/java/model/WinningNumbersTest.java b/src/test/java/model/WinningNumbersTest.java new file mode 100644 index 00000000..a1c4c706 --- /dev/null +++ b/src/test/java/model/WinningNumbersTest.java @@ -0,0 +1,79 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class WinningNumbersTest { + + @Test + @DisplayName("당첨 번호와 보너스 번호가 정상적으로 생성된다.") + void createWinningNumbers() { + List numbers = Arrays.asList(1, 2, 3, 4, 5, 6); + int bonusNumber = 7; + + WinningNumbers winningNumbers = new WinningNumbers(numbers, bonusNumber); + + assertEquals(numbers, winningNumbers.getNumbers(), "당첨 번호가 올바르게 저장되지 않았습니다."); + assertEquals(bonusNumber, winningNumbers.getBonusNumber(), "보너스 번호가 올바르게 저장되지 않았습니다."); + } + + @Test + @DisplayName("당첨 번호가 6개가 아니면 예외가 발생한다.") + void invalidSizeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5), 6), + "당첨 번호가 6개가 아닐 때 예외가 발생해야 합니다." + ); + } + + @Test + @DisplayName("당첨 번호에 중복된 숫자가 있으면 예외가 발생한다.") + void duplicateNumbersThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5, 5), 6), + "중복된 숫자가 있을 때 예외가 발생해야 합니다." + ); + } + + @Test + @DisplayName("당첨 번호가 1~45 범위를 벗어나면 예외가 발생한다.") + void outOfRangeNumbersThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(0, 1, 2, 3, 4, 5), 6), + "1~45 범위를 벗어난 숫자가 있을 때 예외가 발생해야 합니다." + ); + + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5, 46), 6), + "1~45 범위를 벗어난 숫자가 있을 때 예외가 발생해야 합니다." + ); + } + + @Test + @DisplayName("보너스 번호가 1~45 범위를 벗어나면 예외가 발생한다.") + void outOfRangeBonusThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5, 6), 0), + "보너스 번호가 1~45 범위를 벗어났을 때 예외가 발생해야 합니다." + ); + + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5, 6), 46), + "보너스 번호가 1~45 범위를 벗어났을 때 예외가 발생해야 합니다." + ); + } + + @Test + @DisplayName("보너스 번호가 당첨 번호와 중복되면 예외가 발생한다.") + void duplicateBonusNumberThrowsException() { + assertThrows(IllegalArgumentException.class, () -> + new WinningNumbers(Arrays.asList(1, 2, 3, 4, 5, 6), 6), + "보너스 번호가 당첨 번호와 중복될 때 예외가 발생해야 합니다." + ); + } +}