Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6cbdbef
feat: 로또 구입금액을 입력받은 후 입력 금액을 검증하는 기능 구현
junseoplee Jan 8, 2024
7276e90
junseoplee Jan 9, 2024
b6f34e0
Revert "경"
junseoplee Jan 9, 2024
5fb75e0
refactor: 패키지, 클래스 구분 및 생성, 구입금액 입력 및 유효성 검사 기능 재구현
junseoplee Jan 9, 2024
0bf4e6f
feat: 로또 번호를 생성 및 출력하는 기능 구현.
junseoplee Jan 10, 2024
93faf63
fix: 정렬이 제대로 되지 않는 문제를 해결, 변수명을 명확하게 변경
junseoplee Jan 10, 2024
14f83cd
fix: 실행결과를 양식에 맞게 수정
junseoplee Jan 10, 2024
c3ec26b
feat: 당첨 번호와 보너스 번호를 입력 받아 객체를 생성, 유효성 검사를 진행하는 기능 구현
junseoplee Jan 10, 2024
96f6737
fix: 불필요한 import 삭제
junseoplee Jan 11, 2024
0ccdc47
feat: 결과 출력 기능 구현
junseoplee Jan 11, 2024
e4cf662
feat: 구입금액이 숫자인지 검증하는 기능 구현
junseoplee Jan 12, 2024
c2c7bc9
feat: 수익률 계산 기능 구현
junseoplee Jan 12, 2024
68d01e7
docs: 구현할 기능 목록 정리
junseoplee Jan 13, 2024
cb5e447
docs: 구현할 기능 목록 정리
junseoplee Jan 15, 2024
177b560
refactor: 패키지와 클래스 구분 및 리팩토링, 보너스 번호 검사가 제대로 동작하지 않는 오류 수정
junseoplee Jan 15, 2024
c12be99
fix: getter의 위치 등 컨벤션 수정
junseoplee Jan 16, 2024
2b5fd6f
fix: 변수명 수정
junseoplee Jan 16, 2024
c0fda34
fix: 구현 수정
junseoplee Jan 17, 2024
1829f9c
docs: 구현할 기능 목록 정리
junseoplee Jan 17, 2024
32f7bd4
fix: WinningCheck enum의 상금을 String에서 int로 변경
junseoplee Jan 17, 2024
d62f6cd
fix: 불필요한 변수 및 주석 제거
junseoplee Jan 18, 2024
e22152e
docs: 해결한 문제 항목 삭제
junseoplee Jan 18, 2024
3e0d8fc
docs: 오타 수정
junseoplee Jan 18, 2024
76224f2
fix: 단위 구분을 위해 '_' 삽입
junseoplee Jan 21, 2024
58013f8
refactor: Calculator 클래스 책임 분리 및 수정
junseoplee Jan 21, 2024
c331b18
fix: 필요 없는 static 삭제와 안정성을 위한 final 추가
junseoplee Jan 21, 2024
ea0ea00
refactor: PurchaseAmount 클래스와 InputManager 리팩토링
junseoplee Jan 21, 2024
71be87d
fix: 클래스명을 명사형으로 변경
junseoplee Jan 21, 2024
9aa81c9
fix: 리팩토링된 항목들을 수정
junseoplee Jan 21, 2024
3ae6d9e
refactor: 에러 메세지를 enum에서 관리
junseoplee Jan 21, 2024
63bf89d
feat: 도메인 테스트 코드 구현
junseoplee Jan 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
Application과 동일 선상에 RunLotto 클래스 생성

RunLotto에 필요한 기능들을 패키지로 묶어보자
LottoManager에 필요한 기능들을 패키지로 묶어보자

- 도메인 모델 패키지 (domain)
- 사용자로부터 입력받은 구입금액을 저장하는 클래스 PurchaseAmount
- 난수 생성된 로또 번호가 있는 클래스 Lotto
- 사용자로부터 로또 번호 6개와 보너스 번호 1개를 입력받아 저장하는 클래스 UserInputNumbers
- 상금과 당첨 수를 저장하는 열거형 enum WinningCheck
- 비즈니스 로직을 수행하는 서비스 패키지 (service)
- 당첨 결과와 수익률을 계산하는 클래스 Calculator
- 당첨 결과를 계산하는 클래스 LottoResultCalculator
- 수익률을 계산하는 클래스 LottoProfitRateCalculator
- 구입금액만큼 로또를 발행하는 클래스 LottoGenerator
- 각종 메서드가 있는 패키지 (util)
- 사용자로부터 값들을 입력받는 클래스 InputManager
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class Application {

public static void main(String[] args) {
RunLotto runLotto = new RunLotto();
runLotto.runLotto();
LottoManager lottoManager = new LottoManager();
lottoManager.runLotto();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import lotto.util.OutputManager;
import lotto.domain.WinningCheck;

public class RunLotto {
public class LottoManager {

private final InputManager inputManager = new InputManager();
private final LottoGenerator lottoGenerator = new LottoGenerator();
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lotto.util.ErrorMessage;

public class Lotto {

Expand All @@ -28,18 +29,18 @@ public boolean isBonusMatch(UserInputNumbers receivedLotto) {

private void validateLottoNumbers(List<Integer> lottoNumbers) {
if (lottoNumbers.size() != SIZE_OF_LOTTO) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 총 6개여야 합니다.");
throw new IllegalArgumentException(ErrorMessage.NOT_SIX_NUMBERS.getMessage());
}

for (int number : lottoNumbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
throw new IllegalArgumentException(ErrorMessage.NOT_IN_RANGE.getMessage());
}
}

Set<Integer> uniqueNumbers = new HashSet<>(lottoNumbers);
if (uniqueNumbers.size() < lottoNumbers.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복이 없어야 합니다.");
throw new IllegalArgumentException(ErrorMessage.MUST_NOT_DUPLICATE.getMessage());
}
}

Expand Down
26 changes: 9 additions & 17 deletions src/main/java/lotto/domain/PurchaseAmount.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
package lotto.domain;

import lotto.util.ErrorMessage;

public class PurchaseAmount {

public static final int LOTTO_PRICE = 1000;
private int amount;

public PurchaseAmount(String input) {
validateIsNumeric(input);
int inputAmount = Integer.parseInt(input);
validatePurchaseAmount(amount);
this.amount = inputAmount;
}
private final int purchaseAmount;

private void validateIsNumeric(String inputAmount) {
try {
Integer.parseInt(inputAmount);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 구입금액은 숫자여야합니다.");
}
public PurchaseAmount(int inputAmount) {
validatePurchaseAmount(inputAmount);
this.purchaseAmount = inputAmount;
}

private void validatePurchaseAmount(int inputAmount) {
if (inputAmount % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("[ERROR] 1000 단위의 금액을 입력하세요.");
throw new IllegalArgumentException(ErrorMessage.NOT_A_UNIT_OF_1000.getMessage());
}
}

public int getAmount() {
return amount;
public int getPurchaseAmount() {
return purchaseAmount;
}
}
11 changes: 6 additions & 5 deletions src/main/java/lotto/domain/UserInputNumbers.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lotto.util.ErrorMessage;

public class UserInputNumbers {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드는 Validation 만 수행하고 있는 클래스네요🤔 이 클래스의 책임으로 줄 수 있는 건 뭘까요!?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자신의 역할(유효성 검사, 저장, 제공)을 훌륭히 해내고 있는 클래스라고 생각합니다!
추가적인 책임을 주고싶지는 않습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다ㅋㅋㅋㅋ 사실 저도 요구사항 자체가 간단해서 책임 할당은 이 정도로 충분하다고 생각해요 🧐


Expand All @@ -22,28 +23,28 @@ public UserInputNumbers(List<Integer> receivedLottoNumbers, int bonusNumber) {

private void validateLottoNumbers(List<Integer> receivedLottoNumbers) {
if (receivedLottoNumbers.size() != SIZE_OF_LOTTO) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 총 6개여야 합니다.");
throw new IllegalArgumentException(ErrorMessage.NOT_SIX_NUMBERS.getMessage());
}

for (int number : receivedLottoNumbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
throw new IllegalArgumentException(ErrorMessage.NOT_IN_RANGE.getMessage());
}
}

Set<Integer> uniqueNumbers = new HashSet<>(receivedLottoNumbers);
if (uniqueNumbers.size() < receivedLottoNumbers.size()) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복이 없어야 합니다.");
throw new IllegalArgumentException(ErrorMessage.MUST_NOT_DUPLICATE.getMessage());
}
}

private void validateBonusNumbers(int bonusNumber) {
if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
throw new IllegalArgumentException(ErrorMessage.NOT_IN_RANGE.getMessage());
}

if (new HashSet<>(receivedLottoNumbers).contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 중복이 없어야 합니다.");
throw new IllegalArgumentException(ErrorMessage.MUST_NOT_DUPLICATE.getMessage());
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/lotto/service/LottoGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public class LottoGenerator {

public static List<Lotto> generateLottos(PurchaseAmount purchaseAmount) {
int numberOfLotto = purchaseAmount.getAmount() / LOTTO_PRICE;
int numberOfLotto = purchaseAmount.getPurchaseAmount() / LOTTO_PRICE;
System.out.println("\n" + numberOfLotto + "개를 구매했습니다.");

List<Lotto> generatedLottos = new ArrayList<>();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/lotto/service/LottoProfitRateCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public double calculateProfitRate(PurchaseAmount purchaseAmount,
.mapToInt(entry -> entry.getKey().getPrizeAmount() * entry.getValue())
.sum();

double profitRate = (totalPrize / purchaseAmount.getAmount()) * PERCENTAGE_FACTOR;
double profitRate = (totalPrize / purchaseAmount.getPurchaseAmount()) * PERCENTAGE_FACTOR;
DecimalFormat df = new DecimalFormat("#.##");
return Double.parseDouble(df.format(profitRate));
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/lotto/util/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lotto.util;

public enum ErrorMessage {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enum으로 관리하는 것 vs 클래스의 상수로 두는 것 어떤 장단점이 있을까요!?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enum으로 관리했을 때의
장점: 관련 있는 상수들을 묶어서 관리할 수 있습니다. 이넘의 메서드를 이용해 기능을 추가할 수 있습니다.
단점: 인스턴스화할 수 없습니다. 동적으로 에러 메세지를 추가하거나 변경할 수 없습니다.

상수로 관리했을 때의
장점: 상수 선언 및 초기화만으로 관리하여 간결해집니다.
단점: 관련 있는 상수들을 묶어서 관리하기 어렵습니다. 모든 상수가 클래스의 필드로 선언되서 구분하기 어렵습니다.

NOT_A_NUMBER("[ERROR] 숫자여야 합니다."),
NOT_A_SINGLE_NUMBER("[ERROR] 보너스 번호는 하나의 숫자여야 합니다."),
NOT_A_UNIT_OF_1000("[ERROR] 1000 단위의 금액이어야 합니다."),
NOT_SIX_NUMBERS("[ERROR] 로또 번호는 총 6개여야 합니다."),
NOT_IN_RANGE("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."),
MUST_NOT_DUPLICATE("[ERROR] 로또 번호는 중복이 없어야 합니다.");

private final String message;

ErrorMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}
44 changes: 29 additions & 15 deletions src/main/java/lotto/util/InputManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,48 @@ public PurchaseAmount receivePurchaseAmount() {
while (true) {
try {
System.out.println("구입금액을 입력해 주세요.");
String input = Console.readLine();
return new PurchaseAmount(input);
int inputAmount = Integer.parseInt(Console.readLine().trim());
return new PurchaseAmount(inputAmount);
} catch (NumberFormatException e) {
System.out.println(ErrorMessage.NOT_A_NUMBER.getMessage());
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}


public UserInputNumbers receiveLottoNumber() {
while (true) {
System.out.println("\n당첨 번호를 입력해 주세요.");
String inputNumbers = Console.readLine();
try {
String[] separatedNumbers = inputNumbers.split(",");
List<Integer> receivedLottoNumbers = Arrays.stream(separatedNumbers)
.map(String::trim)
.map(Integer::parseInt)
.toList();
System.out.println("\n보너스 번호를 입력해 주세요.");
int bonusNumber = Integer.parseInt(Console.readLine().trim());

List<Integer> receivedLottoNumbers = receiveLottoNumbers();
int bonusNumber = receiveBonusNumber();
return new UserInputNumbers(receivedLottoNumbers, bonusNumber);
} catch (NumberFormatException e) {
System.out.println("[ERROR] 보너스 번호는 1개여야 합니다."); // 하나가 아니라면 String으로 인식
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

private List<Integer> receiveLottoNumbers() {
try {
System.out.println("\n당첨 번호를 입력해 주세요.");
String inputNumbers = Console.readLine();
String[] separatedNumbers = inputNumbers.split(",");
return Arrays.stream(separatedNumbers)
.map(String::trim)
.map(Integer::parseInt)
.toList();
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ErrorMessage.NOT_A_NUMBER.getMessage());
}
}

private int receiveBonusNumber() {
try {
System.out.println("\n보너스 번호를 입력해 주세요.");
return Integer.parseInt(Console.readLine().trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ErrorMessage.NOT_A_SINGLE_NUMBER.getMessage());
}
}
}
29 changes: 0 additions & 29 deletions src/test/java/lotto/LottoTest.java

This file was deleted.

59 changes: 59 additions & 0 deletions src/test/java/lotto/domain/LottoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package lotto.domain;

import java.util.Arrays;
import lotto.domain.Lotto;
import lotto.domain.UserInputNumbers;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

class LottoTest {

@DisplayName("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.")
@Test
void createLottoByOverSize() {
assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 6, 7)))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("로또 번호의 범위가 1~45가 아닌 경우 예외가 발생한다.")
@Test
void createLottoByInvalidNumber() {
assertThatThrownBy(() -> new Lotto(List.of(0, 2, 3, 4, 5, 6)))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 46)))
.isInstanceOf(IllegalArgumentException.class);
}
Comment on lines +16 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

경계값 테스트 매우 훌륭하네요..
지금은 공감이 안되겠지만 아마 한 4개월 쯤 뒤면 테스트코드 없이는 코드를 불안해서 못 짜는 사람이 되실 겁니다 그때 이 댓글 성지순례 한 번 하러 오세요ㅋㅋㅋㅋ


@DisplayName("로또 번호에 중복된 숫자가 있는 경우 예외가 발생한다.")
@Test
void createLottoByDuplicatedNumber() {
// TODO: 이 테스트가 통과할 수 있게 구현 코드 작성
assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5)))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("getMatchCount()는 로또 번호와 일치하는 번호의 수를 반환한다.")
@Test
void testGetMatchCount() {
List<Integer> lottoNumbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Lotto lotto = new Lotto(lottoNumbers);
UserInputNumbers userInputNumbers = new UserInputNumbers(lottoNumbers, 7);
int matchCount = lotto.getMatchCount(userInputNumbers);
assertThat(matchCount).isEqualTo(6);
}

@DisplayName("isBonusMatch()는 로또 번호와 보너스 번호가 일치하면 true를 반환한다.")
@Test
void testIsBonusMatch() {
List<Integer> lottoNumbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Lotto lotto = new Lotto(lottoNumbers);
UserInputNumbers userInputNumbers = new UserInputNumbers(lottoNumbers, 7);
boolean isBonusMatch = lotto.isBonusMatch(userInputNumbers);
assertThat(isBonusMatch).isFalse(); // 보너스 번호는 7이므로 일치하지 않아야 함
}
}
16 changes: 16 additions & 0 deletions src/test/java/lotto/domain/PurchaseAmountTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package lotto.domain;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class PurchaseAmountTest {

@DisplayName("구입금액이 1000 단위가 아니라면 예외가 발생한다.")
@Test
void createPurchaseAmountByInvalidAmount() {
assertThatThrownBy(() -> new PurchaseAmount(1500))
.isInstanceOf(IllegalArgumentException.class);
}
}
33 changes: 33 additions & 0 deletions src/test/java/lotto/domain/UserInputNumbersTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package lotto.domain;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class UserInputNumbersTest {

@DisplayName("당첨 번호의 개수가 6개를 넘어가는 경우 예외가 발생한다.")
@Test
void createNumberByOverSize() {
assertThatThrownBy(() -> new UserInputNumbers(List.of(1, 2, 3, 4, 5, 6, 7), 8))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("당첨 번호의 범위가 1~45가 아닌 경우 예외가 발생한다.")
@Test
void createNumberByInvalidNumber() {
assertThatThrownBy(() -> new UserInputNumbers(List.of(0, 2, 3, 4, 5, 6), 7))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> new UserInputNumbers(List.of(1, 2, 3, 4, 5, 46), 7))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("당첨 번호에 중복된 숫자가 있는 경우 예외가 발생한다.")
@Test
void createNumberByDuplicatedNumber() {
assertThatThrownBy(() -> new UserInputNumbers(List.of(1, 2, 3, 4, 5, 5), 7))
.isInstanceOf(IllegalArgumentException.class);
Comment on lines +11 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input(입력), output(출력) 을 도메인로직으로부터 분리하면 이렇게 테스트하기 좋은 코드가 된다는 것을 알려드리고 싶었습니다. 👍👍

}
}