diff --git a/src/main/java/lotto/LottoGameApplication.java b/src/main/java/lotto/LottoGameApplication.java index 7fc738d13a..a3c223c332 100644 --- a/src/main/java/lotto/LottoGameApplication.java +++ b/src/main/java/lotto/LottoGameApplication.java @@ -1,8 +1,8 @@ package lotto; -import lotto.domain.game.LottoGame; +import java.util.List; import lotto.domain.game.LottoMachine; -import lotto.domain.lotto.Money; +import lotto.domain.lotto.LottoTickets; import lotto.domain.lotto.Purchase; import lotto.domain.lotto.WinningTicket; import lotto.domain.result.GameResult; @@ -15,16 +15,20 @@ public static void main(String[] args) { LottoMachine machine = new LottoMachine(); int purchaseAmount = InputView.inputPurchaseAmount(); - Purchase purchase = machine.purchase(new Money(purchaseAmount)); + int manualCount = InputView.inputManualLottoCount(); + List manualTickets = InputView.inputManualLottoTickets(manualCount); + + Purchase purchase = machine.createPurchase(purchaseAmount, manualCount); ResultView.printPurchase(purchase); + LottoTickets tickets = machine.generateTickets(manualTickets, purchase.getAutoCount()); + ResultView.printTickets(tickets); + String winningLottoNumbers = InputView.inputWinningLottoNumbers(); int bonusNumber = InputView.inputBonusNumber(); WinningTicket winning = new WinningTicket(winningLottoNumbers, bonusNumber); - LottoGame game = new LottoGame(winning); - GameResult gameResult = game.check(purchase); - - ResultView.printResult(gameResult, purchase.getPurchaseAmount()); + GameResult result = tickets.match(winning); + ResultView.printResult(result, purchase); } -} +} \ No newline at end of file diff --git a/src/main/java/lotto/README.md b/src/main/java/lotto/README.md index bf8997404e..6fb270cc62 100644 --- a/src/main/java/lotto/README.md +++ b/src/main/java/lotto/README.md @@ -1,11 +1,13 @@ # 기능 요구사항 -> 입력한 금액만큼 로또 자동으로 발급해주는 기능 +> 입력한 금액만큼 로또를 수동/자동으로 발급해주는 기능 1. 구입 금액 입력 기능 -2. N개의 로또 구매 기능 (자동) -3. 당첨 번호 입력 기능 (당첨 번호 6개 + 보너스볼 1개) -4. 당첨 통계 조회 기능 +2. 수동 구매 로또 개수 입력 기능 +3. 수동 로또 번호 입력 기능 +4. N개의 로또 구매 기능 (수동 + 자동) +5. 당첨 번호 입력 기능 (당첨 번호 6개 + 보너스볼 1개) +6. 당첨 통계 조회 기능 - 몇 개의 번호가 일치하는지, 몇 개인지 - 보너스볼 일치 여부 (5개 일치 시) - 수익률 @@ -17,37 +19,65 @@ - 무엇인가? : 구매금에 따라 로또를 생성해주는 머신 - 역할 - 로또 발급 개수 계산 - - 랜덤 로또 티켓 생성 + - 수동 로또 티켓 변환 + - 자동 로또 티켓 생성 + - 구매 정보(Purchase) 생성 + +## 로또 티켓들 (LottoTickets) + +- 무엇인가? : 여러 로또 티켓을 관리하는 일급 컬렉션 +- 역할 + - 로또 티켓 목록 보관 + - 당첨 티켓과 비교하여 게임 결과 생성 ## 로또 (LottoTicket) - 무엇인가? : 6개의 고유한 숫자를 담은 로또 티켓 - 역할 - 6개의 숫자 보관 - - 일치 갯수 비교 + - 문자열/숫자 배열로부터 티켓 생성 + - 일치 개수 비교 + - 특정 번호 포함 여부 확인 ## 로또 숫자 (LottoNumber) - 무엇인가? : 로또 내 숫자 - 역할 - 번호 유효성 보장 (1 ~ 45) + - 캐싱을 통한 객체 재사용 - 숫자 객체 비교 -## 금액 (Money) - -- 무엇인가? : 금액을 표현하는 일급 컬렉션 -- 역할 - - 금액 유효성 보장 (0 이상) - - 금액 연산 (덧셈, 곱셈, 나눗셈) - - BigDecimal을 사용한 정확한 금액 계산 - ## 구매 (Purchase) -- 무엇인가? : 로또 구매 정보를 담은 객체 +- 무엇인가? : 로또 구매 정보와 금액 계산을 담당하는 객체 - 역할 - - 구매한 로또 티켓 목록 관리 - - 티켓 개수 조회 + - 수동/자동 구매 개수 관리 - 총 구매 금액 계산 + - 수동 구매 수 유효성 검증 + - 수익률 계산 + +### 설계 결정: Money를 Purchase에 통합한 이유 + +기존에는 `Money`(금액 VO)와 `Purchase`(구매 정보)가 분리되어 있었다. + +``` +Money: "나는 금액이야. 더하고, 빼고, 비교할 수 있어" +Purchase: "나는 Money를 갖고 있어. Money에게 물어보고 판단해" +``` + +**문제점:** +- Money가 수동적인 값 객체로만 존재 +- Purchase가 Money에게 물어보고 판단하는 구조 +- "수익률"은 "금액의 비율"이 아니라 "구매의 결과" + +**개선:** +- Purchase가 금액 관련 로직을 직접 담당 (능동적 객체) +- "로또 구매"라는 도메인 맥락에서 수익률 계산이 자연스러움 +- int 타입으로 단순화 (BigDecimal wrapper 불필요) + +``` +Purchase: "나는 로또 구매야. 얼마 썼고, 몇 장 샀는지 알아. 수익률도 계산해줄게" +``` ## 당첨 티켓 (WinningTicket) @@ -55,15 +85,7 @@ - 역할 - 당첨 번호 6개 보관 - 보너스볼 보관 - - 구매 티켓의 보너스볼 포함 여부 확인 - -## 로또 게임 (LottoGame) - -- 무엇인가? : 당첨 번호와 구매한 티켓들을 비교하여 결과를 생성하는 객체 -- 역할 - - 당첨 번호 보관 - - 구매한 티켓들의 당첨 결과 계산 - - 게임 결과 생성 + - 구매 티켓과 비교하여 등수(Rank) 반환 ## 당첨 등수 (Rank) @@ -72,15 +94,42 @@ - 일치 개수와 보너스볼 여부에 따른 등수 판정 - 등수별 당첨금 관리 - 5개 일치 시 보너스볼로 2등/3등 구분 + - 당첨 개수에 따른 총 상금 계산 + +### 설계 결정: 상금 계산 로직을 Rank 내부에 캡슐화한 이유 + +기존에는 GameResult가 Rank에게 prize를 물어보고 직접 계산했다. + +```java +// 기존: GameResult에서 계산 (Ask) +entry.getKey().getPrize() * entry.getValue() +``` + +**문제점:** +- Rank가 prize 값만 제공하는 수동적 객체 +- "상금 × 개수" 계산 로직이 GameResult에 노출 + +**개선:** +- Rank가 직접 상금을 계산하도록 변경 (Tell, Don't Ask) + +```java +// 개선: Rank에게 계산 요청 (Tell) +entry.getKey().calculatePrize(entry.getValue()) +``` + +- Rank가 자신의 prize를 알고 있으므로, 계산도 Rank가 담당하는 것이 자연스러움 +- GameResult는 "얼마인지 계산해줘"라고 요청만 함 ## 로또 게임 결과 (GameResult) -- 무엇인가? : 전체 로또 구매 결과 (당첨 통계, 수익률)를 나타내는 객체 +- 무엇인가? : 전체 로또 구매 결과 (당첨 통계)를 나타내는 객체 - 역할 - 등수별 당첨 통계 관리 - - 수익률 계산 (당첨 금액 / 구입 금액) - - 손익 판정 (기준: 1.0) - - + - 총 당첨 금액 계산 +## 랜덤 번호 생성기 (Random) +- 무엇인가? : 로또 번호를 랜덤으로 생성하는 유틸리티 +- 역할 + - 1~45 범위에서 중복 없이 6개 번호 생성 + - 정렬된 LottoNumber 리스트 반환 diff --git a/src/main/java/lotto/domain/game/LottoGame.java b/src/main/java/lotto/domain/game/LottoGame.java deleted file mode 100644 index dc9c7909a4..0000000000 --- a/src/main/java/lotto/domain/game/LottoGame.java +++ /dev/null @@ -1,23 +0,0 @@ -package lotto.domain.game; - -import java.util.List; -import lotto.domain.lotto.LottoTicket; -import lotto.domain.lotto.LottoTickets; -import lotto.domain.lotto.Purchase; -import lotto.domain.lotto.WinningTicket; -import lotto.domain.result.GameResult; - -public class LottoGame { - - private final WinningTicket winningTicket; - - public LottoGame(WinningTicket winningTicket) { - this.winningTicket = winningTicket; - } - - public GameResult check(Purchase purchase) { - LottoTickets tickets = purchase.getTickets(); - return tickets.updateRank(winningTicket); - } - -} \ No newline at end of file diff --git a/src/main/java/lotto/domain/game/LottoMachine.java b/src/main/java/lotto/domain/game/LottoMachine.java index 294b4bab33..c103e1cfa4 100644 --- a/src/main/java/lotto/domain/game/LottoMachine.java +++ b/src/main/java/lotto/domain/game/LottoMachine.java @@ -1,33 +1,47 @@ package lotto.domain.game; +import java.util.ArrayList; +import java.util.List; +import lotto.domain.lotto.LottoTicket; import lotto.domain.lotto.LottoTickets; -import lotto.domain.lotto.Money; import lotto.domain.lotto.Purchase; public class LottoMachine { - private static final Money DEFAULT_LOTTO_TICKET_PRICE = new Money(1_000); + private static final int DEFAULT_LOTTO_TICKET_PRICE = 1_000; - private final Money lottoTicketPrice; + private final int lottoTicketPrice; public LottoMachine() { this(DEFAULT_LOTTO_TICKET_PRICE); } - public LottoMachine(Money lottoTicketPrice) { + public LottoMachine(int lottoTicketPrice) { this.lottoTicketPrice = lottoTicketPrice; } - public Purchase purchase(Money money) { - return new Purchase(lottoTicketPrice, generateTickets(calculate(money))); + public Purchase createPurchase(int budget, int manualCount) { + return new Purchase(budget, lottoTicketPrice, manualCount); } - private int calculate(Money money) { - return money.divideForCount(lottoTicketPrice); + public LottoTickets generateTickets(List manualTickets, int autoCount) { + List manual = convert(manualTickets); + List auto = generate(autoCount); + + List allTickets = new ArrayList<>(manual); + allTickets.addAll(auto); + return new LottoTickets(allTickets); } - LottoTickets generateTickets(int cnt) { - return new LottoTickets(cnt); + private List convert(List tickets) { + return tickets.stream().map(LottoTicket::new).toList(); } + private List generate(int count) { + List tickets = new ArrayList<>(); + for (int i = 0; i < count; i++) { + tickets.add(new LottoTicket(Random.generate())); + } + return tickets; + } } \ No newline at end of file diff --git a/src/main/java/lotto/domain/game/Rank.java b/src/main/java/lotto/domain/game/Rank.java index ba091122a1..bc92b553a6 100644 --- a/src/main/java/lotto/domain/game/Rank.java +++ b/src/main/java/lotto/domain/game/Rank.java @@ -32,8 +32,8 @@ public static Rank valueOf(int matched, boolean bonusMatched) { .orElse(NONE); } - public int getPrize() { - return prize; + public int calculatePrize(int count) { + return prize * count; } private static boolean isMatch(Rank rank, int matched, diff --git a/src/main/java/lotto/domain/lotto/LottoTickets.java b/src/main/java/lotto/domain/lotto/LottoTickets.java index 050742da1d..4a7afc17b8 100644 --- a/src/main/java/lotto/domain/lotto/LottoTickets.java +++ b/src/main/java/lotto/domain/lotto/LottoTickets.java @@ -1,47 +1,28 @@ package lotto.domain.lotto; -import java.util.ArrayList; import java.util.List; -import lotto.domain.game.Random; import lotto.domain.result.GameResult; public class LottoTickets { private final List tickets; - public LottoTickets(int cnt) { - this(generate(cnt)); - } - public LottoTickets(List tickets){ this.tickets = tickets; } - private static List generate(int cnt){ - List tickets = new ArrayList<>(); - for (int i = 0; i < cnt; i++) { - tickets.add(new LottoTicket(Random.generate())); - } - return tickets; - } - - public Money calculate(Money pricePerTicket) { - return pricePerTicket.multiply(tickets.size()); - } - - public GameResult updateRank(WinningTicket winningTicket) { + public GameResult match(WinningTicket winning) { GameResult gameResult = new GameResult(); for(LottoTicket ticket : tickets){ - gameResult.updateRank(winningTicket.match(ticket)); + gameResult.updateRank(winning.match(ticket)); } return gameResult; } @Override - public String toString(){ + public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(tickets.size()+"개를 구매했습니다."); - for (int i = 0; i < tickets.size(); i++) { - sb.append(tickets.get(i) + "\n"); + for (LottoTicket ticket : tickets) { + sb.append(ticket).append("\n"); } return sb.toString(); } diff --git a/src/main/java/lotto/domain/lotto/Money.java b/src/main/java/lotto/domain/lotto/Money.java deleted file mode 100644 index 32b97582fc..0000000000 --- a/src/main/java/lotto/domain/lotto/Money.java +++ /dev/null @@ -1,68 +0,0 @@ -package lotto.domain.lotto; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Objects; - -public class Money { - - private final BigDecimal amount; - - public Money(int amount) { - validate(amount); - this.amount = new BigDecimal(amount); - } - - private Money(BigDecimal amount) { - this.amount = amount; - } - - private void validate(int amount) { - if (amount < 0) { - throw new IllegalArgumentException("금액은 0이상이어야 합니다"); - } - } - - public Money add(Money money) { - return new Money(this.amount.add(money.amount)); - } - - public Money multiply(int multiplier) { - return new Money(this.amount.multiply(BigDecimal.valueOf(multiplier))); - } - - public BigDecimal divideBy(Money divisor) { - validateDivisor(divisor); - return this.amount.divide(divisor.amount, 2, RoundingMode.HALF_UP); - } - - public int divideForCount(Money divisor) { - validateDivisor(divisor); - return this.amount.divide(divisor.amount, 0, RoundingMode.DOWN).intValue(); - } - - private void validateDivisor(Money divisor) { - if (divisor.amount.compareTo(BigDecimal.ZERO) <= 0) { - throw new IllegalArgumentException("몫은 0보다 커야 합니다"); - } - } - - @Override - public String toString() { - return this.amount.toString(); - } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - Money money = (Money) o; - return Objects.equals(amount, money.amount); - } - - @Override - public int hashCode() { - return Objects.hashCode(amount); - } -} diff --git a/src/main/java/lotto/domain/lotto/Purchase.java b/src/main/java/lotto/domain/lotto/Purchase.java index 0d6632b362..8c99560fab 100644 --- a/src/main/java/lotto/domain/lotto/Purchase.java +++ b/src/main/java/lotto/domain/lotto/Purchase.java @@ -1,25 +1,66 @@ package lotto.domain.lotto; +import java.math.BigDecimal; +import java.math.RoundingMode; + public class Purchase { - private final Money pricePerTicket; - private final LottoTickets tickets; + private final int ticketPrice; + private final int manualCount; + private final int autoCount; + + public Purchase(int budget, int ticketPrice, int manualCount) { + validatePositive(budget, "예산"); + validatePositive(ticketPrice, "티켓 가격"); + validateBudget(budget, ticketPrice); + int totalCount = budget / ticketPrice; + validateManualCount(manualCount, totalCount); + this.ticketPrice = ticketPrice; + this.manualCount = manualCount; + this.autoCount = totalCount - manualCount; + } + + private static void validatePositive(int amount, String name) { + if (amount < 0) { + throw new IllegalArgumentException(name + "은 0 이상이어야 합니다"); + } + } + + private static void validateBudget(int budget, int ticketPrice) { + if (budget < ticketPrice) { + throw new IllegalArgumentException("예산이 티켓 가격보다 적습니다"); + } + } + + private static void validateManualCount(int manualCount, int totalCount) { + if (manualCount > totalCount) { + throw new IllegalArgumentException("수동 구매 수가 총 구매 가능 수를 초과합니다"); + } + } + + public BigDecimal calculateProfitRate(int totalPrize) { + int spent = getSpentAmount(); + if (spent == 0) { + throw new IllegalArgumentException("지출 금액이 0입니다"); + } + return BigDecimal.valueOf(totalPrize) + .divide(BigDecimal.valueOf(spent), 2, RoundingMode.HALF_UP); + } - public Purchase(Money pricePerTicket, LottoTickets tickets) { - this.pricePerTicket = pricePerTicket; - this.tickets = tickets; + public int getSpentAmount() { + return ticketPrice * getTotalCount(); } - public LottoTickets getTickets(){ - return this.tickets; + public int getTotalCount() { + return manualCount + autoCount; } - public Money getPurchaseAmount() { - return tickets.calculate(this.pricePerTicket); + public int getAutoCount() { + return autoCount; } @Override public String toString() { - return tickets.toString(); + return "수동으로 " + manualCount + "장, 자동으로 " + autoCount + "개를 구매했습니다."; } } \ No newline at end of file diff --git a/src/main/java/lotto/domain/lotto/WinningTicket.java b/src/main/java/lotto/domain/lotto/WinningTicket.java index 842936d474..b72ef39b1a 100644 --- a/src/main/java/lotto/domain/lotto/WinningTicket.java +++ b/src/main/java/lotto/domain/lotto/WinningTicket.java @@ -11,10 +11,14 @@ public WinningTicket(String numbers, int bonus){ } public WinningTicket(LottoTicket ticket, LottoNumber bonus) { + validate(ticket, bonus); this.ticket = ticket; this.bonus = bonus; } + private static void validate(LottoTicket ticket, LottoNumber bonus){ + if(ticket.contains(bonus))throw new IllegalArgumentException("당첨 번호와 중복됩니다."); + } public Rank match(LottoTicket ticket) { return Rank.valueOf(this.ticket.matchCount(ticket), ticket.contains(this.bonus)); } diff --git a/src/main/java/lotto/domain/result/GameResult.java b/src/main/java/lotto/domain/result/GameResult.java index a9fbeec5fe..36debf9af7 100644 --- a/src/main/java/lotto/domain/result/GameResult.java +++ b/src/main/java/lotto/domain/result/GameResult.java @@ -1,12 +1,11 @@ package lotto.domain.result; -import java.math.BigDecimal; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import lotto.domain.game.Rank; -import lotto.domain.lotto.Money; public class GameResult { @@ -20,44 +19,22 @@ public GameResult(Map ranks) { this.ranks = ranks; } - public void updateRank(Rank rank){ - ranks.put(rank, ranks.getOrDefault(rank, 0) + 1); + public void updateRank(Rank rank) { + ranks.put(rank, ranks.getOrDefault(rank, 0) + 1); } - public String getProfitMessage(Money purchaseAmount) { - int totalPrize = ranks.entrySet().stream() - .mapToInt(entry -> entry.getKey().getPrize() * entry.getValue()) + public int getTotalPrize() { + return ranks.entrySet().stream() + .mapToInt(entry -> entry.getKey().calculatePrize(entry.getValue())) .sum(); - BigDecimal profit = new Money(totalPrize).divideBy(purchaseAmount); - String explanation = Explanation.getMessage(profit); - return String.format("총 수익률은 %s입니다.(기준이 1이기 때문에 결과적으로 %s라는 의미임)", - profit, explanation); } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(Arrays.stream(Rank.values()) + return Arrays.stream(Rank.values()) .filter(x -> x != Rank.NONE) - .map( - e -> String.format("%s - %d개", e, ranks.getOrDefault(e, 0)) - ).collect(Collectors.joining("\n"))); - return sb.toString(); - } - - enum Explanation { - PROFIT("이익"), LOSS("손해"); - private final String message; - - Explanation(String message) { - this.message = message; - } - - static String getMessage(BigDecimal value) { - if (value.compareTo(BigDecimal.ONE) >= 0) { - return PROFIT.message; - } - return LOSS.message; - } + .sorted(Comparator.reverseOrder()) + .map(e -> String.format("%s - %d개", e, ranks.getOrDefault(e, 0))) + .collect(Collectors.joining("\n")); } } \ No newline at end of file diff --git a/src/main/java/lotto/ui/InputView.java b/src/main/java/lotto/ui/InputView.java index 84de8918db..d8b2c1f516 100644 --- a/src/main/java/lotto/ui/InputView.java +++ b/src/main/java/lotto/ui/InputView.java @@ -1,5 +1,7 @@ package lotto.ui; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; public class InputView { @@ -21,6 +23,20 @@ public static String inputWinningLottoNumbers() { public static int inputBonusNumber() { System.out.println("보너스 번호를 입력해주세요."); - return scanner.nextInt(); + return Integer.parseInt(scanner.nextLine()); + } + + public static int inputManualLottoCount() { + System.out.println("수동으로 구매할 로또 수를 입력해 주세요."); + return Integer.parseInt(scanner.nextLine()); + } + + public static List inputManualLottoTickets(int manualLottoCount) { + System.out.println("수동으로 구매할 번호를 입력해 주세요."); + List tickets = new ArrayList<>(); + for(int i = 0; i < manualLottoCount; i++){ + tickets.add(scanner.nextLine()); + } + return tickets; } } diff --git a/src/main/java/lotto/ui/ResultView.java b/src/main/java/lotto/ui/ResultView.java index 8e929ac42d..472c3596a1 100644 --- a/src/main/java/lotto/ui/ResultView.java +++ b/src/main/java/lotto/ui/ResultView.java @@ -1,6 +1,7 @@ package lotto.ui; -import lotto.domain.lotto.Money; +import java.math.BigDecimal; +import lotto.domain.lotto.LottoTickets; import lotto.domain.lotto.Purchase; import lotto.domain.result.GameResult; @@ -10,11 +11,19 @@ public static void printPurchase(Purchase purchase) { System.out.println(purchase); } - public static void printResult(GameResult gameResult, Money purchaseAmount) { + public static void printTickets(LottoTickets tickets) { + System.out.println(tickets); + } + public static void printResult(GameResult gameResult, Purchase purchase) { System.out.println("\n당첨 통계\n---------"); System.out.println(gameResult); - System.out.println("총 수익률은 " + gameResult.getProfitMessage(purchaseAmount)); - } -} + int totalPrize = gameResult.getTotalPrize(); + BigDecimal profitRate = purchase.calculateProfitRate(totalPrize); + String explanation = profitRate.compareTo(BigDecimal.ONE) >= 0 ? "이익" : "손해"; + + System.out.printf("총 수익률은 %s입니다.(기준이 1이기 때문에 결과적으로 %s라는 의미임)%n", + profitRate, explanation); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/game/LottoMachineTest.java b/src/test/java/lotto/domain/game/LottoMachineTest.java index f19b0cc226..f39e83026c 100644 --- a/src/test/java/lotto/domain/game/LottoMachineTest.java +++ b/src/test/java/lotto/domain/game/LottoMachineTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import lotto.domain.lotto.Money; import lotto.domain.lotto.Purchase; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -10,10 +9,18 @@ public class LottoMachineTest { @ParameterizedTest - @CsvSource({"10000,10000", "1,0", "2320,2000"}) - void lottoMachinePurchaseNTicketsBasedOnPrice(int pay, int expected) { + @CsvSource({"10000, 3, 7", "1000, 0, 1", "10000, 5, 5"}) + void createPurchaseWithBudgetAndManualCount(int budget, int manualCount, int expectedAutoCount) { LottoMachine machine = new LottoMachine(); - Purchase tickets = machine.purchase(new Money(pay)); - assertThat(tickets.getPurchaseAmount()).isEqualTo(new Money(expected)); + Purchase purchase = machine.createPurchase(budget, manualCount); + assertThat(purchase.getAutoCount()).isEqualTo(expectedAutoCount); + } + + @ParameterizedTest + @CsvSource({"10000, 3, 10000", "5000, 2, 5000", "2320, 0, 2000"}) + void createPurchaseCalculatesSpentAmount(int budget, int manualCount, int expectedSpent) { + LottoMachine machine = new LottoMachine(); + Purchase purchase = machine.createPurchase(budget, manualCount); + assertThat(purchase.getSpentAmount()).isEqualTo(expectedSpent); } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/lotto/LottoTicketsTest.java b/src/test/java/lotto/domain/lotto/LottoTicketsTest.java new file mode 100644 index 0000000000..1734265d29 --- /dev/null +++ b/src/test/java/lotto/domain/lotto/LottoTicketsTest.java @@ -0,0 +1,29 @@ +package lotto.domain.lotto; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; +import lotto.domain.game.Rank; +import lotto.domain.result.GameResult; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LottoTicketsTest { + + @Test + @DisplayName("여러 티켓의 당첨 결과가 올바르게 집계된다") + void match_multiple_tickets() { + LottoTickets tickets = new LottoTickets(List.of( + new LottoTicket("1, 2, 3, 10, 11, 12"), + new LottoTicket("1, 2, 3, 20, 21, 22"), + new LottoTicket("1, 2, 3, 30, 31, 32") + )); + WinningTicket winning = new WinningTicket("1, 2, 3, 4, 5, 6", 7); + + GameResult result = tickets.match(winning); + + GameResult expected = new GameResult(Map.of(Rank.FIFTH, 3)); + assertThat(result.toString()).isEqualTo(expected.toString()); + } +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/lotto/MoneyTest.java b/src/test/java/lotto/domain/lotto/MoneyTest.java deleted file mode 100644 index e6c8cef1fa..0000000000 --- a/src/test/java/lotto/domain/lotto/MoneyTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package lotto.domain.lotto; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Test; - -public class MoneyTest { - @Test - void negativeAmountThrowsException() { - assertThatThrownBy(() -> new Money(-1000)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("금액은 0이상이어야 합니다"); - } - - @Test - void divideByZeroThrowsException() { - Money money = new Money(1000); - assertThatThrownBy(() -> money.divideBy(new Money(0))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("몫은 0보다 커야 합니다"); - } -} diff --git a/src/test/java/lotto/domain/lotto/WinningTicketTest.java b/src/test/java/lotto/domain/lotto/WinningTicketTest.java new file mode 100644 index 0000000000..b15402617c --- /dev/null +++ b/src/test/java/lotto/domain/lotto/WinningTicketTest.java @@ -0,0 +1,47 @@ +package lotto.domain.lotto; + +import static org.assertj.core.api.Assertions.assertThat; + +import lotto.domain.game.Rank; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class WinningTicketTest { + + private WinningTicket winningTicket; + + @BeforeEach + void setUp() { + winningTicket = new WinningTicket("1, 2, 3, 4, 5, 6", 7); + } + + @Test + @DisplayName("5개 일치 + 보너스 일치 시 2등") + void match_5개_보너스_일치_2등() { + LottoTicket ticket = new LottoTicket("1, 2, 3, 4, 5, 7"); + assertThat(winningTicket.match(ticket)).isEqualTo(Rank.SECOND); + } + + @Test + @DisplayName("5개 일치 + 보너스 불일치 시 3등") + void match_5개_보너스_불일치_3등() { + LottoTicket ticket = new LottoTicket("1, 2, 3, 4, 5, 8"); + assertThat(winningTicket.match(ticket)).isEqualTo(Rank.THIRD); + } + + @ParameterizedTest + @CsvSource({ + "1, 2, 3, 4, 5, 6, FIRST", + "1, 2, 3, 4, 8, 9, FOURTH", + "1, 2, 3, 7, 9, 10, FIFTH", + "1, 2, 8, 9, 10, 11, NONE" + }) + void match_3개_일치_5등(int n1, int n2, int n3, int n4, int n5, int n6, Rank rank) { + LottoTicket ticket = new LottoTicket(n1, n2, n3, n4, n5, n6); + assertThat(winningTicket.match(ticket)).isEqualTo(rank); + } + +} \ No newline at end of file diff --git a/src/test/java/lotto/domain/result/GameResultTest.java b/src/test/java/lotto/domain/result/GameResultTest.java index da4efe3c04..1bd95a4399 100644 --- a/src/test/java/lotto/domain/result/GameResultTest.java +++ b/src/test/java/lotto/domain/result/GameResultTest.java @@ -2,11 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import lotto.domain.game.Rank; -import lotto.domain.lotto.Money; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -14,21 +12,21 @@ class GameResultTest { @Test - void testCalculateProfitRate() { + void getTotalPrize_first() { Map details = new HashMap<>(); details.put(Rank.FIRST, 1); GameResult result = new GameResult(details); - Money purchaseAmount = new Money(1000); - assertThat(result.getProfitMessage(purchaseAmount)).contains("2000000.00"); + + assertThat(result.getTotalPrize()).isEqualTo(2_000_000_000); } @ParameterizedTest - @CsvSource({"50000,50000,이익", "100000,50000,손해"}) - void testExplanation(int purchaseAmount, int totalPrize, String value) { + @CsvSource({"1, 50000", "2, 100000", "3, 150000"}) + void getTotalPrize_fourth(int count, int expectedPrize) { Map details = new HashMap<>(); - details.put(Rank.FOURTH, totalPrize / 50000); + details.put(Rank.FOURTH, count); GameResult result = new GameResult(details); - assertThat(result.getProfitMessage(new Money(purchaseAmount))).contains(value); - } + assertThat(result.getTotalPrize()).isEqualTo(expectedPrize); + } } \ No newline at end of file diff --git a/src/test/java/lotto/domain/result/PurchaseTest.java b/src/test/java/lotto/domain/result/PurchaseTest.java index 40b71b269e..f57f021647 100644 --- a/src/test/java/lotto/domain/result/PurchaseTest.java +++ b/src/test/java/lotto/domain/result/PurchaseTest.java @@ -2,13 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.ArrayList; -import java.util.List; -import lotto.domain.lotto.LottoTicket; -import lotto.domain.lotto.LottoTickets; -import lotto.domain.lotto.Money; +import java.math.BigDecimal; import lotto.domain.lotto.Purchase; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -16,23 +11,35 @@ class PurchaseTest { @ParameterizedTest @CsvSource({ - "1000, 1, 1000", - "1000, 5, 5000", - "1000, 10, 10000", - "2000, 3, 6000" + "1000, 1000, 0, 1000", + "5000, 1000, 2, 5000", + "10000, 1000, 5, 10000", + "6000, 2000, 1, 6000" }) - void getPurchaseAmount(int pricePerTicket, int ticketCount, int expectedAmount) { - LottoTickets tickets = createTickets(ticketCount); - Purchase purchase = new Purchase(new Money(pricePerTicket), tickets); - assertThat(purchase.getPurchaseAmount()).isEqualTo(new Money(expectedAmount)); + void getSpentAmount(int budget, int pricePerTicket, int manualCount, int expectedAmount) { + Purchase purchase = new Purchase(budget, pricePerTicket, manualCount); + assertThat(purchase.getSpentAmount()).isEqualTo(expectedAmount); } + @ParameterizedTest + @CsvSource({ + "10000, 1000, 3, 7", + "10000, 1000, 0, 10", + "10000, 1000, 5, 5" + }) + void getAutoCount(int budget, int pricePerTicket, int manualCount, int expectedAutoCount) { + Purchase purchase = new Purchase(budget, pricePerTicket, manualCount); + assertThat(purchase.getAutoCount()).isEqualTo(expectedAutoCount); + } - private LottoTickets createTickets(int count) { - List tickets = new ArrayList<>(); - for (int i = 0; i < count; i++) { - tickets.add(new LottoTicket(1, 2, 3, 4, 5, 6)); - } - return new LottoTickets(tickets); + @ParameterizedTest + @CsvSource({ + "10000, 1000, 0, 50000, 5.00", + "10000, 1000, 5, 5000, 0.50", + "1000, 1000, 0, 2000000000, 2000000.00" + }) + void calculateProfitRate(int budget, int pricePerTicket, int manualCount, int totalPrize, String expectedRate) { + Purchase purchase = new Purchase(budget, pricePerTicket, manualCount); + assertThat(purchase.calculateProfitRate(totalPrize)).isEqualTo(new BigDecimal(expectedRate)); } } \ No newline at end of file