From 5e2fb3049a0c11a52dac2b3384650d130dc58537 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Thu, 20 Feb 2025 04:36:08 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20Lotto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/Lotto.java | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/model/Lotto.java diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java new file mode 100644 index 00000000..07694d77 --- /dev/null +++ b/src/main/java/model/Lotto.java @@ -0,0 +1,54 @@ +package model; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Lotto { + + // 로또 번호 관련 상수 선언 + 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 int LOTTO_PRICE = 1000; + public static final List LOTTO_NUMBER_POOL = + IntStream + .rangeClosed(LOTTO_MIN_NUMBER,LOTTO_MAX_NUMBER) + .boxed() + .collect(Collectors.toList()); + + private List numbers = new ArrayList<>(); + + public static int getTicketCount(int purchaseAmount){ + return purchaseAmount / LOTTO_PRICE; + } + + public Lotto(){ + this.numbers = createLottoNumbers(); + } + + private List createLottoNumbers(){ + List shuffledNumbers = new ArrayList<>(LOTTO_NUMBER_POOL); + Collections.shuffle(shuffledNumbers); + numbers = shuffledNumbers.subList(0, LOTTO_CREATE_SIZE); + + return numbers; + } + + public static List generateLottoTickets(int ticketCount){ + List tickets = new ArrayList<>(); + + for (int i = 0; i < ticketCount; i++){ + tickets.add(new Lotto()); + } + + return tickets; + } + + public List getNumbers() { + List sortedNumbers = new ArrayList<>(numbers); + Collections.sort(sortedNumbers); + + return sortedNumbers; + } +} From f9f5a87cdbde5192b11b133fbdfdd3098b4f6a43 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Thu, 20 Feb 2025 04:37:17 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20controller=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/LottoController.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/java/controller/LottoController.java diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java new file mode 100644 index 00000000..c6633cc1 --- /dev/null +++ b/src/main/java/controller/LottoController.java @@ -0,0 +1,34 @@ +package controller; + +import model.Lotto; +import view.InputView; +import view.ResultView; + +import java.util.List; +import java.util.stream.Collectors; + +public class LottoController { + + public void run() { + int purchaseAmount = InputView.getPurchaseAmount(); + + int ticketCount = Lotto.getTicketCount(purchaseAmount); + + List tickets = Lotto.generateLottoTickets(ticketCount); + + ResultView.printOrderTickets(ticketCount); + ResultView.printTickets(ticketCount, formatTickets(tickets)); + } + + public List formatTickets(List tickets) { + List formattedTickets = tickets.stream() + .map(lotto -> lotto.getNumbers() + .stream() + .sorted() + .map(String::valueOf) + .collect(Collectors.joining(",","[","]"))) + .toList(); + + return formattedTickets; + } +} From 1308961614014e494fd5906930569b59348a6e31 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Thu, 20 Feb 2025 04:37:58 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20view=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/InputView.java | 16 ++++++++++++++++ src/main/java/view/ResultView.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/java/view/InputView.java create mode 100644 src/main/java/view/ResultView.java diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..539b51e2 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,16 @@ +package view; + +import java.util.Scanner; + +public class InputView { + + private static final Scanner scanner = new Scanner(System.in); + + public static int getPurchaseAmount(){ + System.out.println("구입금액을 입력해 주세요."); + int purchaseAmount = scanner.nextInt(); + scanner.close(); + + return purchaseAmount; + } +} diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java new file mode 100644 index 00000000..dff86d0f --- /dev/null +++ b/src/main/java/view/ResultView.java @@ -0,0 +1,16 @@ +package view; +import java.util.List; + +public class ResultView { + + public static void printOrderTickets(int ticketCount){ + System.out.println(); + System.out.println(ticketCount + "개를 구매했습니다."); + } + + public static void printTickets(int ticketCount ,List formattedTickets){ + for (String ticket : formattedTickets) { + System.out.println(ticket); + } + } +} From d7bfc50595aea3023b2df888b1b56c7f4396a67c Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Thu, 20 Feb 2025 04:38:32 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20application=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Application.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/Application.java diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..6db9f734 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,9 @@ +import controller.LottoController; + +public class Application { + + public static void main(String[] args) { + LottoController lottoController = new LottoController(); + lottoController.run(); + } +} From 8188fc6d14a2484a34cd7ede673a6417a66409df Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 24 Feb 2025 20:13:31 +0900 Subject: [PATCH 05/12] =?UTF-8?q?refactor:=20controller=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/LottoController.java | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java index c6633cc1..add1b719 100644 --- a/src/main/java/controller/LottoController.java +++ b/src/main/java/controller/LottoController.java @@ -1,6 +1,7 @@ package controller; import model.Lotto; +import model.LottoTickets; import view.InputView; import view.ResultView; @@ -10,25 +11,42 @@ public class LottoController { public void run() { - int purchaseAmount = InputView.getPurchaseAmount(); + try { + int purchaseAmount = InputView.getPurchaseAmount(); - int ticketCount = Lotto.getTicketCount(purchaseAmount); + validatePurchaseAmount(purchaseAmount); - List tickets = Lotto.generateLottoTickets(ticketCount); + int ticketCount = Lotto.getTicketCount(purchaseAmount); - ResultView.printOrderTickets(ticketCount); - ResultView.printTickets(ticketCount, formatTickets(tickets)); + LottoTickets lottoTickets = new LottoTickets(ticketCount); + + ResultView.printOrderTickets(ticketCount); + ResultView.printPurchasedLottoTickets(formatTickets(lottoTickets.getTickets())); + }catch (IllegalArgumentException e){ + ResultView.printErrorMessage(e.getMessage()); + } } - public List formatTickets(List tickets) { - List formattedTickets = tickets.stream() - .map(lotto -> lotto.getNumbers() - .stream() - .sorted() - .map(String::valueOf) - .collect(Collectors.joining(",","[","]"))) + private List formatTickets(List tickets) { + return tickets.stream() + .map(this::convertLottoToString) .toList(); + } + + private String convertLottoToString(Lotto lotto) { + return lotto.getSortedNumbers().stream() + .map(String::valueOf) + .collect(Collectors.joining(",", "[", "]")); + } + + private void validatePurchaseAmount(int purchaseAmount) { + if (purchaseAmount < Lotto.LOTTO_PRICE) { + throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); + } - return formattedTickets; + if (purchaseAmount % Lotto.LOTTO_PRICE != 0) { + throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); + } } + } From a63a20178f56f6bb69c0d9382ddeb6e0fe212628 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 24 Feb 2025 20:14:44 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20Lotto=20=EC=B1=85=EC=9E=84?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=EB=B0=8F=20=EA=B0=9C=ED=96=89=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/Lotto.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java index 07694d77..f2007890 100644 --- a/src/main/java/model/Lotto.java +++ b/src/main/java/model/Lotto.java @@ -12,8 +12,7 @@ public class Lotto { public static final int LOTTO_CREATE_SIZE = 6; public static final int LOTTO_PRICE = 1000; public static final List LOTTO_NUMBER_POOL = - IntStream - .rangeClosed(LOTTO_MIN_NUMBER,LOTTO_MAX_NUMBER) + IntStream.rangeClosed(LOTTO_MIN_NUMBER,LOTTO_MAX_NUMBER) .boxed() .collect(Collectors.toList()); @@ -35,17 +34,7 @@ private List createLottoNumbers(){ return numbers; } - public static List generateLottoTickets(int ticketCount){ - List tickets = new ArrayList<>(); - - for (int i = 0; i < ticketCount; i++){ - tickets.add(new Lotto()); - } - - return tickets; - } - - public List getNumbers() { + public List getSortedNumbers() { List sortedNumbers = new ArrayList<>(numbers); Collections.sort(sortedNumbers); From 643a77fa9d886a3906e19cbf0790156dd270d9ae Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 24 Feb 2025 20:16:07 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/ResultView.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java index dff86d0f..227014ec 100644 --- a/src/main/java/view/ResultView.java +++ b/src/main/java/view/ResultView.java @@ -8,9 +8,13 @@ public static void printOrderTickets(int ticketCount){ System.out.println(ticketCount + "개를 구매했습니다."); } - public static void printTickets(int ticketCount ,List formattedTickets){ + public static void printPurchasedLottoTickets(List formattedTickets){ for (String ticket : formattedTickets) { System.out.println(ticket); } } + + public static void printErrorMessage(String message) { + System.out.println(message); + } } From 2be807c97f80323f4ec1dc27fd58d7981bb27d9b Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 24 Feb 2025 20:16:56 +0900 Subject: [PATCH 08/12] =?UTF-8?q?refactor:=20LottoTickets=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/LottoTickets.java | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/model/LottoTickets.java diff --git a/src/main/java/model/LottoTickets.java b/src/main/java/model/LottoTickets.java new file mode 100644 index 00000000..73749287 --- /dev/null +++ b/src/main/java/model/LottoTickets.java @@ -0,0 +1,25 @@ +package model; + +import java.util.ArrayList; +import java.util.List; + +public class LottoTickets { + + private final List tickets; + + public LottoTickets(int ticketCount) { + this.tickets = generateLottoTickets(ticketCount); + } + + private List generateLottoTickets(int ticketCount) { + List tickets = new ArrayList<>(); + for (int i = 0; i < ticketCount; i++) { + tickets.add(new Lotto()); + } + return tickets; + } + + public List getTickets() { + return tickets; + } +} From 4a44acce1c117f185abe9fba1d7992e59b777ee5 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Sun, 2 Mar 2025 15:32:31 +0900 Subject: [PATCH 09/12] =?UTF-8?q?refactor:=20=ED=94=BC=EB=93=9C=EB=B0=B1?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/LottoController.java | 14 ++++++++------ src/main/java/model/Lotto.java | 7 +------ src/main/java/model/LottoTickets.java | 7 ++++++- src/main/java/view/ErrorView.java | 7 +++++++ src/main/java/view/ResultView.java | 4 ---- 5 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 src/main/java/view/ErrorView.java diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java index add1b719..f6a8d8e4 100644 --- a/src/main/java/controller/LottoController.java +++ b/src/main/java/controller/LottoController.java @@ -2,6 +2,7 @@ import model.Lotto; import model.LottoTickets; +import view.ErrorView; import view.InputView; import view.ResultView; @@ -16,14 +17,16 @@ public void run() { validatePurchaseAmount(purchaseAmount); - int ticketCount = Lotto.getTicketCount(purchaseAmount); + int ticketCount = LottoTickets.getTicketCount(purchaseAmount); LottoTickets lottoTickets = new LottoTickets(ticketCount); ResultView.printOrderTickets(ticketCount); + ResultView.printPurchasedLottoTickets(formatTickets(lottoTickets.getTickets())); - }catch (IllegalArgumentException e){ - ResultView.printErrorMessage(e.getMessage()); + + } catch (IllegalArgumentException e) { + ErrorView.printErrorMessage(e.getMessage()); } } @@ -40,13 +43,12 @@ private String convertLottoToString(Lotto lotto) { } private void validatePurchaseAmount(int purchaseAmount) { - if (purchaseAmount < Lotto.LOTTO_PRICE) { + if (purchaseAmount < LottoTickets.LOTTO_PRICE) { throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); } - if (purchaseAmount % Lotto.LOTTO_PRICE != 0) { + if (purchaseAmount % LottoTickets.LOTTO_PRICE != 0) { throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); } } - } diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java index f2007890..0d9f6daa 100644 --- a/src/main/java/model/Lotto.java +++ b/src/main/java/model/Lotto.java @@ -10,17 +10,12 @@ public class Lotto { 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 int LOTTO_PRICE = 1000; public static final List LOTTO_NUMBER_POOL = IntStream.rangeClosed(LOTTO_MIN_NUMBER,LOTTO_MAX_NUMBER) .boxed() .collect(Collectors.toList()); - private List numbers = new ArrayList<>(); - - public static int getTicketCount(int purchaseAmount){ - return purchaseAmount / LOTTO_PRICE; - } + private List numbers; public Lotto(){ this.numbers = createLottoNumbers(); diff --git a/src/main/java/model/LottoTickets.java b/src/main/java/model/LottoTickets.java index 73749287..9ed8f200 100644 --- a/src/main/java/model/LottoTickets.java +++ b/src/main/java/model/LottoTickets.java @@ -6,6 +6,7 @@ public class LottoTickets { private final List tickets; + public static final int LOTTO_PRICE = 1000; public LottoTickets(int ticketCount) { this.tickets = generateLottoTickets(ticketCount); @@ -20,6 +21,10 @@ private List generateLottoTickets(int ticketCount) { } public List getTickets() { - return tickets; + return new ArrayList<>(tickets); + } + + public static int getTicketCount(int purchaseAmount){ + return purchaseAmount / LOTTO_PRICE; } } diff --git a/src/main/java/view/ErrorView.java b/src/main/java/view/ErrorView.java new file mode 100644 index 00000000..1d5d654d --- /dev/null +++ b/src/main/java/view/ErrorView.java @@ -0,0 +1,7 @@ +package view; + +public class ErrorView { + public static void printErrorMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java index 227014ec..b67592c2 100644 --- a/src/main/java/view/ResultView.java +++ b/src/main/java/view/ResultView.java @@ -13,8 +13,4 @@ public static void printPurchasedLottoTickets(List formattedTickets){ System.out.println(ticket); } } - - public static void printErrorMessage(String message) { - System.out.println(message); - } } From fa5e7ff3d9615c511f22378c868b4bf2e965a18b Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 3 Mar 2025 17:26:01 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=201=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/LottoController.java | 70 +++++++++++++------ src/main/java/model/Lotto.java | 30 ++------ src/main/java/model/LottoTickets.java | 23 +++--- src/main/java/view/InputView.java | 45 +++++++++++- src/main/java/view/ResultView.java | 62 ++++++++++++++-- 5 files changed, 164 insertions(+), 66 deletions(-) diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java index f6a8d8e4..86d680bf 100644 --- a/src/main/java/controller/LottoController.java +++ b/src/main/java/controller/LottoController.java @@ -1,54 +1,78 @@ 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; public class LottoController { public void run() { try { - int purchaseAmount = InputView.getPurchaseAmount(); + Money money = getPurchaseAmount(); + int manualCount = getManualCount(money); + LottoTickets lottoTickets = generateLottoTickets(money, manualCount); - validatePurchaseAmount(purchaseAmount); + ResultView.printOrderTickets(manualCount, money.getTicketCount() - manualCount); + ResultView.printPurchasedLottoTickets(lottoTickets); - int ticketCount = LottoTickets.getTicketCount(purchaseAmount); + processWinningResults(lottoTickets, money); - LottoTickets lottoTickets = new LottoTickets(ticketCount); - - ResultView.printOrderTickets(ticketCount); - - ResultView.printPurchasedLottoTickets(formatTickets(lottoTickets.getTickets())); + InputView.closeScanner(); } catch (IllegalArgumentException e) { ErrorView.printErrorMessage(e.getMessage()); } } - private List formatTickets(List tickets) { - return tickets.stream() - .map(this::convertLottoToString) - .toList(); + private Money getPurchaseAmount() { + Money money = new Money(InputView.getPurchaseAmount()); + validatePurchaseAmount(money); + return money; } - private String convertLottoToString(Lotto lotto) { - return lotto.getSortedNumbers().stream() - .map(String::valueOf) - .collect(Collectors.joining(",", "[", "]")); + private int getManualCount(Money money) { + int manualCount = InputView.getManualTicketCount(); + validateManualCount(manualCount, money); + return manualCount; } - private void validatePurchaseAmount(int purchaseAmount) { - if (purchaseAmount < LottoTickets.LOTTO_PRICE) { - throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); + private LottoTickets generateLottoTickets(Money money, int manualCount) { + List> manualNumbers = InputView.getManualNumbers(manualCount); + int autoCount = money.getTicketCount() - manualCount; + return new LottoTickets(manualNumbers, autoCount); + } + + public void validateManualCount(int manualCount, Money money) { + if (manualCount > money.getTicketCount()) { + throw new IllegalArgumentException("수동 구매 개수가 구매 가능한 개수를 초과할 수 없습니다."); } + } - if (purchaseAmount % LottoTickets.LOTTO_PRICE != 0) { + public void validatePurchaseAmount(Money money) { + int purchaseAmount = money.getAmount(); + + if (purchaseAmount < 1000) { + throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); + } + if (purchaseAmount % 1000 != 0) { throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); } } + + private void processWinningResults(LottoTickets lottoTickets, Money money) { + ResultView.printWinningStatistics(createLottoResult(lottoTickets, getWinningNumbers()), money.getAmount()); + } + + 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..3859d0a7 100644 --- a/src/main/java/model/Lotto.java +++ b/src/main/java/model/Lotto.java @@ -1,38 +1,20 @@ package model; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; public class Lotto { - // 로또 번호 관련 상수 선언 - 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 final LottoNumbers lottoNumbers; - private List numbers; - - public Lotto(){ - this.numbers = createLottoNumbers(); + public Lotto() { + this.lottoNumbers = new LottoNumbers(); } - private List createLottoNumbers(){ - List shuffledNumbers = new ArrayList<>(LOTTO_NUMBER_POOL); - Collections.shuffle(shuffledNumbers); - numbers = shuffledNumbers.subList(0, LOTTO_CREATE_SIZE); - - return numbers; + public Lotto(List numbers) { + this.lottoNumbers = new LottoNumbers(numbers); } public List getSortedNumbers() { - List sortedNumbers = new ArrayList<>(numbers); - Collections.sort(sortedNumbers); - - return sortedNumbers; + return lottoNumbers.getSortedNumbers(); } } diff --git a/src/main/java/model/LottoTickets.java b/src/main/java/model/LottoTickets.java index 9ed8f200..7d54acd1 100644 --- a/src/main/java/model/LottoTickets.java +++ b/src/main/java/model/LottoTickets.java @@ -6,25 +6,26 @@ public class LottoTickets { private final List tickets; - public static final int LOTTO_PRICE = 1000; - public LottoTickets(int ticketCount) { - this.tickets = generateLottoTickets(ticketCount); + public LottoTickets(List> manualNumbers, int autoTicketCount) { + this.tickets = new ArrayList<>(); + addManualTickets(manualNumbers); + addAutoTickets(autoTicketCount); } - private List generateLottoTickets(int ticketCount) { - List tickets = new ArrayList<>(); - for (int i = 0; i < ticketCount; i++) { + private void addManualTickets(List> manualNumbers) { + for (List numbers : manualNumbers) { + tickets.add(new Lotto(numbers)); + } + } + + private void addAutoTickets(int autoTicketCount) { + for (int i = 0; i < autoTicketCount; i++) { tickets.add(new Lotto()); } - return tickets; } public List getTickets() { return new ArrayList<>(tickets); } - - public static int getTicketCount(int purchaseAmount){ - return purchaseAmount / LOTTO_PRICE; - } } 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..e6358ef8 100644 --- a/src/main/java/view/ResultView.java +++ b/src/main/java/view/ResultView.java @@ -1,16 +1,64 @@ package view; -import java.util.List; +import model.Lotto; +import model.LottoResult; +import model.LottoTickets; +import model.Rank; +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) { - System.out.println(ticket); + public static void printPurchasedLottoTickets(LottoTickets lottoTickets) { + for (Lotto ticket : lottoTickets.getTickets()) { + System.out.println(ticket.getSortedNumbers()); } } + + + public static void printWinningStatistics(LottoResult lottoResult, int totalCost) { + System.out.println("\n당첨 통계"); + System.out.println("---------"); + + printWinningDetails(lottoResult); + printProfitRate(lottoResult, totalCost); + } + + private static void printWinningDetails(LottoResult lottoResult) { + Map matchCountMap = lottoResult.getMatchCountMap(); + Rank[] orderedRanks = {Rank.FIFTH, Rank.FOURTH, Rank.THIRD, Rank.SECOND, Rank.FIRST}; + + for (Rank rank : orderedRanks) { + String bonusText = getBonusText(rank); + System.out.printf("%d개 일치%s (%d원)- %d개%n", + rank.getMatchCount(), + bonusText, + rank.getPrizeMoney(), + matchCountMap.getOrDefault(rank, 0)); + } + } + + private static void printProfitRate(LottoResult lottoResult, int totalCost) { + double profitRate = lottoResult.calculateProfitRate(totalCost); + String profitResult = getProfitResult(profitRate); + + System.out.printf("총 수익률은 %.2f입니다. (기준이 1이기 때문에 결과적으로 %s)\n", + profitRate, profitResult); + } + + private static String getBonusText(Rank rank) { + if (rank == Rank.SECOND) { + return ", 보너스 볼 일치"; + } + return ""; + } + + private static String getProfitResult(double profitRate) { + if (profitRate >= 1) { + return "이득"; + } + return "손해"; + } } From b9c9badbad5d620a6c75e2e7cc518a7f09736a64 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 3 Mar 2025 17:28:55 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=202,3,4,5=20=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/LottoNumbers.java | 53 ++++++++++++ src/main/java/model/LottoResult.java | 42 ++++++++++ src/main/java/model/Money.java | 18 +++++ src/main/java/model/Rank.java | 32 ++++++++ src/main/java/model/WinningNumbers.java | 43 ++++++++++ .../java/controller/LottoControllerTest.java | 80 ++++++++++++++++++ src/test/java/model/LottoTest.java | 81 +++++++++++++++++++ src/test/java/model/LottoTicketsTest.java | 59 ++++++++++++++ src/test/java/model/MoneyTest.java | 49 +++++++++++ src/test/java/model/WinningNumbersTest.java | 79 ++++++++++++++++++ 10 files changed, 536 insertions(+) create mode 100644 src/main/java/model/LottoNumbers.java create mode 100644 src/main/java/model/LottoResult.java create mode 100644 src/main/java/model/Money.java create mode 100644 src/main/java/model/Rank.java create mode 100644 src/main/java/model/WinningNumbers.java create mode 100644 src/test/java/controller/LottoControllerTest.java create mode 100644 src/test/java/model/LottoTest.java create mode 100644 src/test/java/model/LottoTicketsTest.java create mode 100644 src/test/java/model/MoneyTest.java create mode 100644 src/test/java/model/WinningNumbersTest.java diff --git a/src/main/java/model/LottoNumbers.java b/src/main/java/model/LottoNumbers.java new file mode 100644 index 00000000..1c7e560e --- /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 static final List LOTTO_NUMBER_POOL = + IntStream.rangeClosed(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER) + .boxed() + .collect(Collectors.toList()); + + private List numbers; + + public LottoNumbers() { + this.numbers = createLottoNumbers(); + } + + public LottoNumbers(List numbers) { + validate(numbers); + this.numbers = List.copyOf(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 createLottoNumbers() { + List shuffledNumbers = new ArrayList<>(LOTTO_NUMBER_POOL); + Collections.shuffle(shuffledNumbers); + + return shuffledNumbers.subList(0, LOTTO_CREATE_SIZE); + } + + public List getSortedNumbers() { + return numbers.stream() + .sorted() + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/model/LottoResult.java b/src/main/java/model/LottoResult.java new file mode 100644 index 00000000..fdc8f23a --- /dev/null +++ b/src/main/java/model/LottoResult.java @@ -0,0 +1,42 @@ +package model; + +import java.util.EnumMap; +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) { + List ticketNumbers = ticket.getSortedNumbers(); + int matchCount = (int) ticketNumbers.stream() + .filter(winningNumbers.getNumbers()::contains) + .count(); + boolean hasBonus = ticketNumbers.contains(winningNumbers.getBonusNumber()); + + 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) { + int totalPrize = matchCountMap.entrySet().stream() + .mapToInt(entry -> entry.getKey().getPrizeMoney() * entry.getValue()) + .sum(); + return (double) totalPrize / totalCost; + } +} diff --git a/src/main/java/model/Money.java b/src/main/java/model/Money.java new file mode 100644 index 00000000..7cd3c490 --- /dev/null +++ b/src/main/java/model/Money.java @@ -0,0 +1,18 @@ +package model; + +public class Money { + private static final int LOTTO_PRICE = 1000; + private final int amount; + + public Money(int amount) { + this.amount = amount; + } + + public int getAmount() { + return amount; + } + + public int getTicketCount() { + return amount / LOTTO_PRICE; + } +} diff --git a/src/main/java/model/Rank.java b/src/main/java/model/Rank.java new file mode 100644 index 00000000..03b2ff10 --- /dev/null +++ b/src/main/java/model/Rank.java @@ -0,0 +1,32 @@ +package model; + +public enum Rank { + FIFTH(3, false, 5_000), + FOURTH(4, false, 50_000), + THIRD(5, false, 1_500_000), + SECOND(5, true, 30_000_000), // 5개 일치 + 보너스 볼 일치 + FIRST(6, false, 2_000_000_000), + NONE(0, false, 0); // NONE은 출력에서 제외됨 + + private final int matchCount; + private final boolean bonusMatch; + private final int prizeMoney; + + Rank(int matchCount, boolean bonusMatch, int prizeMoney) { + this.matchCount = matchCount; + this.bonusMatch = bonusMatch; + this.prizeMoney = prizeMoney; + } + + public int getMatchCount() { + return matchCount; + } + + public int getPrizeMoney() { + return prizeMoney; + } + + public boolean isBonusMatch() { + return bonusMatch; + } +} diff --git a/src/main/java/model/WinningNumbers.java b/src/main/java/model/WinningNumbers.java new file mode 100644 index 00000000..dcad0037 --- /dev/null +++ b/src/main/java/model/WinningNumbers.java @@ -0,0 +1,43 @@ +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; + } + + private void validate(List numbers, int bonusNumber) { + if (numbers.size() != SIZE) { + throw new IllegalArgumentException("당첨 번호는 6개의 숫자로 입력해야 합니다."); + } + if (numbers.stream().distinct().count() != SIZE) { + throw new IllegalArgumentException("중복된 숫자가 있습니다."); + } + if (numbers.stream().anyMatch(n -> n < MIN_NUMBER || n > MAX_NUMBER)) { + throw new IllegalArgumentException("당첨 번호는 1부터 45 사이여야 합니다."); + } + if (bonusNumber < MIN_NUMBER || bonusNumber > MAX_NUMBER) { + throw new IllegalArgumentException("보너스 볼은 1부터 45 사이여야 합니다."); + } + if (numbers.contains(bonusNumber)) { + throw new IllegalArgumentException("보너스 볼은 당첨 번호와 중복될 수 없습니다."); + } + } + + public List getNumbers() { + return numbers; + } + + public int getBonusNumber() { + return bonusNumber; + } +} diff --git a/src/test/java/controller/LottoControllerTest.java b/src/test/java/controller/LottoControllerTest.java new file mode 100644 index 00000000..5c38ad69 --- /dev/null +++ b/src/test/java/controller/LottoControllerTest.java @@ -0,0 +1,80 @@ +package controller; + +import model.*; +import org.junit.jupiter.api.*; + +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원 단위가 아니거나 1000원 미만인 경우 예외가 발생한다.") + void validatePurchaseAmountTest() { + Money validMoney = new Money(5000); + Money invalidMoney1 = new Money(900); // 1000원 미만 + Money invalidMoney2 = new Money(1500); // 1000원 단위가 아님 + + assertDoesNotThrow(() -> lottoController.validatePurchaseAmount(validMoney)); // 정상 입력 + assertThrows(IllegalArgumentException.class, () -> lottoController.validatePurchaseAmount(invalidMoney1), + "1000원 미만일 때 예외가 발생해야 합니다."); + assertThrows(IllegalArgumentException.class, () -> lottoController.validatePurchaseAmount(invalidMoney2), + "1000원 단위가 아닐 때 예외가 발생해야 합니다."); + } + + @Test + @DisplayName("수동 구매 개수가 전체 구매 가능 개수를 초과할 경우 예외가 발생한다.") + void validateManualCountTest() { + Money money = new Money(3000); // 총 3장 구매 가능 + int validManualCount = 2; + int invalidManualCount = 5; // 초과 + + assertDoesNotThrow(() -> lottoController.validateManualCount(validManualCount, money)); // 정상 입력 + assertThrows(IllegalArgumentException.class, () -> lottoController.validateManualCount(invalidManualCount, money), + "수동 구매 개수가 초과되었을 때 예외가 발생해야 합니다."); + } + + @Test + @DisplayName("수동 및 자동 로또 티켓을 정상적으로 생성한다.") + void generateLottoTicketsTest() { + Money money = new Money(5000); // 총 5장 구매 가능 + int manualCount = 2; + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(7, 8, 9, 10, 11, 12) + ); + + LottoTickets lottoTickets = new LottoTickets(manualNumbers, money.getTicketCount() - manualCount); + + assertEquals(5, lottoTickets.getTickets().size(), "로또 티켓 개수가 5장이 아닙니다."); + assertEquals(manualNumbers.get(0), lottoTickets.getTickets().get(0).getSortedNumbers(), "첫 번째 수동 로또 번호가 다릅니다."); + assertEquals(manualNumbers.get(1), lottoTickets.getTickets().get(1).getSortedNumbers(), "두 번째 수동 로또 번호가 다릅니다."); + } + + @Test + @DisplayName("로또 결과를 정상적으로 생성한다.") + void createLottoResultTest() { + List lottoTickets = List.of( + new Lotto(List.of(1, 2, 3, 4, 5, 6)), // 1등 + new Lotto(List.of(1, 2, 3, 4, 5, 7)), // 2등 + new Lotto(List.of(1, 2, 3, 4, 5, 8)) // 3등 + ); + + WinningNumbers winningNumbers = new WinningNumbers(List.of(1, 2, 3, 4, 5, 6), 7); + LottoResult lottoResult = new LottoResult(lottoTickets, winningNumbers); + + assertNotNull(lottoResult, "로또 결과 객체가 null입니다."); + assertEquals(1, lottoResult.getMatchCountMap().get(Rank.FIRST), "1등 당첨 개수가 올바르지 않습니다."); + assertEquals(1, lottoResult.getMatchCountMap().get(Rank.SECOND), "2등 당첨 개수가 올바르지 않습니다."); + assertEquals(1, lottoResult.getMatchCountMap().get(Rank.THIRD), "3등 당첨 개수가 올바르지 않습니다."); + } +} diff --git a/src/test/java/model/LottoTest.java b/src/test/java/model/LottoTest.java new file mode 100644 index 00000000..ee7048e1 --- /dev/null +++ b/src/test/java/model/LottoTest.java @@ -0,0 +1,81 @@ +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)) + ); + + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 6, 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)) + ); + + assertThrows(IllegalArgumentException.class, () -> + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 46)) + ); + } + + @Test + @DisplayName("로또번호는 자동으로 생성할 수 있다.") + void createAutoLotto() { + Lotto lotto = new Lotto(); + + assertNotNull(lotto, "자동 생성된 로또 객체가 null입니다."); + } + + @Test + @DisplayName("로또번호는 수동으로 생성할 수 있다.") + void createManualLotto() { + List numbers = Arrays.asList(1, 2, 3, 4, 5, 6); + + Lotto lotto = new Lotto(numbers); + + assertNotNull(lotto, "수동 생성된 로또 객체가 null입니다."); + } + + @Test + @DisplayName("수동으로 생성된 로또 번호는 정렬되어야 한다.") + void manualLottoShouldBeSorted() { + List manualNumbers = Arrays.asList(6, 3, 1, 5, 4, 2); + + Lotto manualLotto = new Lotto(manualNumbers); + + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), manualLotto.getSortedNumbers(), "수동 로또 번호가 정렬되지 않았습니다."); + } + + @Test + @DisplayName("자동으로 생성된 로또 번호는 정렬되어야 한다.") + void autoLottoShouldBeSorted() { + Lotto autoLotto = new Lotto(); + List autoNumbers = autoLotto.getSortedNumbers(); + + assertEquals(autoNumbers, autoNumbers.stream().sorted().toList(), "자동 생성된 로또 번호가 정렬되지 않았습니다."); + } +} diff --git a/src/test/java/model/LottoTicketsTest.java b/src/test/java/model/LottoTicketsTest.java new file mode 100644 index 00000000..e8790843 --- /dev/null +++ b/src/test/java/model/LottoTicketsTest.java @@ -0,0 +1,59 @@ +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("수동으로 2장의 로또 티켓을 생성할 수 있다.") + void createManualLottoTickets() { + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(7, 8, 9, 10, 11, 12) + ); + + LottoTickets lottoTickets = new LottoTickets(manualNumbers, 0); + List tickets = lottoTickets.getTickets(); + + assertEquals(2, tickets.size(), "수동 로또 티켓 개수가 2장이 아닙니다."); + for (int i = 0; i < manualNumbers.size(); i++) { + assertEquals(manualNumbers.get(i), tickets.get(i).getSortedNumbers(), "수동 입력 로또 번호가 다릅니다."); + } + } + + @Test + @DisplayName("자동으로 3장의 로또 티켓을 생성할 수 있다.") + void createAutoLottoTickets() { + int autoTicketCount = 3; + + LottoTickets lottoTickets = new LottoTickets(List.of(), autoTicketCount); + List tickets = lottoTickets.getTickets(); + + assertEquals(3, tickets.size(), "자동 로또 티켓 개수가 3장이 아닙니다."); + for (Lotto ticket : tickets) { + assertNotNull(ticket, "자동 생성된 로또 티켓이 null입니다."); + assertEquals(6, ticket.getSortedNumbers().size(), "자동 생성된 로또 번호 개수가 6개가 아닙니다."); + } + } + + @Test + @DisplayName("총 5장의 로또 티켓을 생성할 수 있다 (수동 2장 + 자동 3장).") + void createFiveLottoTickets() { + List> manualNumbers = Arrays.asList( + Arrays.asList(1, 2, 3, 4, 5, 6), + Arrays.asList(7, 8, 9, 10, 11, 12) + ); + int autoTicketCount = 3; + + LottoTickets lottoTickets = new LottoTickets(manualNumbers, autoTicketCount); + List tickets = lottoTickets.getTickets(); + + assertEquals(5, tickets.size(), "총 로또 티켓 개수가 5장이 아닙니다."); + } +} diff --git a/src/test/java/model/MoneyTest.java b/src/test/java/model/MoneyTest.java new file mode 100644 index 00000000..5ab9cc8f --- /dev/null +++ b/src/test/java/model/MoneyTest.java @@ -0,0 +1,49 @@ +package model; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MoneyTest { + + @Test + @DisplayName("Money 객체가 정상적으로 생성되고 금액을 반환할 수 있다.") + void createMoneyAndGetAmount() { + int amount = 5000; + + Money money = new Money(amount); + + assertEquals(amount, money.getAmount(), "금액이 올바르게 저장되지 않았습니다."); + } + + @Test + @DisplayName("로또 구매 가능 개수를 정확히 반환한다.") + void getCorrectTicketCount() { + Money money = new Money(5000); + + int ticketCount = money.getTicketCount(); + + assertEquals(5, ticketCount, "로또 티켓 개수가 잘못 계산되었습니다."); + } + + @Test + @DisplayName("금액이 0원일 때 로또 티켓을 구매할 수 없다.") + void zeroMoneyShouldReturnZeroTickets() { + Money money = new Money(0); + + int ticketCount = money.getTicketCount(); + + assertEquals(0, ticketCount, "금액이 0원일 때 티켓 개수는 0이어야 합니다."); + } + + @Test + @DisplayName("로또 한 장도 못 사는 금액일 때 티켓 개수는 0이어야 한다.") + void lessThanLottoPriceShouldReturnZeroTickets() { + Money money = new Money(500); // 로또 한 장 가격(1000)보다 적은 금액 + + int ticketCount = money.getTicketCount(); + + assertEquals(0, ticketCount, "로또 가격보다 적은 금액으로 티켓 개수가 0이어야 합니다."); + } +} 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), + "보너스 번호가 당첨 번호와 중복될 때 예외가 발생해야 합니다." + ); + } +} From 0a91304c6190090ff49eb88023c22eae832a96f5 Mon Sep 17 00:00:00 2001 From: JeongBeanHyun Date: Mon, 17 Mar 2025 00:16:02 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=202,3,4,5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/java/view/ResultView.java --- src/main/java/controller/LottoController.java | 55 +++++------ src/main/java/model/Lotto.java | 16 ++-- src/main/java/model/LottoNumbers.java | 28 +++--- src/main/java/model/LottoPurchaseInfo.java | 34 +++++++ src/main/java/model/LottoResult.java | 62 ++++++++++-- src/main/java/model/LottoTickets.java | 41 ++++++-- src/main/java/model/Money.java | 18 ---- src/main/java/model/Rank.java | 20 ++-- src/main/java/model/WinningNumbers.java | 23 ++++- src/main/java/view/ResultView.java | 53 ++++------- .../java/controller/LottoControllerTest.java | 84 +++++++++-------- .../java/model/LottoPurchaseInfoTest.java | 40 ++++++++ src/test/java/model/LottoTest.java | 94 +++++++++++++------ src/test/java/model/LottoTicketsTest.java | 58 +++++++----- src/test/java/model/MoneyTest.java | 49 ---------- 15 files changed, 406 insertions(+), 269 deletions(-) create mode 100644 src/main/java/model/LottoPurchaseInfo.java delete mode 100644 src/main/java/model/Money.java create mode 100644 src/test/java/model/LottoPurchaseInfoTest.java delete mode 100644 src/test/java/model/MoneyTest.java diff --git a/src/main/java/controller/LottoController.java b/src/main/java/controller/LottoController.java index 2db50130..f01defa5 100644 --- a/src/main/java/controller/LottoController.java +++ b/src/main/java/controller/LottoController.java @@ -6,65 +6,56 @@ import view.ResultView; import java.util.List; +import java.util.Map; public class LottoController { public void run() { try { - Money money = getPurchaseAmount(); - int manualCount = getManualCount(money); - LottoTickets lottoTickets = generateLottoTickets(money, manualCount); - - ResultView.printOrderTickets(manualCount, money.getTicketCount() - manualCount); - ResultView.printPurchasedLottoTickets(lottoTickets); - - processWinningResults(lottoTickets, money); - + 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 Money getPurchaseAmount() { - Money money = new Money(InputView.getPurchaseAmount()); - validatePurchaseAmount(money); - return money; + private LottoPurchaseInfo getPurchaseAmount() { + return new LottoPurchaseInfo(InputView.getPurchaseAmount()); } - private int getManualCount(Money money) { + private int getManualCount(LottoPurchaseInfo lottoPurchaseInfo) { int manualCount = InputView.getManualTicketCount(); - validateManualCount(manualCount, money); + validateManualCount(manualCount, lottoPurchaseInfo); return manualCount; } - private LottoTickets generateLottoTickets(Money money, int manualCount) { + private LottoTickets generateLottoTickets(LottoPurchaseInfo lottoPurchaseInfo, int manualCount) { List> manualNumbers = InputView.getManualNumbers(manualCount); - int autoCount = money.getTicketCount() - manualCount; - return new LottoTickets(manualNumbers, autoCount); + int autoCount = lottoPurchaseInfo.getTicketCount() - manualCount; + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(autoCount); + return LottoTickets.merge(manualTickets, autoTickets); } - public void validateManualCount(int manualCount, Money money) { - if (manualCount > money.getTicketCount()) { + public void validateManualCount(int manualCount, LottoPurchaseInfo lottoPurchaseInfo) { + if (manualCount > lottoPurchaseInfo.getTicketCount()) { throw new IllegalArgumentException("수동 구매 개수가 구매 가능한 개수를 초과할 수 없습니다."); } } - public void validatePurchaseAmount(Money money) { - int purchaseAmount = money.getAmount(); + private void processWinningResults(LottoTickets lottoTickets, LottoPurchaseInfo lottoPurchaseInfo) { + LottoResult lottoResult = createLottoResult(lottoTickets, getWinningNumbers()); - if (purchaseAmount < 1000) { - throw new IllegalArgumentException("구매 금액은 1000원 이상이어야 합니다."); - } - if (purchaseAmount % 1000 != 0) { - throw new IllegalArgumentException("구매 금액은 1000원 단위여야 합니다."); - } - } + Map formattedWinningDetails = lottoResult.getFormattedWinningDetails(); + double profitRate = lottoResult.calculateProfitRate(lottoPurchaseInfo.getAmount()); - private void processWinningResults(LottoTickets lottoTickets, Money money) { - ResultView.printWinningStatistics(createLottoResult(lottoTickets, getWinningNumbers()), money.getAmount()); + ResultView.printWinningStatistics(formattedWinningDetails, profitRate); } private WinningNumbers getWinningNumbers() { diff --git a/src/main/java/model/Lotto.java b/src/main/java/model/Lotto.java index 3859d0a7..c900649f 100644 --- a/src/main/java/model/Lotto.java +++ b/src/main/java/model/Lotto.java @@ -2,19 +2,21 @@ import java.util.*; +//로또 한 장 public class Lotto { - private final LottoNumbers lottoNumbers; - public Lotto() { - this.lottoNumbers = new LottoNumbers(); - } - + //수동 로또 public Lotto(List numbers) { this.lottoNumbers = new LottoNumbers(numbers); } - public List getSortedNumbers() { - return lottoNumbers.getSortedNumbers(); + //자동 로또 + public Lotto(LottoNumbers lottoNumbers) { + this.lottoNumbers = lottoNumbers; + } + + public List getNumbers() { + return lottoNumbers.getNumbers(); } } diff --git a/src/main/java/model/LottoNumbers.java b/src/main/java/model/LottoNumbers.java index 1c7e560e..7a27db9a 100644 --- a/src/main/java/model/LottoNumbers.java +++ b/src/main/java/model/LottoNumbers.java @@ -10,20 +10,16 @@ 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 static final List LOTTO_NUMBER_POOL = - IntStream.rangeClosed(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER) - .boxed() - .collect(Collectors.toList()); - private List numbers; + private final List numbers; public LottoNumbers() { - this.numbers = createLottoNumbers(); + this.numbers = sortedNumbers(generateAutoLottoNumbers()); } public LottoNumbers(List numbers) { validate(numbers); - this.numbers = List.copyOf(numbers); + this.numbers = sortedNumbers(numbers); } private void validate(List numbers) { @@ -38,16 +34,20 @@ private void validate(List numbers) { } } - private List createLottoNumbers() { - List shuffledNumbers = new ArrayList<>(LOTTO_NUMBER_POOL); + 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); + return shuffledNumbers.subList(0,LOTTO_CREATE_SIZE); } - public List getSortedNumbers() { - return numbers.stream() - .sorted() - .collect(Collectors.toList()); + 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 index fdc8f23a..ffead3ac 100644 --- a/src/main/java/model/LottoResult.java +++ b/src/main/java/model/LottoResult.java @@ -1,6 +1,7 @@ package model; import java.util.EnumMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -15,12 +16,25 @@ public LottoResult(List tickets, WinningNumbers winningNumbers) { } private Rank determineRank(Lotto ticket, WinningNumbers winningNumbers) { - List ticketNumbers = ticket.getSortedNumbers(); - int matchCount = (int) ticketNumbers.stream() + 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(); - boolean hasBonus = ticketNumbers.contains(winningNumbers.getBonusNumber()); + } + + // 보너스 번호 포함 여부 확인 + 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; @@ -34,9 +48,45 @@ public Map getMatchCountMap() { } public double calculateProfitRate(int totalCost) { - int totalPrize = matchCountMap.entrySet().stream() - .mapToInt(entry -> entry.getKey().getPrizeMoney() * entry.getValue()) - .sum(); + 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 7d54acd1..42892faa 100644 --- a/src/main/java/model/LottoTickets.java +++ b/src/main/java/model/LottoTickets.java @@ -4,28 +4,51 @@ import java.util.List; public class LottoTickets { - private final List tickets; - public LottoTickets(List> manualNumbers, int autoTicketCount) { - this.tickets = new ArrayList<>(); - addManualTickets(manualNumbers); - addAutoTickets(autoTicketCount); + 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); } - private void addManualTickets(List> manualNumbers) { + public static LottoTickets merge(LottoTickets manualTickets, LottoTickets autoTickets) { + List mergedTickets = new ArrayList<>(manualTickets.tickets); + mergedTickets.addAll(autoTickets.tickets); + return new LottoTickets(mergedTickets, true); + } + + private static void addManualTickets(List tickets, List> manualNumbers) { for (List numbers : manualNumbers) { tickets.add(new Lotto(numbers)); } } - private void addAutoTickets(int autoTicketCount) { + private static void addAutoTickets(List tickets, int autoTicketCount) { for (int i = 0; i < autoTicketCount; i++) { - tickets.add(new Lotto()); + tickets.add(new Lotto(new LottoNumbers())); } } public List getTickets() { - return new ArrayList<>(tickets); + return tickets; + } + + public List> getFormattedTicketNumbers() { + List> ticketNumbers = new ArrayList<>(); + for (Lotto ticket : tickets) { + ticketNumbers.add(ticket.getNumbers()); + } + return ticketNumbers; } } diff --git a/src/main/java/model/Money.java b/src/main/java/model/Money.java deleted file mode 100644 index 7cd3c490..00000000 --- a/src/main/java/model/Money.java +++ /dev/null @@ -1,18 +0,0 @@ -package model; - -public class Money { - private static final int LOTTO_PRICE = 1000; - private final int amount; - - public Money(int amount) { - this.amount = amount; - } - - public int getAmount() { - return amount; - } - - public int getTicketCount() { - return amount / LOTTO_PRICE; - } -} diff --git a/src/main/java/model/Rank.java b/src/main/java/model/Rank.java index 03b2ff10..c537e4e6 100644 --- a/src/main/java/model/Rank.java +++ b/src/main/java/model/Rank.java @@ -1,21 +1,23 @@ package model; public enum Rank { - FIFTH(3, false, 5_000), - FOURTH(4, false, 50_000), - THIRD(5, false, 1_500_000), - SECOND(5, true, 30_000_000), // 5개 일치 + 보너스 볼 일치 - FIRST(6, false, 2_000_000_000), - NONE(0, false, 0); // NONE은 출력에서 제외됨 + 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) { + Rank(int matchCount, boolean bonusMatch, int prizeMoney, String description) { this.matchCount = matchCount; this.bonusMatch = bonusMatch; this.prizeMoney = prizeMoney; + this.description = description; } public int getMatchCount() { @@ -29,4 +31,8 @@ public int getPrizeMoney() { 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 index dcad0037..69076088 100644 --- a/src/main/java/model/WinningNumbers.java +++ b/src/main/java/model/WinningNumbers.java @@ -15,24 +15,45 @@ public WinningNumbers(List numbers, int bonusNumber) { this.bonusNumber = bonusNumber; } - private void validate(List numbers, int 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; } diff --git a/src/main/java/view/ResultView.java b/src/main/java/view/ResultView.java index 1f309375..9e756319 100644 --- a/src/main/java/view/ResultView.java +++ b/src/main/java/view/ResultView.java @@ -1,9 +1,6 @@ package view; -import model.Lotto; -import model.LottoResult; -import model.LottoTickets; -import model.Rank; +import java.util.List; import java.util.Map; public class ResultView { @@ -12,54 +9,42 @@ public static void printOrderTickets(int manualCount, int autoCount) { System.out.printf("\n수동으로 %d장, 자동으로 %d개를 구매했습니다.%n", manualCount, autoCount); } - public static void printPurchasedLottoTickets(LottoTickets lottoTickets) { - for (Lotto ticket : lottoTickets.getTickets()) { - System.out.println(ticket.getSortedNumbers()); + public static void printPurchasedLottoTickets(List> lottoTickets) { + for (List ticket : lottoTickets) { + System.out.println(ticket); } } - - public static void printWinningStatistics(LottoResult lottoResult, int totalCost) { + public static void printWinningStatistics(Map winningDetails, double profitRate) { System.out.println("\n당첨 통계"); System.out.println("---------"); - printWinningDetails(lottoResult); - printProfitRate(lottoResult, totalCost); + printWinningDetails(winningDetails); + printProfitRate(profitRate); } - private static void printWinningDetails(LottoResult lottoResult) { - Map matchCountMap = lottoResult.getMatchCountMap(); - Rank[] orderedRanks = {Rank.FIFTH, Rank.FOURTH, Rank.THIRD, Rank.SECOND, Rank.FIRST}; - - for (Rank rank : orderedRanks) { - String bonusText = getBonusText(rank); - System.out.printf("%d개 일치%s (%d원)- %d개%n", - rank.getMatchCount(), - bonusText, - rank.getPrizeMoney(), - matchCountMap.getOrDefault(rank, 0)); + private static void printWinningDetails(Map winningDetails) { + for (Map.Entry entry : winningDetails.entrySet()) { + printRankDetails(entry.getKey(), entry.getValue()); } } - private static void printProfitRate(LottoResult lottoResult, int totalCost) { - double profitRate = lottoResult.calculateProfitRate(totalCost); + 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", + System.out.printf("총 수익률은 %.2f입니다. (기준이 1이기 때문에 결과적으로 %s)%n", profitRate, profitResult); } - private static String getBonusText(Rank rank) { - if (rank == Rank.SECOND) { - return ", 보너스 볼 일치"; - } - return ""; - } - private static String getProfitResult(double profitRate) { if (profitRate >= 1) { - return "이득"; + return "이득이라는 의미임"; + } else { + return "손해라는 의미임"; } - return "손해"; } } diff --git a/src/test/java/controller/LottoControllerTest.java b/src/test/java/controller/LottoControllerTest.java index 5c38ad69..e3ed2a11 100644 --- a/src/test/java/controller/LottoControllerTest.java +++ b/src/test/java/controller/LottoControllerTest.java @@ -1,7 +1,9 @@ package controller; import model.*; -import org.junit.jupiter.api.*; +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; @@ -18,63 +20,65 @@ void setUp() { } @Test - @DisplayName("구매 금액이 1000원 단위가 아니거나 1000원 미만인 경우 예외가 발생한다.") - void validatePurchaseAmountTest() { - Money validMoney = new Money(5000); - Money invalidMoney1 = new Money(900); // 1000원 미만 - Money invalidMoney2 = new Money(1500); // 1000원 단위가 아님 - - assertDoesNotThrow(() -> lottoController.validatePurchaseAmount(validMoney)); // 정상 입력 - assertThrows(IllegalArgumentException.class, () -> lottoController.validatePurchaseAmount(invalidMoney1), - "1000원 미만일 때 예외가 발생해야 합니다."); - assertThrows(IllegalArgumentException.class, () -> lottoController.validatePurchaseAmount(invalidMoney2), - "1000원 단위가 아닐 때 예외가 발생해야 합니다."); + @DisplayName("구매 금액이 1000원 단위일 때 LottoPurchaseInfo가 정상 생성되어야 한다.") + void validPurchaseAmountShouldCreateLottoPurchaseInfo() { + int validAmount = 5000; + + LottoPurchaseInfo purchaseInfo = new LottoPurchaseInfo(validAmount); + + assertEquals(validAmount, purchaseInfo.getAmount(), "구매 금액이 일치하지 않습니다."); + assertEquals(5, purchaseInfo.getTicketCount(), "구매 가능한 티켓 수가 올바르지 않습니다."); } @Test - @DisplayName("수동 구매 개수가 전체 구매 가능 개수를 초과할 경우 예외가 발생한다.") - void validateManualCountTest() { - Money money = new Money(3000); // 총 3장 구매 가능 - int validManualCount = 2; - int invalidManualCount = 5; // 초과 - - assertDoesNotThrow(() -> lottoController.validateManualCount(validManualCount, money)); // 정상 입력 - assertThrows(IllegalArgumentException.class, () -> lottoController.validateManualCount(invalidManualCount, money), - "수동 구매 개수가 초과되었을 때 예외가 발생해야 합니다."); + @DisplayName("수동 로또 개수가 구매 가능한 개수를 초과하면 예외 발생") + void validateManualCountThrowsException() { + LottoPurchaseInfo purchaseInfo = new LottoPurchaseInfo(5000); + int manualCount = 6; + + assertThrows(IllegalArgumentException.class, () -> + lottoController.validateManualCount(manualCount, purchaseInfo), + "수동 구매 개수가 초과했는데 예외가 발생하지 않았습니다." + ); } @Test - @DisplayName("수동 및 자동 로또 티켓을 정상적으로 생성한다.") - void generateLottoTicketsTest() { - Money money = new Money(5000); // 총 5장 구매 가능 + @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 lottoTickets = new LottoTickets(manualNumbers, money.getTicketCount() - manualCount); + LottoTickets manualTickets = new LottoTickets(manualNumbers); + LottoTickets autoTickets = new LottoTickets(purchaseInfo.getTicketCount() - manualCount); + LottoTickets lottoTickets = LottoTickets.merge(manualTickets, autoTickets); - assertEquals(5, lottoTickets.getTickets().size(), "로또 티켓 개수가 5장이 아닙니다."); - assertEquals(manualNumbers.get(0), lottoTickets.getTickets().get(0).getSortedNumbers(), "첫 번째 수동 로또 번호가 다릅니다."); - assertEquals(manualNumbers.get(1), lottoTickets.getTickets().get(1).getSortedNumbers(), "두 번째 수동 로또 번호가 다릅니다."); + assertEquals(5, lottoTickets.getTickets().size(), "전체 로또 티켓 개수가 올바르지 않습니다."); + assertEquals(manualNumbers, lottoTickets.getFormattedTicketNumbers().subList(0, 2), + "수동 로또 번호가 일치하지 않습니다."); } @Test - @DisplayName("로또 결과를 정상적으로 생성한다.") - void createLottoResultTest() { - List lottoTickets = List.of( - new Lotto(List.of(1, 2, 3, 4, 5, 6)), // 1등 - new Lotto(List.of(1, 2, 3, 4, 5, 7)), // 2등 - new Lotto(List.of(1, 2, 3, 4, 5, 8)) // 3등 + @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); - WinningNumbers winningNumbers = new WinningNumbers(List.of(1, 2, 3, 4, 5, 6), 7); - LottoResult lottoResult = new LottoResult(lottoTickets, winningNumbers); + LottoResult lottoResult = new LottoResult(lottoTickets.getTickets(), winningNumbers); - assertNotNull(lottoResult, "로또 결과 객체가 null입니다."); - assertEquals(1, lottoResult.getMatchCountMap().get(Rank.FIRST), "1등 당첨 개수가 올바르지 않습니다."); - assertEquals(1, lottoResult.getMatchCountMap().get(Rank.SECOND), "2등 당첨 개수가 올바르지 않습니다."); - assertEquals(1, lottoResult.getMatchCountMap().get(Rank.THIRD), "3등 당첨 개수가 올바르지 않습니다."); + 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 index ee7048e1..a2e5d8a7 100644 --- a/src/test/java/model/LottoTest.java +++ b/src/test/java/model/LottoTest.java @@ -11,71 +11,111 @@ class LottoTest { @Test - @DisplayName("로또번호가 6개보다 작거나 큰 경우 예외를 발생한다.") + @DisplayName("로또 번호가 6개보다 작거나 많으면 예외가 발생한다.") void invalidSizeThrowsException() { assertThrows(IllegalArgumentException.class, () -> - new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5)) + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5)) // 5개 → 예외 발생 ); assertThrows(IllegalArgumentException.class, () -> - new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 6, 7)) + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 6, 7)) // 7개 → 예외 발생 ); } @Test - @DisplayName("로또번호는 중복될 경우 예외를 발생한다.") + @DisplayName("로또 번호가 중복되면 예외가 발생한다.") void duplicateNumbersThrowException() { assertThrows(IllegalArgumentException.class, () -> - new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 5)) + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 5)) // 중복된 숫자 → 예외 발생 ); } @Test - @DisplayName("로또번호는 1~45 사이의 숫자가 아닌 경우 예외를 발생한다.") + @DisplayName("로또 번호가 1~45 사이가 아니면 예외 발생") void outOfRangeThrowsException() { assertThrows(IllegalArgumentException.class, () -> - new LottoNumbers(Arrays.asList(0, 1, 2, 3, 4, 5)) + new LottoNumbers(Arrays.asList(0, 1, 2, 3, 4, 5)) // 0 포함 → 예외 발생 ); assertThrows(IllegalArgumentException.class, () -> - new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 46)) + new LottoNumbers(Arrays.asList(1, 2, 3, 4, 5, 46)) // 46 포함 → 예외 발생 ); } @Test - @DisplayName("로또번호는 자동으로 생성할 수 있다.") - void createAutoLotto() { - Lotto lotto = new Lotto(); - - assertNotNull(lotto, "자동 생성된 로또 객체가 null입니다."); + @DisplayName("자동으로 로또 번호를 생성할 수 있다.") + void createAutoLottoNumbers() { + LottoNumbers autoLottoNumbers = new LottoNumbers(); // 자동 생성 + assertNotNull(autoLottoNumbers, "자동 생성된 로또 번호가 null입니다."); + assertEquals(6, autoLottoNumbers.getNumbers().size(), "자동 생성된 로또 번호 개수가 6개가 아닙니다."); } @Test - @DisplayName("로또번호는 수동으로 생성할 수 있다.") - void createManualLotto() { + @DisplayName("수동으로 로또 번호를 생성할 수 있다.") + void createManualLottoNumbers() { List numbers = Arrays.asList(1, 2, 3, 4, 5, 6); - - Lotto lotto = new Lotto(numbers); - - assertNotNull(lotto, "수동 생성된 로또 객체가 null입니다."); + LottoNumbers manualLottoNumbers = new LottoNumbers(numbers); // 수동 생성 + assertNotNull(manualLottoNumbers, "수동 생성된 로또 번호가 null입니다."); + assertEquals(numbers, manualLottoNumbers.getNumbers(), "수동 생성된 로또 번호가 입력과 다릅니다."); } @Test @DisplayName("수동으로 생성된 로또 번호는 정렬되어야 한다.") - void manualLottoShouldBeSorted() { + void manualLottoNumbersShouldBeSorted() { List manualNumbers = Arrays.asList(6, 3, 1, 5, 4, 2); - - Lotto manualLotto = new Lotto(manualNumbers); - - assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), manualLotto.getSortedNumbers(), "수동 로또 번호가 정렬되지 않았습니다."); + LottoNumbers manualLottoNumbers = new LottoNumbers(manualNumbers); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), manualLottoNumbers.getNumbers(), "수동 로또 번호가 정렬되지 않았습니다."); } @Test @DisplayName("자동으로 생성된 로또 번호는 정렬되어야 한다.") - void autoLottoShouldBeSorted() { - Lotto autoLotto = new Lotto(); - List autoNumbers = autoLotto.getSortedNumbers(); + 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 index e8790843..5d8edab9 100644 --- a/src/test/java/model/LottoTicketsTest.java +++ b/src/test/java/model/LottoTicketsTest.java @@ -11,49 +11,57 @@ class LottoTicketsTest { @Test - @DisplayName("수동으로 2장의 로또 티켓을 생성할 수 있다.") - void createManualLottoTickets() { + @DisplayName("수동 로또 티켓이 정상적으로 생성되어야 한다.") + void createManualTickets() { List> manualNumbers = Arrays.asList( Arrays.asList(1, 2, 3, 4, 5, 6), Arrays.asList(7, 8, 9, 10, 11, 12) ); - LottoTickets lottoTickets = new LottoTickets(manualNumbers, 0); - List tickets = lottoTickets.getTickets(); + LottoTickets manualTickets = new LottoTickets(manualNumbers); - assertEquals(2, tickets.size(), "수동 로또 티켓 개수가 2장이 아닙니다."); - for (int i = 0; i < manualNumbers.size(); i++) { - assertEquals(manualNumbers.get(i), tickets.get(i).getSortedNumbers(), "수동 입력 로또 번호가 다릅니다."); - } + assertEquals(2, manualTickets.getTickets().size(), "수동 로또 티켓 개수가 일치하지 않습니다."); + assertEquals(manualNumbers, manualTickets.getFormattedTicketNumbers(), "수동 로또 번호가 일치하지 않습니다."); } @Test - @DisplayName("자동으로 3장의 로또 티켓을 생성할 수 있다.") - void createAutoLottoTickets() { + @DisplayName("자동 로또 티켓이 정상적으로 생성되어야 한다.") + void createAutoTickets() { int autoTicketCount = 3; - LottoTickets lottoTickets = new LottoTickets(List.of(), autoTicketCount); - List tickets = lottoTickets.getTickets(); + LottoTickets autoTickets = new LottoTickets(autoTicketCount); - assertEquals(3, tickets.size(), "자동 로또 티켓 개수가 3장이 아닙니다."); - for (Lotto ticket : tickets) { - assertNotNull(ticket, "자동 생성된 로또 티켓이 null입니다."); - assertEquals(6, ticket.getSortedNumbers().size(), "자동 생성된 로또 번호 개수가 6개가 아닙니다."); - } + assertEquals(autoTicketCount, autoTickets.getTickets().size(), "자동 로또 티켓 개수가 일치하지 않습니다."); + autoTickets.getTickets().forEach(ticket -> + assertEquals(6, ticket.getNumbers().size(), "자동 생성된 로또 번호 개수가 6개가 아닙니다.") + ); } @Test - @DisplayName("총 5장의 로또 티켓을 생성할 수 있다 (수동 2장 + 자동 3장).") - void createFiveLottoTickets() { + @DisplayName("수동 + 자동 로또 티켓이 정상적으로 합쳐져야 한다.") + void mergeTickets() { List> manualNumbers = Arrays.asList( - Arrays.asList(1, 2, 3, 4, 5, 6), - Arrays.asList(7, 8, 9, 10, 11, 12) + Arrays.asList(1, 2, 3, 4, 5, 6) ); - int autoTicketCount = 3; + 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); - LottoTickets lottoTickets = new LottoTickets(manualNumbers, autoTicketCount); - List tickets = lottoTickets.getTickets(); + manualNumbers.get(0).set(0, 99); - assertEquals(5, tickets.size(), "총 로또 티켓 개수가 5장이 아닙니다."); + assertNotEquals(99, lottoTickets.getTickets().get(0).getNumbers().get(0), + "LottoTickets 내부 데이터가 변경되었습니다. 불변성이 깨졌습니다."); } } diff --git a/src/test/java/model/MoneyTest.java b/src/test/java/model/MoneyTest.java deleted file mode 100644 index 5ab9cc8f..00000000 --- a/src/test/java/model/MoneyTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package model; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class MoneyTest { - - @Test - @DisplayName("Money 객체가 정상적으로 생성되고 금액을 반환할 수 있다.") - void createMoneyAndGetAmount() { - int amount = 5000; - - Money money = new Money(amount); - - assertEquals(amount, money.getAmount(), "금액이 올바르게 저장되지 않았습니다."); - } - - @Test - @DisplayName("로또 구매 가능 개수를 정확히 반환한다.") - void getCorrectTicketCount() { - Money money = new Money(5000); - - int ticketCount = money.getTicketCount(); - - assertEquals(5, ticketCount, "로또 티켓 개수가 잘못 계산되었습니다."); - } - - @Test - @DisplayName("금액이 0원일 때 로또 티켓을 구매할 수 없다.") - void zeroMoneyShouldReturnZeroTickets() { - Money money = new Money(0); - - int ticketCount = money.getTicketCount(); - - assertEquals(0, ticketCount, "금액이 0원일 때 티켓 개수는 0이어야 합니다."); - } - - @Test - @DisplayName("로또 한 장도 못 사는 금액일 때 티켓 개수는 0이어야 한다.") - void lessThanLottoPriceShouldReturnZeroTickets() { - Money money = new Money(500); // 로또 한 장 가격(1000)보다 적은 금액 - - int ticketCount = money.getTicketCount(); - - assertEquals(0, ticketCount, "로또 가격보다 적은 금액으로 티켓 개수가 0이어야 합니다."); - } -}