Repository: next-step/java-lotto
Pull request #4151: 🚀 4단계 - 로또(수동)
Author: login: jiyoung928
Created: 2025-04-04T15:23:36Z | Merged: 2025-04-09T14:16:58Z

안녕하세요 리뷰어님!
이번 단계에서는 도메인 로직을 많이 변경하지는 않고,
LottoTickets에 수동생성로또 와 자동생성로또를 합쳐서 반환하는 merge 를 만들어서 구현해봤습니다.
이번 단계도 리뷰 잘 부탁드립니다! 🙇‍♀️


Diff:
diff --git a/src/main/java/lotto/LottoMain.java b/src/main/java/lotto/LottoMain.java
index 377e6536c0..16a2bd407f 100644
--- a/src/main/java/lotto/LottoMain.java
+++ b/src/main/java/lotto/LottoMain.java
@@ -1,19 +1,24 @@
 package lotto;
 
 import lotto.domain.LottoGame;
-import lotto.domain.LottoNumber;
 import lotto.domain.LottoTickets;
 import lotto.strategy.AutoLottoStrategy;
 import lotto.ui.InputView;
 import lotto.ui.ResultView;
 
+import java.util.List;
+
 public class LottoMain {
     public static void main(String[] args) {
         int lottoPrice = InputView.inputLottoPrice();
-        LottoTickets tickets = LottoTickets.purchase(lottoPrice, new AutoLottoStrategy());
-        ResultView.viewLottoCount(tickets.getCount());
 
-        LottoGame lottoGame = new LottoGame(tickets);
+        int manualCount = InputView.inputManualLottoCount();
+        List<String> manualLottoTickets = InputView.inputManualLottoTickets(manualCount);
+
+        LottoTickets allTickets = LottoTickets.purchase(lottoPrice, manualCount, manualLottoTickets, new AutoLottoStrategy());
+        ResultView.viewAutoManualLottoCount(manualCount, allTickets.getCount()-manualCount);
+
+        LottoGame lottoGame = new LottoGame(allTickets);
         ResultView.viewLottoTickets(lottoGame);
 
         ResultView.viewWinningStatistics(lottoGame.gameStart(InputView.inputWinningLottoNumbers()));
diff --git a/src/main/java/lotto/domain/LottoTicket.java b/src/main/java/lotto/domain/LottoTicket.java
index d12f843468..280948c5ae 100644
--- a/src/main/java/lotto/domain/LottoTicket.java
+++ b/src/main/java/lotto/domain/LottoTicket.java
@@ -3,6 +3,7 @@
 import lotto.strategy.LottoStrategy;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -16,11 +17,16 @@ public LottoTicket(List<LottoNumber> lottoNumbers) {
         this.lottoNumbers = lottoNumbers;
     }
 
+    public List<LottoNumber> getLottoTicket() {
+        return lottoNumbers;
+    }
+
     public List<Integer> getLottoNumbers() {
         return lottoNumbers.stream()
                 .map(LottoNumber::getNumber)
                 .collect(Collectors.toList());
     }
+
     private void validateLottoNumbers(List<LottoNumber> lottoNumbers) {
         Set<LottoNumber> uniqueNumbers = lottoNumbers.stream().collect(Collectors.toSet());
 
@@ -34,8 +40,15 @@ public static LottoTicket generateLottoNumbers(LottoStrategy lottoStrategy) {
         return new LottoTicket(generatedNumbers);
     }
 
-
     public boolean contains(LottoNumber lottoNumber) {
         return lottoNumbers.contains(lottoNumber);
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LottoTicket that = (LottoTicket) o;
+        return Objects.equals(lottoNumbers, that.lottoNumbers);
+    }
 }
diff --git a/src/main/java/lotto/domain/LottoTickets.java b/src/main/java/lotto/domain/LottoTickets.java
index 8898347407..97569fec49 100644
--- a/src/main/java/lotto/domain/LottoTickets.java
+++ b/src/main/java/lotto/domain/LottoTickets.java
@@ -1,5 +1,6 @@
 package lotto.domain;
 
+import lotto.factory.LottoFactory;
 import lotto.strategy.LottoStrategy;
 
 import java.util.ArrayList;
@@ -11,14 +12,11 @@ public class LottoTickets {
     public static final int LOTTO_PRICE = 1000;
     private final List<LottoTicket> lottoTickets;
 
+
     public LottoTickets(List<LottoTicket> lottoTickets) {
         this.lottoTickets = lottoTickets;
     }
 
-    public List<LottoTicket> getLottoTickets() {
-        return lottoTickets;
-    }
-
     public static LottoTickets fromNumbers(int count, LottoStrategy lottoStrategy) {
         List<LottoTicket> tickets = new ArrayList<>();
         for (int i = 0; i < count; i++) {
@@ -27,9 +25,34 @@ public static LottoTickets fromNumbers(int count, LottoStrategy lottoStrategy) {
         return new LottoTickets(tickets);
     }
 
-    public static LottoTickets purchase(int payment, LottoStrategy lottoStrategy) {
+    public static LottoTickets purchase(int payment, int manualCount, List<String> manualLottoInputs, LottoStrategy lottoStrategy) {
         int count = payment / LOTTO_PRICE;
-        return fromNumbers(count, lottoStrategy);
+        validLottoTickets(payment, manualCount);
+        LottoTickets manualTickets = LottoFactory.createTickets(manualLottoInputs);
+        LottoTickets autoTickets = fromNumbers(count - manualCount, lottoStrategy);
+        return LottoTickets.merge(manualTickets, autoTickets);
+    }
+
+    public static LottoTickets merge(LottoTickets manualLottoTickets, LottoTickets autoLottoTickets) {
+        // 두 LottoTickets 객체의 내부 리스트를 가져와 병합
+        List<LottoTicket> merged = new ArrayList<>(manualLottoTickets.getLottoTickets());
+        merged.addAll(autoLottoTickets.getLottoTickets());
+
+        // 병합된 리스트로 새로운 LottoTickets 객체 생성
+        return new LottoTickets(merged);
+    }
+
+    private static void validLottoTickets(int totalPrice, int manualLottoCount) {
+        int totalCount = totalPrice / LottoTickets.LOTTO_PRICE;
+        if (totalCount < manualLottoCount) {
+            throw new IllegalArgumentException(
+                    "수동으로 구매하려는 로또 개수가 총 구매 가능한 개수를 초과했습니다."
+            );
+        }
+    }
+
+    public List<LottoTicket> getLottoTickets() {
+        return lottoTickets;
     }
 
     public int getCount() {
@@ -60,6 +83,4 @@ private double calculateReturnRate(Map<Rank, Integer> results) {
                 .sum();
         return (double) totalWon / totalSpent;
     }
-
-
 }
diff --git a/src/main/java/lotto/domain/Rank.java b/src/main/java/lotto/domain/Rank.java
index ab2a915ff8..0ce7f0194f 100644
--- a/src/main/java/lotto/domain/Rank.java
+++ b/src/main/java/lotto/domain/Rank.java
@@ -40,7 +40,7 @@ public static Rank getRankByMatchCount(int countOfMatch, boolean bonusMatch) {
                     if (rank == SECOND) {
                         return bonusMatch; // SECOND는 보너스 매치가 true여야 함
                     }
-                    return rank != SECOND || !bonusMatch; // 나머지는 보너스 매치와 무관
+                    return !bonusMatch; // 나머지는 보너스 매치와 무관
                 })
                 .findFirst()
                 .orElse(MISS);
diff --git a/src/main/java/lotto/domain/WinningLottoTicket.java b/src/main/java/lotto/domain/WinningLottoTicket.java
index c01ab7d939..ff747cfb28 100644
--- a/src/main/java/lotto/domain/WinningLottoTicket.java
+++ b/src/main/java/lotto/domain/WinningLottoTicket.java
@@ -1,21 +1,17 @@
 package lotto.domain;
 
-import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Objects;
 
 public class WinningLottoTicket {
     private final List<LottoNumber> lottoNumbers;
     private final LottoNumber bonusNumber;
 
-    public WinningLottoTicket(final List<LottoNumber>  lottoNumbers, final LottoNumber bonusNumber) {
+    public WinningLottoTicket(final List<LottoNumber> lottoNumbers, final LottoNumber bonusNumber) {
         validateNumbers(lottoNumbers, bonusNumber);
         this.lottoNumbers = lottoNumbers;
         this.bonusNumber = bonusNumber;
     }
-    public WinningLottoTicket(final String lottoNumbers,final int bonusNumber) {
-        this(createWinningTicket(lottoNumbers), LottoNumber.of(bonusNumber));
-    }
 
     private void validateNumbers(List<LottoNumber> lottoNumbers, LottoNumber bonusNumber) {
         if (lottoNumbers.size() != 6) {
@@ -40,12 +36,12 @@ public boolean hasBonusMatch(LottoTicket userLottoTicket) {
         return userLottoTicket.contains(bonusNumber); // 직접 LottoTicket에 contains 호출
     }
 
-    private static List<LottoNumber> createWinningTicket(String input) {
-        List<LottoNumber> lottoNumbers = Arrays.stream(input.trim().split(","))
-                .map(String::trim)        // 각 번호의 공백 제거
-                .map(Integer::parseInt)   // 문자열을 숫자로 변환
-                .map(LottoNumber::of)     // 숫자를 LottoNumber로 변환
-                .collect(Collectors.toList()); // List<LottoNumber>로 수집
-        return lottoNumbers;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WinningLottoTicket that = (WinningLottoTicket) o;
+        return Objects.equals(lottoNumbers, that.lottoNumbers) && Objects.equals(bonusNumber, that.bonusNumber);
     }
+
 }
diff --git a/src/main/java/lotto/factory/LottoFactory.java b/src/main/java/lotto/factory/LottoFactory.java
new file mode 100644
index 0000000000..08af75d46c
--- /dev/null
+++ b/src/main/java/lotto/factory/LottoFactory.java
@@ -0,0 +1,50 @@
+package lotto.factory;
+
+import lotto.domain.LottoNumber;
+import lotto.domain.LottoTicket;
+import lotto.domain.LottoTickets;
+import lotto.domain.WinningLottoTicket;
+import lotto.strategy.ManualLottoStrategy;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class LottoFactory {
+    public static LottoTickets createTickets(List<String> inputs) {
+        List<LottoTicket> tickets = inputs.stream()
+                .map(input -> parseLottoNumbers(input)) // 문자열 → 숫자 리스트
+                .map(numbers -> new ManualLottoStrategy(numbers)) // 전략 생성
+                .map(strategy -> LottoTicket.generateLottoNumbers(strategy)) // 티켓 생성
+                .collect(Collectors.toList());
+        return new LottoTickets(tickets);
+    }
+
+    public static LottoTicket createLottoTicket(String input) {
+        List<LottoNumber> lottoNumbers = Arrays.stream(input.trim().split(","))
+                .map(String::trim)
+                .map(Integer::parseInt)
+                .map(LottoNumber::of)
+                .collect(Collectors.toList());
+        return new LottoTicket(lottoNumbers); // LottoTicket 객체 생성 후 반환
+    }
+
+    public static WinningLottoTicket createWinningLottoTicket(String input, int bonusNumber) {
+        List<LottoNumber> lottoNumbers = Arrays.stream(input.trim().split(","))
+                .map(String::trim)
+                .map(Integer::parseInt)
+                .map(LottoNumber::of)
+                .collect(Collectors.toList());
+        return new WinningLottoTicket(lottoNumbers, LottoNumber.of(bonusNumber)); // LottoTicket 객체 생성 후 반환
+    }
+
+    private static List<LottoNumber> parseLottoNumbers(String input) {
+        return Arrays.stream(input.split(","))
+                .map(String::trim)                      // 각 번호의 공백 제거
+                .map(Integer::parseInt)                 // 문자열을 숫자로 변환
+                .map(LottoNumber::of)                   // 숫자를 LottoNumber로 변환 (유효성 검증 포함)
+                .collect(Collectors.toList());
+    }
+
+
+}
diff --git a/src/main/java/lotto/strategy/ManualLottoStrategy.java b/src/main/java/lotto/strategy/ManualLottoStrategy.java
new file mode 100644
index 0000000000..9b69019de9
--- /dev/null
+++ b/src/main/java/lotto/strategy/ManualLottoStrategy.java
@@ -0,0 +1,24 @@
+package lotto.strategy;
+
+import lotto.domain.LottoNumber;
+import lotto.domain.LottoTicket;
+
+import java.util.List;
+
+public class ManualLottoStrategy implements LottoStrategy {
+
+    private final List<LottoNumber> lottoNumbers;
+
+    public ManualLottoStrategy(List<LottoNumber> numbers) {
+        this.lottoNumbers = numbers;
+    }
+
+    public ManualLottoStrategy(LottoTicket lottoTicket) {
+        this.lottoNumbers = lottoTicket.getLottoTicket();
+    }
+
+    @Override
+    public List<LottoNumber> generateLottoNumbers() {
+        return lottoNumbers;
+    }
+}
\ 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 99eccf1c45..7ed7c72bc2 100644
--- a/src/main/java/lotto/ui/InputView.java
+++ b/src/main/java/lotto/ui/InputView.java
@@ -1,12 +1,11 @@
 package lotto.ui;
 
-import lotto.domain.LottoNumber;
 import lotto.domain.WinningLottoTicket;
+import lotto.factory.LottoFactory;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Scanner;
-import java.util.stream.Collectors;
 
 public class InputView {
     public static Scanner SCANNER = new Scanner(System.in);
@@ -16,12 +15,26 @@ public static int inputLottoPrice() {
         return SCANNER.nextInt();
     }
 
+    public static int inputManualLottoCount() {
+        System.out.println("수동으로 구매할 로또 수를 입력해 주세요.");
+        return SCANNER.nextInt();
+    }
+
     public static WinningLottoTicket inputWinningLottoNumbers() {
         System.out.println("지난 주 당첨 번호를 입력해 주세요.");
-        String test = SCANNER.nextLine();
         String input = SCANNER.nextLine();
         System.out.println("보너스 볼을 입력해 주세요.");
         int bonus = SCANNER.nextInt();
-        return new WinningLottoTicket(input, bonus);
+        return LottoFactory.createWinningLottoTicket(input, bonus);
+    }
+
+    public static List<String> inputManualLottoTickets(int count) {
+        System.out.println("수동으로 구매할 번호를 입력해 주세요.");
+        String test = SCANNER.nextLine();
+        List<String> lottoTickets = new ArrayList<>();
+        for(int i = 0; i < count; i++) {
+            lottoTickets.add(SCANNER.nextLine());
+        }
+        return lottoTickets;
     }
 }
diff --git a/src/main/java/lotto/ui/ResultView.java b/src/main/java/lotto/ui/ResultView.java
index d17fe790ea..d3e7f74f92 100644
--- a/src/main/java/lotto/ui/ResultView.java
+++ b/src/main/java/lotto/ui/ResultView.java
@@ -9,6 +9,10 @@ public static void viewLottoCount(int lottoCount) {
         System.out.println(lottoCount + "개를 구매했습니다.");
     }
 
+    public static void viewAutoManualLottoCount(int manualLottoCount, int autoLottoCount) {
+        System.out.println("수동으로 "+manualLottoCount+"장, 자동으로 "+autoLottoCount+"개를 구매했습니다.");
+    }
+
     public static void viewLottoTickets(LottoGame lottoGame) {
         for (LottoTicket ticket : lottoGame.getTickets().getLottoTickets()) {
             System.out.println("[" + String.join(", ", ticket.getLottoNumbers().stream()
diff --git a/src/test/java/lotto/LottoFactoryTest.java b/src/test/java/lotto/LottoFactoryTest.java
new file mode 100644
index 0000000000..573477f7fe
--- /dev/null
+++ b/src/test/java/lotto/LottoFactoryTest.java
@@ -0,0 +1,32 @@
+package lotto;
+
+import lotto.domain.LottoNumber;
+import lotto.domain.LottoTicket;
+import lotto.domain.WinningLottoTicket;
+import lotto.factory.LottoFactory;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+public class LottoFactoryTest {
+    @Test
+    @DisplayName("팩토리가 입력 문자열로부터 입력된 문자열 번호와 동일한 LottoTicket들을 생성한다")
+    void createTickets(){
+        List<String> inputs = List.of("1,2,3,4,5,6", "7,8,9,10,11,12");
+        List<LottoTicket> tickets = new ArrayList<>();
+        tickets.add(LottoFactory.createLottoTicket("1,2,3,4,5,6"));
+        tickets.add(LottoFactory.createLottoTicket("7,8,9,10,11,12"));
+        assertThat(LottoFactory.createTickets(inputs).getLottoTickets()).isEqualTo(tickets);
+    }
+
+    @Test
+    @DisplayName("팩토리가 입력 문자열로부터 입력된 문자열 번호와 동일한 WinningLottoTicket을 생성한다")
+    void createWinningTickets(){
+        assertThat(LottoFactory.createWinningLottoTicket("1,2,3,4,5,6", 7))
+                .isEqualTo(new WinningLottoTicket(LottoFactory.createLottoTicket("1, 2, 3, 4, 5, 6").getLottoTicket(), LottoNumber.of(7)));
+    }
+}
diff --git a/src/test/java/lotto/LottoGameTest.java b/src/test/java/lotto/LottoGameTest.java
index 8145f08e4c..29ca4028cb 100644
--- a/src/test/java/lotto/LottoGameTest.java
+++ b/src/test/java/lotto/LottoGameTest.java
@@ -1,6 +1,7 @@
 package lotto;
 
 import lotto.domain.*;
+import lotto.factory.LottoFactory;
 import org.junit.jupiter.api.Test;
 
 
@@ -23,7 +24,7 @@ void gameStart() {
 
         // 실제 게임 실행
         LottoGame lottoGame = new LottoGame(new LottoTickets(tickets));
-        GameResult gameResult = lottoGame.gameStart(new WinningLottoTicket("1, 2, 3, 4, 5, 6", 7));
+        GameResult gameResult = lottoGame.gameStart(LottoFactory.createWinningLottoTicket("1, 2, 3, 4, 5, 6", 7));
 
         // 기대하는 결과 생성 (2등, 꼴등)
         Map<Rank, Integer> expectedStats = Map.of(
diff --git a/src/test/java/lotto/LottoNumberTest.java b/src/test/java/lotto/LottoNumberTest.java
index fdb1c0c7c1..3c2ca67eb9 100644
--- a/src/test/java/lotto/LottoNumberTest.java
+++ b/src/test/java/lotto/LottoNumberTest.java
@@ -3,6 +3,8 @@
 import lotto.domain.LottoNumber;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import java.util.List;
 
@@ -11,21 +13,15 @@
 
 public class LottoNumberTest {
 
-    @Test
     @DisplayName("로또 번호는 1에서 45 사이여야 한다. ")
-    void validateNumber_1보다작은수(){
-        assertThatThrownBy(() -> LottoNumber.of(0))
+    @ParameterizedTest
+    @ValueSource(ints = {0, 46})
+    void validateNumber(int number){
+        assertThatThrownBy(() -> LottoNumber.of(number))
                 .isInstanceOf(IllegalArgumentException.class)
                 .hasMessageContaining("로또 번호는 1에서 45 사이여야 합니다.");
     }
 
-    @Test
-    @DisplayName("로또 번호는 1에서 45 사이여야 한다. ")
-    void validateNumber_45보다큰수(){
-        assertThatThrownBy(() -> LottoNumber.of(46))
-                .isInstanceOf(IllegalArgumentException.class)
-                .hasMessageContaining("로또 번호는 1에서 45 사이여야 합니다.");
-    }
     @Test
     @DisplayName("셔플된 번호 리스트의 크기는 45개이다.")
     void testShuffledNumbersSize() {
diff --git a/src/test/java/lotto/LottoTicketTest.java b/src/test/java/lotto/LottoTicketTest.java
index ab97b1976b..337d4c30d9 100644
--- a/src/test/java/lotto/LottoTicketTest.java
+++ b/src/test/java/lotto/LottoTicketTest.java
@@ -2,15 +2,17 @@
 
 import lotto.domain.LottoNumber;
 import lotto.domain.LottoTicket;
+import lotto.factory.LottoFactory;
 import lotto.strategy.AutoLottoStrategy;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
 
-import static lotto.LottoTestUtils.createLottoTicket;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class LottoTicketTest {
 
@@ -51,5 +53,23 @@ void validateLottoNumbers_withLessThanSixNumbers() {
                 .hasMessageContaining("로또 번호는 중복되지 않은 6개의 숫자여야 합니다.");
     }
 
+    @Test
+    @DisplayName("contains()는 LottoNumber가 리스트에 포함되어 있는지 확인한다")
+    void contains_LottoNumberExists() {
+        String input = "1, 2, 3, 4, 5, 6";
+        LottoTicket lottoTicket = LottoFactory.createLottoTicket(input);
+        LottoNumber targetNumber = LottoNumber.of(3);
+
+        assertTrue(lottoTicket.contains(targetNumber));
+    }
 
+    @Test
+    @DisplayName("contains()는 LottoNumber가 리스트에 포함되어 있지 않으면 false를 반환한다")
+    void contains_LottoNumberDoesNotExist() {
+        String input = "1, 2, 3, 4, 5, 6";
+        LottoTicket lottoTicket = LottoFactory.createLottoTicket(input);
+        LottoNumber targetNumber = LottoNumber.of(7);
+
+        assertFalse(lottoTicket.contains(targetNumber));
+    }
 }
diff --git a/src/test/java/lotto/LottoTicketsTest.java b/src/test/java/lotto/LottoTicketsTest.java
index 094db630d7..677343919b 100644
--- a/src/test/java/lotto/LottoTicketsTest.java
+++ b/src/test/java/lotto/LottoTicketsTest.java
@@ -1,12 +1,21 @@
 package lotto;
 
+import caculator.Operator;
+import lotto.domain.LottoTicket;
 import lotto.domain.LottoTickets;
+import lotto.factory.LottoFactory;
 import lotto.strategy.AutoLottoStrategy;
+import lotto.strategy.LottoStrategy;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
+import java.util.List;
+
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 
 public class LottoTicketsTest {
@@ -25,5 +34,69 @@ void fromNumbers(){
         assertThat(lottoTickets.getLottoTickets().size()).isEqualTo(COUNT);
     }
 
+    @Test
+    @DisplayName("두 LottoTickets 객체를 병합하면 모든 티켓이 포함된 새 객체를 반환한다")
+    void merge() {
+        LottoTicket manualTicket = LottoFactory.createLottoTicket("1,2,3,4,5,6");
+        LottoTicket autoTicket = LottoFactory.createLottoTicket("7,8,9,10,11,12");
+        LottoTickets manual = new LottoTickets(List.of(manualTicket));
+        LottoTickets auto = new LottoTickets(List.of(autoTicket));
+
+        LottoTickets merged = LottoTickets.merge(manual, auto);
+
+        assertThat(merged.getLottoTickets())
+                .hasSize(2)
+                .containsExactly(manualTicket, autoTicket);
+    }
+
+    @Test
+    @DisplayName("purchase()는 수동 구매 개수와 수동 구매 번호를 입력받아 나머지 자동 로또를 구매하여 총 로또 티켓을 반환한다.")
+    void purchase_ValidInput() {
+        int payment = 5000; // 총 금액
+        int manualCount = 2; // 수동 티켓 개수
+        LottoStrategy autoStrategy = new AutoLottoStrategy(); // 자동 생성 전략
+
+        LottoTickets lottoTickets = LottoTickets.purchase(payment, manualCount, List.of("1,2,3,4,5,6", "7,8,9,10,11,12") ,autoStrategy);
+
+        assertEquals(payment / LottoTickets.LOTTO_PRICE, lottoTickets.getLottoTickets().size()); // 총 티켓수 확인
+    }
+
+    @Test
+    @DisplayName("purchase()는 가격보다 수동 구매 개수가 많으면 오류를 반환한다.")
+    void purchase_ValidManualCount() {
+        int payment = 1000; // 총 금액
+        int manualCount = 2; // 수동 티켓 개수
+        LottoStrategy autoStrategy = new AutoLottoStrategy(); // 자동 생성 전략
+
+        assertThatIllegalArgumentException().isThrownBy(() -> {
+            LottoTickets.purchase(payment, manualCount, List.of("1,2,3,4,5,6", "7,8,9,10,11,12") ,autoStrategy);
+        });
+
+    }
+
+    @Test
+    @DisplayName("getLottoTickets()는 생성된 모든 티켓을 반환한다")
+    void getLottoTickets_ReturnsAllTickets() {
+        // Given
+        List<LottoTicket> tickets = List.of(
+                LottoFactory.createLottoTicket("1,2,3,4,5,6"),
+                LottoFactory.createLottoTicket("7,8,9,10,11,12")
+        );
+        LottoTickets lottoTickets = new LottoTickets(tickets);
 
+        assertThat(lottoTickets.getLottoTickets().size()).isEqualTo(tickets.size());
+        assertThat(lottoTickets.getLottoTickets()).isEqualTo(tickets);
+    }
+
+    @Test
+    @DisplayName("getCount()는 생성된 티켓의 개수를 반환한다")
+    void getCount_ReturnsCorrectNumberOfTickets() {
+        List<LottoTicket> tickets = List.of(
+                LottoFactory.createLottoTicket("1,2,3,4,5,6"),
+                LottoFactory.createLottoTicket("7,8,9,10,11,12")
+        );
+        LottoTickets lottoTickets = new LottoTickets(tickets);
+
+        assertThat(lottoTickets.getCount()).isEqualTo(2);
+    }
 }
diff --git a/src/test/java/lotto/ManualLottoStrategyTest.java b/src/test/java/lotto/ManualLottoStrategyTest.java
new file mode 100644
index 0000000000..23eeafb3cd
--- /dev/null
+++ b/src/test/java/lotto/ManualLottoStrategyTest.java
@@ -0,0 +1,20 @@
+package lotto;
+
+import lotto.domain.LottoTicket;
+import lotto.factory.LottoFactory;
+import lotto.strategy.ManualLottoStrategy;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+public class ManualLottoStrategyTest {
+    @Test
+    @DisplayName("로또 수동 생성시 입력받은 로또 번호와 동일한 번호로 로또를 생성한다.")
+    void manualLottoStrategy(){
+        LottoTicket lottoTicket = LottoFactory.createLottoTicket("1,2,3,4,5,6");
+        ManualLottoStrategy strategy = new ManualLottoStrategy(LottoFactory.createLottoTicket("1,2,3,4,5,6"));
+        assertThat(strategy.generateLottoNumbers()).isEqualTo(lottoTicket.getLottoTicket());
+    }
+
+}
diff --git a/src/test/java/lotto/RankTest.java b/src/test/java/lotto/RankTest.java
index b50a507d21..b87ca74cb6 100644
--- a/src/test/java/lotto/RankTest.java
+++ b/src/test/java/lotto/RankTest.java
@@ -2,10 +2,13 @@
 
 import lotto.domain.Rank;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 
 public class RankTest {
+
     @Test
     void rank_1등(){
         assertThat(Rank.getRankByMatchCount(6, false)).isEqualTo(Rank.FIRST);
@@ -30,9 +33,9 @@ public class RankTest {
     void rank_5등(){
         assertThat(Rank.getRankByMatchCount(3, false)).isEqualTo(Rank.FIFTH);
     }
-
-    @Test
-    void rank_등수없음(){
-        assertThat(Rank.getRankByMatchCount(2, false)).isEqualTo(Rank.MISS);
+    @ParameterizedTest
+    @ValueSource(ints = {0, 1, 2})
+    void rank_등수없음(int number){
+        assertThat(Rank.getRankByMatchCount(number, false)).isEqualTo(Rank.MISS);
     }
 }
diff --git a/src/test/java/lotto/WinningLottoTicketTest.java b/src/test/java/lotto/WinningLottoTicketTest.java
index 8b2ad3854e..59bdcd0ac3 100644
--- a/src/test/java/lotto/WinningLottoTicketTest.java
+++ b/src/test/java/lotto/WinningLottoTicketTest.java
@@ -2,6 +2,7 @@
 
 import lotto.domain.LottoTicket;
 import lotto.domain.WinningLottoTicket;
+import lotto.factory.LottoFactory;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
@@ -14,38 +15,38 @@ public class WinningLottoTicketTest {
 
     @Test
     public void winningLottoTicket_정상() {
-        WinningLottoTicket ticket = new WinningLottoTicket("1,2,3,4,5,6", 7);
-        assertThat(ticket.matchLottoNumbers(createLottoTicket(List.of(1,2,3,4,5,6)))).isEqualTo(6);
-        assertThat(ticket.hasBonusMatch(createLottoTicket(List.of(1,2,3,4,5,6)))).isFalse();
+        WinningLottoTicket ticket = LottoFactory.createWinningLottoTicket("1,2,3,4,5,6", 7);
+        assertThat(ticket.matchLottoNumbers(createLottoTicket(List.of(1, 2, 3, 4, 5, 6)))).isEqualTo(6);
+        assertThat(ticket.hasBonusMatch(createLottoTicket(List.of(1, 2, 3, 4, 5, 6)))).isFalse();
     }
 
     @Test
     public void winningLottoTicket_6개숫자() {
-        assertThatThrownBy(() -> new WinningLottoTicket("1,2,3,4,5,6,7", 8))
+        assertThatThrownBy(() -> LottoFactory.createWinningLottoTicket("1,2,3,4,5,6,7", 8))
                 .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
     public void winningLottoTicket_보너스숫자중복() {
-        assertThatThrownBy(() -> new WinningLottoTicket("1,2,3,4,5,6", 6))
+        assertThatThrownBy(() -> LottoFactory.createWinningLottoTicket("1,2,3,4,5,6", 6))
                 .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
-    void matchLottoNumbers(){
-        WinningLottoTicket ticket = new WinningLottoTicket("1,2,3,4,5,6", 7);
-        assertThat(ticket.matchLottoNumbers(createLottoTicket(List.of(1,2,3,4,5,6)))).isEqualTo(6);
+    void matchLottoNumbers() {
+        WinningLottoTicket ticket = LottoFactory.createWinningLottoTicket("1,2,3,4,5,6", 7);
+        assertThat(ticket.matchLottoNumbers(createLottoTicket(List.of(1, 2, 3, 4, 5, 6)))).isEqualTo(6);
     }
 
     @Test
-    void hasBonusMatch_false(){
-        WinningLottoTicket ticket = new WinningLottoTicket("1,2,3,4,5,6", 7);
-        assertThat(ticket.hasBonusMatch(createLottoTicket(List.of(1,2,3,4,5,6)))).isFalse();
+    void hasBonusMatch_false() {
+        WinningLottoTicket ticket = LottoFactory.createWinningLottoTicket("1,2,3,4,5,6", 7);
+        assertThat(ticket.hasBonusMatch(createLottoTicket(List.of(1, 2, 3, 4, 5, 6)))).isFalse();
     }
 
     @Test
-    void hasBonusMatch_true(){
-        WinningLottoTicket ticket = new WinningLottoTicket("1,2,3,4,5,6", 7);
-        assertThat(ticket.hasBonusMatch(createLottoTicket(List.of(1,2,3,4,5,7)))).isTrue();
+    void hasBonusMatch_true() {
+        WinningLottoTicket ticket = LottoFactory.createWinningLottoTicket("1,2,3,4,5,6", 7);
+        assertThat(ticket.hasBonusMatch(createLottoTicket(List.of(1, 2, 3, 4, 5, 7)))).isTrue();
     }
 }
diff --git "a/\353\241\234\353\230\220_\354\232\224\352\265\254\354\202\254\355\225\255.md" "b/\353\241\234\353\230\220_\354\232\224\352\265\254\354\202\254\355\225\255.md"
index af51df0d34..e8ee438f24 100644
--- "a/\353\241\234\353\230\220_\354\232\224\352\265\254\354\202\254\355\225\255.md"
+++ "b/\353\241\234\353\230\220_\354\232\224\352\265\254\354\202\254\355\225\255.md"
@@ -18,6 +18,9 @@
 - [x] Rank : 보너스 매치 여부 추가
 - [x] 보너스 볼을 포함한 당첨 기능 구현
 - [x] 보너스 볼을 포함한 출력 기능 구현
+- [x] 입력한 금액, 수동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.
+- [x] 수동 생성 개수와 자동 생성 개수를 출력한다.
+- [x] 수동 생성 번호와 자동 생성 번호를 합쳐서 통계 계산 및 수익율을 계산한다.
 
 ## 개발 환경
 


Reviews:
user: login: testrace
body: 안녕하세요 지영님 😃 
4단계도 잘 구현해 주셨네요 👍 
몇가지 코멘트 남겨두었는데 확인해 주시고 다시 리뷰 요청해 주세요.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-05T02:11:41Z
user: login: testrace
body: 안녕하세요 지영님 😃 
피드백 반영 잘해주셨네요 👍 
조금만 다듬고 마무리하면 좋을 것 같습니다.
남겨둔 코멘트 확인해 주시고 다시 리뷰 요청해 주세요.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-07T10:34:49Z
user: login: testrace
body: 안녕하세요 지영님 😃 
피드백 반영 잘해주셨네요 👍 
로또 미션은 여기서 마무리 하겠습니다.
소소하게 코멘트 남겨두었는데 참고해 주세요.
다음 미션도 화이팅입니다 🔥 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T14:16:25Z

Review comments:
diff_hunk: @@ -16,6 +17,10 @@ public LottoTicket(List<LottoNumber> lottoNumbers) {
         this.lottoNumbers = lottoNumbers;
     }
 
+    public LottoTicket(String input) {
+        this(createLottoTicket(input));
+    }
path: src/main/java/lotto/domain/LottoTicket.java
user: login: testrace
body: 문자열을 직접 받아 수동 생성해주셨네요 👍 
LottoStrategy 라는 인터페이스가 있으니 ManualLottoStrategy 라는 구현체를 고려해 보셔도 좋을 것 같아요
created_at: 2025-04-05T01:59:13Z
author_association: MEMBER
diff_hunk: @@ -61,5 +66,16 @@ private double calculateReturnRate(Map<Rank, Integer> results) {
         return (double) totalWon / totalSpent;
     }
 
+    public void addTicket(LottoTicket lottoTicket) {
+        lottoTickets.add(lottoTicket);
+    }
path: src/main/java/lotto/domain/LottoTickets.java
user: login: testrace
body: 단위 테스트가 있다면 좋을 것 같아요.
이 메서드 외에도 모든 public 메서드에 누락된 단위 테스트가 없는지 검토해 보시면 좋을 것 같아요.
created_at: 2025-04-05T02:01:41Z
author_association: MEMBER
diff_hunk: @@ -8,15 +8,25 @@ public class WinningLottoTicket {
     private final List<LottoNumber> lottoNumbers;
     private final LottoNumber bonusNumber;
 
-    public WinningLottoTicket(final List<LottoNumber>  lottoNumbers, final LottoNumber bonusNumber) {
+    public WinningLottoTicket(final List<LottoNumber> lottoNumbers, final LottoNumber bonusNumber) {
         validateNumbers(lottoNumbers, bonusNumber);
         this.lottoNumbers = lottoNumbers;
         this.bonusNumber = bonusNumber;
     }
-    public WinningLottoTicket(final String lottoNumbers,final int bonusNumber) {
+
+    public WinningLottoTicket(final String lottoNumbers, final int bonusNumber) {
         this(createWinningTicket(lottoNumbers), LottoNumber.of(bonusNumber));
     }
 
+    private static List<LottoNumber> createWinningTicket(String input) {
+        List<LottoNumber> lottoNumbers = Arrays.stream(input.trim().split(","))
+                .map(String::trim)        // 각 번호의 공백 제거
+                .map(Integer::parseInt)   // 문자열을 숫자로 변환
+                .map(LottoNumber::of)     // 숫자를 LottoNumber로 변환
+                .collect(Collectors.toList()); // List<LottoNumber>로 수집
+        return lottoNumbers;
+    }
path: src/main/java/lotto/domain/WinningLottoTicket.java
user: login: testrace
body: LottoTicket 에서 흡사한 메서드가 존재하네요. 저는 중복된 코드라고 생각하는데요.
WIningLottoTicket 내부 변수 lottoNumbers의 타입이 'List' 가 아니라 'LottoTicket' 이면 어떨까요?
당첨번호6개와 로또번호6개는 제약사항이 동일하니 중복 코드를 제거할 수 있을 것 같아요.
created_at: 2025-04-05T02:05:16Z
author_association: MEMBER
diff_hunk: @@ -16,6 +19,22 @@ public static int inputLottoPrice() {
         return SCANNER.nextInt();
     }
 
+    public static int inputManualLottoCount() {
+        System.out.println("수동으로 구매할 로또 수를 입력해 주세요.");
+        return SCANNER.nextInt();
+    }
+
+    public static LottoTickets inputManualLottoTickets(int count) {
+        System.out.println("수동으로 구매할 번호를 입력해 주세요.");
+        String test = SCANNER.nextLine();
+        LottoTickets lottoTickets = new LottoTickets();
+        for(int i = 0; i < count; i++) {
+            String input = SCANNER.nextLine();
+            lottoTickets.addTicket(new LottoTicket(input));
path: src/main/java/lotto/ui/InputView.java
user: login: testrace
body: ui 에서 객체를 생성하다보니 TDD로 진행하더라도 addTicket 메서드에 대한 단위 테스트가 누락되었을 수 있을 것 같아요.
지금 구조가 나쁜 것은 아니지만 ui는 입력/출력 자체에만 집중하고 객체 생성이나 호출 등은 모두 컨트롤러가 수행할 수 있는 구조도 고려해 보시면 좋을 것 같아요.
created_at: 2025-04-05T02:10:49Z
author_association: MEMBER
diff_hunk: @@ -39,15 +45,24 @@ public static LottoTicket generateLottoNumbers(LottoStrategy lottoStrategy) {
         return new LottoTicket(generatedNumbers);
     }
 
-    private static List<LottoNumber> createLottoTicket(String input) {
+    static List<LottoNumber> createLottoTicket(String input) {
path: src/main/java/lotto/domain/LottoTicket.java
user: login: testrace
body: 테스트코드에서 문자열을 직접 입력 받아 생성하는 로직이 많이 존재하는데요
수동 생성이 가능한 전략이 생겼으니 테스트 코드도 모두 ManualLottoFactory 를 활용해 생성하면 어떨까요?
created_at: 2025-04-07T10:28:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,29 @@
+package lotto.factory;
+
+import lotto.domain.LottoNumber;
+import lotto.domain.LottoTicket;
+import lotto.domain.LottoTickets;
+import lotto.strategy.ManualLottoStrategy;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ManualLottoFactory {
path: src/main/java/lotto/factory/ManualLottoFactory.java
user: login: testrace
body: 👍 
created_at: 2025-04-07T10:30:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,25 @@
+package lotto.strategy;
+
+import lotto.domain.LottoNumber;
+import lotto.domain.LottoTicket;
+
+import java.util.List;
+
+public class ManualLottoStrategy implements LottoStrategy {
+
+    private final List<LottoNumber> lottoNumbers;
+
+    public ManualLottoStrategy(List<LottoNumber> numbers) {
+        this.lottoNumbers = numbers;
+
path: src/main/java/lotto/strategy/ManualLottoStrategy.java
user: login: testrace
body: ```suggestion
```
created_at: 2025-04-07T10:31:01Z
author_association: MEMBER
diff_hunk: @@ -43,4 +45,41 @@ void merge() {
                 .containsExactly(manualTicket, autoTicket);
     }
 
+    @Test
+    @DisplayName("purchase()는 자동 로또 티켓을 입력받은 payment에서 수동 티켓 개수를 제외하고 생성한다")
path: src/test/java/lotto/LottoTicketsTest.java
user: login: testrace
body: 👍 
created_at: 2025-04-07T10:32:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,33 @@
+package lotto;
+
+import lotto.domain.LottoNumber;
+import lotto.domain.LottoTicket;
+import lotto.factory.ManualLottoFactory;
+import lotto.strategy.ManualLottoStrategy;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+public class ManualLottoTest {
path: src/test/java/lotto/ManualLottoTest.java
user: login: testrace
body: 일반적으로 테스트 클래스명은 '클래스명+Test' 입니다.
ManualLotto 라는 클래스는 없으니 이름을 적절하게 변경해 보시면 좋을 것 같아요.
created_at: 2025-04-07T10:33:31Z
author_association: MEMBER
diff_hunk: @@ -2,16 +2,21 @@
 
 import lotto.domain.LottoGame;
 import lotto.domain.LottoTickets;
+import lotto.factory.ManualLottoFactory;
 import lotto.strategy.AutoLottoStrategy;
+import lotto.strategy.ManualLottoStrategy;
 import lotto.ui.InputView;
 import lotto.ui.ResultView;
 
+import java.util.List;
+
 public class LottoMain {
path: src/main/java/lotto/LottoMain.java
user: login: testrace
body: 애플리케이션을 실행해보면 매끄럽게 진행이 안되고 있어요 😢 
<img width="463" alt="image" src="https://github.com/user-attachments/assets/9d80f30f-e99d-4d89-a93e-b5ab1062834b" />

created_at: 2025-04-07T10:34:19Z
author_association: MEMBER
diff_hunk: @@ -24,9 +25,30 @@ public static LottoTickets fromNumbers(int count, LottoStrategy lottoStrategy) {
         return new LottoTickets(tickets);
     }
 
-    public static LottoTickets purchase(int payment, int manualCount, LottoStrategy lottoStrategy) {
+    public static LottoTickets purchase(int payment, int manualCount, List<String> manualLottoInputs, LottoStrategy lottoStrategy) {
path: src/main/java/lotto/domain/LottoTickets.java
user: login: testrace
body: 인자가 늘어난 만큼 코드도 많이 추가가 되었네요.
개인적으로 하나의 클래스에 코드가 많아진다면 분리할 순 없을까 고민하는 시점이기도 합니다.
`LottoMachine.purchase` 

created_at: 2025-04-09T14:03:15Z
author_association: MEMBER
diff_hunk: @@ -19,11 +20,31 @@ public static LottoTickets createTickets(List<String> inputs) {
         return new LottoTickets(tickets);
     }
 
+    public static LottoTicket createLottoTicket(String input) {
+        List<LottoNumber> lottoNumbers = Arrays.stream(input.trim().split(","))
+                .map(String::trim)
+                .map(Integer::parseInt)
+                .map(LottoNumber::of)
+                .collect(Collectors.toList());
+        return new LottoTicket(lottoNumbers); // LottoTicket 객체 생성 후 반환
+    }
+
+    public static WinningLottoTicket createWinningLottoTicket(String input, int bonusNumber) {
+        List<LottoNumber> lottoNumbers = Arrays.stream(input.trim().split(","))
+                .map(String::trim)
+                .map(Integer::parseInt)
+                .map(LottoNumber::of)
+                .collect(Collectors.toList());
path: src/main/java/lotto/factory/LottoFactory.java
user: login: testrace
body: parseLottoNumbers 메서드를 활용하면 중복을 제거할 수 있겠네요.
created_at: 2025-04-09T14:03:23Z
author_association: MEMBER
diff_hunk: @@ -46,24 +50,37 @@ void merge() {
     }
 
     @Test
-    @DisplayName("purchase()는 자동 로또 티켓을 입력받은 payment에서 수동 티켓 개수를 제외하고 생성한다")
+    @DisplayName("purchase()는 수동 구매 개수와 수동 구매 번호를 입력받아 나머지 자동 로또를 구매하여 총 로또 티켓을 반환한다.")
     void purchase_ValidInput() {
         int payment = 5000; // 총 금액
         int manualCount = 2; // 수동 티켓 개수
         LottoStrategy autoStrategy = new AutoLottoStrategy(); // 자동 생성 전략
 
-        LottoTickets lottoTickets = LottoTickets.purchase(payment, manualCount, autoStrategy);
+        LottoTickets lottoTickets = LottoTickets.purchase(payment, manualCount, List.of("1,2,3,4,5,6", "7,8,9,10,11,12") ,autoStrategy);
+
+        assertEquals(payment / LottoTickets.LOTTO_PRICE, lottoTickets.getLottoTickets().size()); // 총 티켓수 확인
path: src/test/java/lotto/LottoTicketsTest.java
user: login: testrace
body: 테스트하고자 하는 메서드 내부의 로직을 그대로 차용한다면 테스트의 의미가 없는 것 같아요.
실제 결과 값(하드코딩)으로 검증하면 좋을 것 같아요.
created_at: 2025-04-09T14:06:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,20 @@
+package lotto;
+
+import lotto.domain.LottoTicket;
+import lotto.factory.LottoFactory;
+import lotto.strategy.ManualLottoStrategy;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+public class ManualLottoStrategyTest {
+    @Test
+    @DisplayName("로또 수동 생성시 입력받은 로또 번호와 동일한 번호로 로또를 생성한다.")
+    void manualLottoStrategy(){
+        LottoTicket lottoTicket = LottoFactory.createLottoTicket("1,2,3,4,5,6");
+        ManualLottoStrategy strategy = new ManualLottoStrategy(LottoFactory.createLottoTicket("1,2,3,4,5,6"));
+        assertThat(strategy.generateLottoNumbers()).isEqualTo(lottoTicket.getLottoTicket());
path: src/test/java/lotto/ManualLottoStrategyTest.java
user: login: testrace
body: 👍 
객체간의 비교도 가능하겠네요.
created_at: 2025-04-09T14:07:11Z
author_association: MEMBER

PR comments:
user: login: jiyoung928
created_at: 2025-04-06T09:29:15Z
body: 안녕하세요 리뷰어님! 
리뷰 해주신거 참고해서 ManualLottoStrategy 구현체를 활용하여 다시 구현해봤습니다~
테스트 코드가 아직 부족하지만 LottoTicket와 LottoTickets에 조금 추가해봤습니다!
이번 리뷰도 잘부탁드립니다~
author_association: NONE
user: login: jiyoung928
created_at: 2025-04-09T01:26:05Z
body: 안녕하세요 리뷰어님!
ManualLottoFactory를 LottoFactory로 변경하고 LottoTicket과 WinningLottoTicket 생성 로직을 LottoFactory로 이관하였습니다!
기존에는 LottoMain에서 ManualLottoFactory를 통해 수동 생성 로또를 반환 받고 purchase 함수를 통해 자동 생성 로또를 반환 받아 이걸 merge하여 LottoGame 객체를 생성하였는데요, purchase 함수 내부에서 LottoFactory를 호출하여 수동 생성 로또와 자동 생성 로또를 merge하여 반환 하도록 수정해봤습니다! 
또 수동 생성 로또 개수에대한 검증이 안되었던것 같아서 LottoTickets에 해당 검증 로직도 추가해봤습니다!
이번 리뷰도 잘 부탁드립니다~~
author_association: NONE