Skip to content
Merged
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## 로또 미션
### 1단계
- [x] 로또 구입
- [x] 숫자 입력이 들어와야 한다.
- [x] 로또 구입 금액은 음수가 될 수 없다.
- [x] 로또 구입 금액은 1000단위이어야 한다.
- [x] 로또 생성
- [x] 로또는 6자리로 구성되어 있다.
- [x] 하나의 로또 내 숫자는 중복될 수 없다.
- [x] 로또 숫자는 1 ~ 45 사이의 숫자로만 구상되어야 한다.
- [x] 화면 출력
- [x] 한 로또 내 숫자는 오름차순으로 출력한다.
15 changes: 15 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import model.LottoGenerator;
import model.Lottos;
import model.LottoPurchaseMoney;
import view.InputView;
import view.OutputView;

public class Application {

public static void main(String[] args) {
final LottoPurchaseMoney lottoPurchaseMoney = InputView.inputMoney();
final LottoGenerator lottoGenerator = new LottoGenerator();
final Lottos lottos = lottoGenerator.generateRandomLotto(lottoPurchaseMoney);
OutputView.showLotto(lottos);
}
}
50 changes: 50 additions & 0 deletions src/main/java/model/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package model;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Lotto {

private static final int LOTTO_NUMBER_SIZE = 6;
private static final int LOTTO_MIN_NUMBER = 1;
private static final int LOTTO_MAX_NUMBER = 45;

private final List<Integer> numbers;

public Lotto(final List<Integer> numbers) {
validateNumbers(numbers);
this.numbers = numbers;
}

private void validateNumbers(final List<Integer> numbers) {
validateNumbersSize(numbers);
validateDuplicateNumbers(numbers);
validateNumber1to45(numbers);
}

private void validateNumbersSize(final List<Integer> numbers) {
if (numbers.size() != LOTTO_NUMBER_SIZE) {
throw new IllegalArgumentException("로또는 6개의 숫자로 구성되어야 합니다.");
}
}

private void validateDuplicateNumbers(final List<Integer> numbers) {
Set<Integer> notDuplicatedNumbers = new HashSet<>(numbers);
if (notDuplicatedNumbers.size() != numbers.size()) {
throw new IllegalArgumentException("로또 내 동일한 숫자가 있으면 안됩니다.");
}
}

private void validateNumber1to45(final List<Integer> numbers) {
for (Integer number : numbers) {
if (number < LOTTO_MIN_NUMBER || number > LOTTO_MAX_NUMBER) {
throw new IllegalArgumentException("로또 번호는 1과 45사이의 숫자이어야 합니다.");
}
}
}

public List<Integer> getNumbers() {
return numbers;
}
}
25 changes: 25 additions & 0 deletions src/main/java/model/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class LottoGenerator {

private static final List<Integer> NUMBERS = Arrays.asList(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45);

public Lottos generateRandomLotto(final LottoPurchaseMoney lottoPurchaseMoney) {
final List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < lottoPurchaseMoney.getPurchaseQuantity(); i++) {
Collections.shuffle(NUMBERS);
lottos.add(new Lotto(new ArrayList<>(NUMBERS.subList(0, 6))));
}
return new Lottos(lottos);
}
}
27 changes: 27 additions & 0 deletions src/main/java/model/LottoPurchaseMoney.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package model;

public class LottoPurchaseMoney {

private static final int LOTTO_EXPENSE = 1000;

private final int value;

public LottoPurchaseMoney(int value) {
validateValue(value);
this.value = value;
}

private void validateValue(final int value) {
if (value < 0) {
throw new IllegalArgumentException("구입금액은 음수가 될 수 없습니다.");
}

if (value % LOTTO_EXPENSE != 0) {
throw new IllegalArgumentException("구입금액은 1000단위이어야 합니다.");
}
}

public int getPurchaseQuantity() {
return value / LOTTO_EXPENSE;
}
}
20 changes: 20 additions & 0 deletions src/main/java/model/Lottos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package model;

import java.util.List;

public class Lottos {

private final List<Lotto> lottos;

public Lottos(List<Lotto> lottos) {
this.lottos = lottos;
}

public int getBuyLottoCount() {
return lottos.size();

Choose a reason for hiding this comment

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

Money 객체에 단 리뷰와 마찬가지로 저는 이렇게 되면 "lottos가 관리하는 구매한 로또 개수", "money가 관리하는 구매한 로또 개수"가 같은 의미인데 관리 지점이 두 개가 생기게 되어 문제가 되지 않을까..! 라고 생각했었습니다!

근데 지금 코드를 쭉 읽어보니, 결국 money의 로또 개수를 기반으로 lottos를 만드는 것이기 때문에 로직 상으로는 별 문제가 없을 것 같긴 한데........ 여튼 저는 저렇게 생각했었는데 석환님의 생각은 어떠신지 궁금합니다!!

Copy link
Author

Choose a reason for hiding this comment

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

관리 지점이 두개라고 보기보다는 구매한 로또 개수를 파악할 수 있는 출처가 Lottos와 Money라고 보면 좋을 것 같습니다. (Money는 이제 LottoPurchaseMoney로 변경될 예정입니다.) 그 이유는 민주님이 리뷰달아주신 것처럼 Lottos를 통해서 얻은 로또 개수와 Money를 통해서 얻는 로또 개수가 다르지 않기 때문입니다.

}

public List<Lotto> getLottos() {
return lottos;
}
}
14 changes: 14 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package view;

import model.LottoPurchaseMoney;

import java.util.Scanner;

public class InputView {

public static LottoPurchaseMoney inputMoney() {
Scanner sc = new Scanner(System.in);
System.out.println("구입금액을 입력해 주세요");
return new LottoPurchaseMoney(sc.nextInt());
}
}
27 changes: 27 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package view;

import model.Lotto;
import model.Lottos;

import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;

public class OutputView {

private static final String DELIMITER = "\n";

public static void showLotto(final Lottos lottos) {
System.out.printf("%d개를 구매했습니다.%n", lottos.getBuyLottoCount());

StringJoiner stringJoiner = new StringJoiner(DELIMITER);

for (Lotto lotto : lottos.getLottos()) {
final List<Integer> numbers = lotto.getNumbers();
Collections.sort(numbers);
stringJoiner.add(numbers.toString());
}

System.out.println(stringJoiner);
}
}
50 changes: 50 additions & 0 deletions src/test/java/model/LottoPurchaseMoneyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package model;

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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class LottoPurchaseMoneyTest {

@DisplayName("구매금액이 음수이면 예외를 발생한다.")
@Test
void money_not_under_zero() {
// given
int value = -1;
// when
// then
assertThatThrownBy(() -> new LottoPurchaseMoney(value))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("구입금액은 음수가 될 수 없습니다.");
}

@DisplayName("구매금액이 1000단위가 아니면 예외를 발생한다.")
@ValueSource(ints = {1100, 1010, 1001})
@ParameterizedTest
void money_divide_with_1000(int value) {
// given
// when
// then
assertThatThrownBy(() -> new LottoPurchaseMoney(value))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("구입금액은 1000단위이어야 합니다.");
}

@DisplayName("로또 구매 개수 구한다.")
@Test
void get_lotto_count() {
// given
final int pay = 10000;
final LottoPurchaseMoney lottoPurchaseMoney = new LottoPurchaseMoney(pay);

// when
final int result = lottoPurchaseMoney.getPurchaseQuantity();

// then
assertThat(result).isEqualTo(10);
}
}
68 changes: 68 additions & 0 deletions src/test/java/model/LottoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package model;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;
import java.util.stream.Stream;

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

class LottoTest {

@DisplayName("로또의 숫자가 6개가 아니면 예외를 발생한다.")
@MethodSource("lottoWithWrongSize")
@ParameterizedTest
void lotto_size_6(List<Integer> numbers) {
// given
// when
// then
assertThatThrownBy(() -> new Lotto(numbers))

Choose a reason for hiding this comment

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

테스트코드 관련해서 궁금한 점이 있습니다!
subList로 무조건적으로 로또 숫자 개수는 6개가 나오도록 구현해놓은 상황인데, 이런 케이스에 대해서도 보통 테스트케이스를 작성하시는지 궁금합니다! !
저는 외부적인 요인(사용자의 입력)이 있는 경우만 주로 테스트 케이스로 작성하는 편이라서요🥹

Copy link
Author

@seokhwan-an seokhwan-an Jun 23, 2024

Choose a reason for hiding this comment

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

저는 제가 작성한 검증 로직에 대해서는 모두 테스트를 작성하는 편입니다. 그 이유는 먼저 제가 작성한 코드가 모두 잘 동작하는지 파악하는데 큰 도움이 되는 것이 첫번째 이유이고 두번째는 이유로는 테스트 코드가 다른 개발자가 볼 때 그 사람이 구현한 기능을 파악하는데 도움이 된다고 생각했습니다. 두번째 이유는 제가 개인적으로 겪은 경험인데 다른 분의 코드를 리뷰할 때 테스트 코드를 바탕으로 구현한 로직을 보면 그 사람의 의도와 목적을 더 쉽게 파악하는데 도움이 되었던 것 같습니다! (또한 구현한 로직이 명확하게 파악되었습니다.)

.isInstanceOf(IllegalArgumentException.class)
.hasMessage("로또는 6개의 숫자로 구성되어야 합니다.");
}

private static Stream<Arguments> lottoWithWrongSize() {
return Stream.of(
Arguments.arguments(List.of(1, 2, 3, 4, 5)),
Arguments.of(List.of(1, 2, 3, 4, 5, 6, 7)));
}

@DisplayName("로또 내 숫자는 중복되면 예외를 발생한다.")
@MethodSource("lottoWithDuplicatedNumber")
@ParameterizedTest
void lotto_with_duplicated_number(List<Integer> numbers) {
// given
// when
// then
assertThatThrownBy(() -> new Lotto(numbers))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("로또 내 동일한 숫자가 있으면 안됩니다.");
}

private static Stream<Arguments> lottoWithDuplicatedNumber() {
return Stream.of(
Arguments.arguments(List.of(1, 2, 3, 4, 5, 5)),
Arguments.of(List.of(1, 1, 3, 4, 5, 6)));
}

@DisplayName("로또 내 숫자가 1과 45사이의 숫자가 아니면 예외를 발생한다.")
@MethodSource("lottoWithWrongNumber")
@ParameterizedTest
void lotto_with_not_1_to_45(List<Integer> numbers) {
// given
// when
// then
assertThatThrownBy(() -> new Lotto(numbers))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("로또 번호는 1과 45사이의 숫자이어야 합니다.");
}

private static Stream<Arguments> lottoWithWrongNumber() {
return Stream.of(
Arguments.arguments(List.of(1, 2, 3, 4, 5, 46)),
Arguments.of(List.of(0, 1, 3, 4, 5, 6)));
}
}