From cbe093334f647243f34c2bf2aa430e493a91c87f Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Fri, 1 Nov 2024 16:28:42 +0900 Subject: [PATCH 01/26] docs: update README.md --- README.md | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/README.md b/README.md index 4fc0ae874..2956c3213 100644 --- a/README.md +++ b/README.md @@ -1 +1,138 @@ # kotlin-lotto-precourse + +# 기능 구현 목록 + +- [ ] 로또를 발행한다. + - [ ] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. + - [ ] 오름차순으로 정렬한다. + +- [ ] 로또가 알맞은 형식인지 검사한다. + - [ ] [예외] 1~45사이의 숫자가 아닌 경우. + - [ ] [예외] 숫자가 중복인 경우. + - [ ] [예외] 6개의 숫자가 아닌 경우. + - [ ] [예외] 오름차순이 아닌 경우. + - [ ] [예외] 문자인 경우. + +- [ ] 로또 구입 금액을 입력받는다. + - [ ] 구입 금액에 해당하는 만큼 로또를 발행한다. + - [ ] 로또 1장의 가격은 1,000원이다. + +- [ ] 몇 장을 발행했는지 출력한다. + - `8개를 구매했습니다.` + +- [ ] 당첨 번호를 입력받는다. + - `당첨 번호를 입력해 주세요.` + - [ ] 당첨 번호를 오름차순으로 정렬한다. + +- [ ] 보너스 번호를 입력받는다. + - `보너스 번호를 입력해 주세요.` + +- [ ] 당첨 여부를 비교한다. +``` + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +``` + - [ ] 사용자가 구매한 로또 번호와 당첨 번호를 비교한다. + - [ ] 당첨 내역 및 수익률을 출력한다. + +- [ ] 사용자가 잘못된 값을 입력할 경우 + - [ ] `IllegalArgumentException`을 발생시킨다. + - [ ] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - [ ] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. + +# 입출력 요구 사항 +## 입력 +- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. + +`14000` +- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다. + +`1,2,3,4,5,6` +- 보너스 번호를 입력 받는다. + +`7` + +## 출력 +- 발행한 로또 수량 및 번호를 출력한다. + - 로또 번호는 오름차순으로 정렬하여 보여준다. +``` +8개를 구매했습니다. + [8, 21, 23, 41, 42, 43] + [3, 5, 11, 16, 32, 38] + [7, 11, 16, 35, 36, 44] + [1, 8, 11, 31, 41, 42] + [13, 14, 16, 38, 42, 45] + [7, 11, 30, 40, 42, 43] + [2, 13, 22, 32, 38, 45] + [1, 3, 5, 14, 22, 45] +``` + +- 당첨 내역을 출력한다. +``` +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +``` +- 수익률을 출력한다. + - 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%) + - `총 수익률은 62.5%입니다.` + +- 예외 상황 시 에러 문구를 출력해야 한다. + - 단, 에러 문구는 "[ERROR]"로 시작해야 한다. + `[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.` + + +# 프로그래밍 요구 사항 1 +- Kotlin 1.9.24에서 실행 가능해야 한다. +- Java 코드가 아닌 Kotlin 코드로만 구현해야 한다. +- 프로그램 실행의 시작점은 Application의 main()이다. +- build.gradle.kts 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다. +- 프로그램 종료 시 System.exit() 또는 exitProcess()를 호출하지 않는다. +- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. +- 코틀린 코드 컨벤션을 지키면서 프로그래밍한다. +- 기본적으로 Kotlin Style Guide를 원칙으로 한다. + +## 프로그래밍 요구 사항 2 +- [ ] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- [ ] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. + +- JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다. + - 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다. + ``` + JUnit 5 User Guide + AssertJ User Guide + AssertJ Exception Assertions + Guide to JUnit 5 Parameterized Tests + ``` + +## 프로그래밍 요구 사항 3 +- [ ] 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. +- [ ] 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +- [ ] else를 지양한다. + - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. +- [ ] Enum 클래스를 적용하여 프로그램을 구현한다. +- [ ] 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. + - 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다. + +# 피드백 +- [ ] 기능 목록을 재검토한다. + - [ ] 기능 목록을 작성할 때 클래스 설계와 구현, 메서드 설계와 구현 등은 포함하지 않는다. + - [ ] 예외상황도 함께 정리한다. +- [ ] 기능 목록을 업데이트한다. 기능을 구현하면서 문서를 지속적으로 업데이트한다. +- [ ] **문자열이나 숫자 값을 하드 코딩하지 않는다. 상수를 정의하고 의미있는 이름을 부여하여 해당 값이 어떤 역할을 하는지 명확히 드러낸다.** +- [ ] 구현 순서도 코딩 컨벤션이다. + - [ ] 프로퍼티, init블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다. +- [ ] 변수 이름에 자료형은 사용하지 않는다. 변수 이름은 의미를 명확히 드러낼 수 있도록 한다. +- [ ] 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다. (회고때 포함) + - [ ] 테스트 작성 과정을 통해 구현한 기능의 문제를 빠르게 발견할 수 있을 뿐만 아니라, 코드의 구조와 의도를 명확히 이해하는 데도 도움을 받을 수 있다. 학습 도구로도 활용할 수 있는데, 수 많은 테스트의 장점 중 본인이 가장 공감하는 작성 이유를 작성해 본다. +- [ ] 처음부터 큰 단위 테스트를 만들지 않는다. → 테스트 주도 개발을 하자 + - [ ] 작은 단위 테스트 : 무작위 값이 4 이상이면 자동차가 전진한다. + - [ ] 큰 단위 테스트 : 자동차 경주 게임을 시작하여, 사용자가 이름과 진행횟수를 입력하고, 게임을진행한 후 결과를 확인한다. \ No newline at end of file From d64cb36276e29f96af18908291506a0a046ca1e9 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Sun, 3 Nov 2024 12:23:53 +0900 Subject: [PATCH 02/26] docs: update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2956c3213..ffb521025 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - [ ] 오름차순으로 정렬한다. - [ ] 로또가 알맞은 형식인지 검사한다. - - [ ] [예외] 1~45사이의 숫자가 아닌 경우. + - [ ] [예외] 1~45사이의 숫자가 아닌 경우. - [ ] [예외] 숫자가 중복인 경우. - [ ] [예외] 6개의 숫자가 아닌 경우. - [ ] [예외] 오름차순이 아닌 경우. @@ -16,6 +16,9 @@ - [ ] 로또 구입 금액을 입력받는다. - [ ] 구입 금액에 해당하는 만큼 로또를 발행한다. - [ ] 로또 1장의 가격은 1,000원이다. + - [ ] [예외] 가격이 숫자가 아닌 경우. + - [ ] [예외] 가격이 음수인 경우. + - [ ] [예외] 가격이 1000원 미만인 경우. - [ ] 몇 장을 발행했는지 출력한다. - `8개를 구매했습니다.` From 49ab08544782d2e5214cc7b92d1d837b7bdcb5c4 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Sun, 3 Nov 2024 12:57:23 +0900 Subject: [PATCH 03/26] feat: add exception handling for lotto validation --- README.md | 11 +++++------ src/main/kotlin/lotto/Lotto.kt | 13 +++++++++++-- src/test/kotlin/lotto/LottoTest.kt | 24 ++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ffb521025..8f55f55ea 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,11 @@ - [ ] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. - [ ] 오름차순으로 정렬한다. -- [ ] 로또가 알맞은 형식인지 검사한다. - - [ ] [예외] 1~45사이의 숫자가 아닌 경우. - - [ ] [예외] 숫자가 중복인 경우. - - [ ] [예외] 6개의 숫자가 아닌 경우. - - [ ] [예외] 오름차순이 아닌 경우. - - [ ] [예외] 문자인 경우. +- [x] 로또가 알맞은 형식인지 검사한다. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 숫자가 중복인 경우. + - [x] [예외] 6개의 숫자가 아닌 경우. + - [x] [예외] 오름차순이 아닌 경우. - [ ] 로또 구입 금액을 입력받는다. - [ ] 구입 금액에 해당하는 만큼 로또를 발행한다. diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt index b97abc385..512363e8c 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/Lotto.kt @@ -2,8 +2,17 @@ package lotto class Lotto(private val numbers: List) { init { - require(numbers.size == 6) { "[ERROR] 로또 번호는 6개여야 합니다." } + require(numbers.size == 6) { LottoErrorMessages.INVALID_SIZE } + require(numbers.distinct().size == numbers.size) { LottoErrorMessages.DUPLICATE_NUMBERS } + require(numbers.all { it in 1..45 }) { LottoErrorMessages.OUT_OF_RANGE } + require(numbers == numbers.sorted()) { LottoErrorMessages.NOT_SORTED } + } + + object LottoErrorMessages { + const val INVALID_SIZE = "[ERROR] 로또 번호는 6개여야 합니다." + const val DUPLICATE_NUMBERS = "[ERROR] 로또 번호는 중복되지 않아야 합니다." + const val OUT_OF_RANGE = "[ERROR] 모든 로또 번호는 1부터 45 사이여야 합니다." + const val NOT_SORTED = "[ERROR] 로또 번호는 오름차순으로 정렬되어 있어야 합니다." } - // TODO: 추가 기능 구현 } diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 122fae572..4311ad5e8 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -4,6 +4,16 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows class LottoTest { + @Test + fun `로또 안의 숫자가 1~45사이의 숫자가 아니면 예외가 발생한다`() { + assertThrows { + Lotto(listOf(-1, 2, 3, 4, 5, 6)) + } + assertThrows { + Lotto(listOf(1, 2, 4, 5, 6, 333)) + } + } + @Test fun `로또 번호의 개수가 6개가 넘어가면 예외가 발생한다`() { assertThrows { @@ -11,6 +21,13 @@ class LottoTest { } } + @Test + fun `로또 번호의 개수가 6개 미만이면 예외가 발생한다`() { + assertThrows { + Lotto(listOf(1, 2, 3, 4, 5)) + } + } + // TODO: 테스트가 통과하도록 프로덕션 코드 구현 @Test fun `로또 번호에 중복된 숫자가 있으면 예외가 발생한다`() { @@ -19,5 +36,12 @@ class LottoTest { } } + @Test + fun `로또 번호가 오름차순이 아니면 예외가 발생한다`() { + assertThrows { + Lotto(listOf(1, 3, 2, 4, 5, 6)) + } + } + // TODO: 추가 기능 구현에 따른 테스트 코드 작성 } From e83e5aa508511ec2445e3729c9671deaccec6343 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Sun, 3 Nov 2024 15:32:46 +0900 Subject: [PATCH 04/26] feat: add generate lotto --- README.md | 6 +++--- src/main/kotlin/lotto/Store.kt | 16 ++++++++++++++++ src/test/kotlin/lotto/StoreTest.kt | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/lotto/Store.kt create mode 100644 src/test/kotlin/lotto/StoreTest.kt diff --git a/README.md b/README.md index 8f55f55ea..88e814c2c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ # 기능 구현 목록 -- [ ] 로또를 발행한다. - - [ ] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. - - [ ] 오름차순으로 정렬한다. +- [x] 로또를 발행한다. + - [x] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. + - [x] 오름차순으로 정렬한다. - [x] 로또가 알맞은 형식인지 검사한다. - [x] [예외] 1~45사이의 숫자가 아닌 경우. diff --git a/src/main/kotlin/lotto/Store.kt b/src/main/kotlin/lotto/Store.kt new file mode 100644 index 000000000..ba29f9e3d --- /dev/null +++ b/src/main/kotlin/lotto/Store.kt @@ -0,0 +1,16 @@ +package lotto + +import camp.nextstep.edu.missionutils.Randoms + +object Store { + //fun buyLotto(money: Int) + + fun generateLotto(): Lotto { + var numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) + while (numbers.distinct().size != numbers.size){ + numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) + } + val lotto = Lotto(numbers.sorted()) + return lotto + } +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/StoreTest.kt b/src/test/kotlin/lotto/StoreTest.kt new file mode 100644 index 000000000..6fba14167 --- /dev/null +++ b/src/test/kotlin/lotto/StoreTest.kt @@ -0,0 +1,15 @@ +package lotto + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertDoesNotThrow + +class StoreTest { + val lotto = Store.generateLotto() + @Test + fun `생성한 로또가 예외를 발생시키지는 않는지 검사한다`() { + assertDoesNotThrow { + lotto + } + } + +} \ No newline at end of file From ca27f0fa1eac44042562a8ff99aa40094508f09f Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Sun, 3 Nov 2024 21:27:41 +0900 Subject: [PATCH 05/26] feat: impliment lotto generation based on purchase amount --- README.md | 5 +++-- src/main/kotlin/lotto/Store.kt | 9 ++++++++- src/test/kotlin/lotto/StoreTest.kt | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 88e814c2c..5744f9e7f 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ - [x] [예외] 6개의 숫자가 아닌 경우. - [x] [예외] 오름차순이 아닌 경우. +- [x] 구입 금액에 해당하는 만큼 로또를 발행한다. +- [x] 로또 1장의 가격은 1,000원이다. + - [ ] 로또 구입 금액을 입력받는다. - - [ ] 구입 금액에 해당하는 만큼 로또를 발행한다. - - [ ] 로또 1장의 가격은 1,000원이다. - [ ] [예외] 가격이 숫자가 아닌 경우. - [ ] [예외] 가격이 음수인 경우. - [ ] [예외] 가격이 1000원 미만인 경우. diff --git a/src/main/kotlin/lotto/Store.kt b/src/main/kotlin/lotto/Store.kt index ba29f9e3d..9c93bf1ce 100644 --- a/src/main/kotlin/lotto/Store.kt +++ b/src/main/kotlin/lotto/Store.kt @@ -3,7 +3,14 @@ package lotto import camp.nextstep.edu.missionutils.Randoms object Store { - //fun buyLotto(money: Int) + fun buyLotto(money: Int): List { + val numberOfLottoPurchased = money/1000 + val purchasedLottos = mutableListOf() + repeat(numberOfLottoPurchased){ + purchasedLottos.add(generateLotto()) + } + return purchasedLottos + } fun generateLotto(): Lotto { var numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) diff --git a/src/test/kotlin/lotto/StoreTest.kt b/src/test/kotlin/lotto/StoreTest.kt index 6fba14167..f8b16f2f0 100644 --- a/src/test/kotlin/lotto/StoreTest.kt +++ b/src/test/kotlin/lotto/StoreTest.kt @@ -2,6 +2,7 @@ package lotto import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertDoesNotThrow +import org.assertj.core.api.Assertions.assertThat class StoreTest { val lotto = Store.generateLotto() @@ -12,4 +13,9 @@ class StoreTest { } } + @Test + fun `2000원 냈을 때 로또 2개를 반환하는지 검사한다`() { + assertThat(Store.buyLotto(2000)).hasSize(2) + } + } \ No newline at end of file From 3c479cca742e3ef683219d78bfe86664fc9c5d4f Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 04:25:40 +0900 Subject: [PATCH 06/26] feat: add lottoResultChecker object and test --- README.md | 18 +++------- src/main/kotlin/lotto/Lotto.kt | 4 +++ src/main/kotlin/lotto/LottoResultChecker.kt | 30 ++++++++++++++++ src/main/kotlin/lotto/Store.kt | 6 ++-- .../kotlin/lotto/LottoResultCheckerTest.kt | 34 +++++++++++++++++++ 5 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/lotto/LottoResultChecker.kt create mode 100644 src/test/kotlin/lotto/LottoResultCheckerTest.kt diff --git a/README.md b/README.md index 5744f9e7f..259a7c988 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,7 @@ - [ ] [예외] 가격이 음수인 경우. - [ ] [예외] 가격이 1000원 미만인 경우. -- [ ] 몇 장을 발행했는지 출력한다. - - `8개를 구매했습니다.` - -- [ ] 당첨 번호를 입력받는다. - - `당첨 번호를 입력해 주세요.` - - [ ] 당첨 번호를 오름차순으로 정렬한다. - -- [ ] 보너스 번호를 입력받는다. - - `보너스 번호를 입력해 주세요.` - -- [ ] 당첨 여부를 비교한다. +- [x] 당첨 여부를 비교한다. ``` - 1등: 6개 번호 일치 / 2,000,000,000원 - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 @@ -38,8 +28,10 @@ - 4등: 4개 번호 일치 / 50,000원 - 5등: 3개 번호 일치 / 5,000원 ``` - - [ ] 사용자가 구매한 로또 번호와 당첨 번호를 비교한다. - - [ ] 당첨 내역 및 수익률을 출력한다. +- [x] 사용자가 구매한 로또 번호와 당첨 번호를 비교한다. + - [x] 당첨 내역을 구한다. + +- [x] 수익률을 구한다. - [ ] 사용자가 잘못된 값을 입력할 경우 - [ ] `IllegalArgumentException`을 발생시킨다. diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt index 512363e8c..7f9a54030 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/Lotto.kt @@ -8,6 +8,10 @@ class Lotto(private val numbers: List) { require(numbers == numbers.sorted()) { LottoErrorMessages.NOT_SORTED } } + fun getNumbers(): List { + return numbers + } + object LottoErrorMessages { const val INVALID_SIZE = "[ERROR] 로또 번호는 6개여야 합니다." const val DUPLICATE_NUMBERS = "[ERROR] 로또 번호는 중복되지 않아야 합니다." diff --git a/src/main/kotlin/lotto/LottoResultChecker.kt b/src/main/kotlin/lotto/LottoResultChecker.kt new file mode 100644 index 000000000..db9aefbd3 --- /dev/null +++ b/src/main/kotlin/lotto/LottoResultChecker.kt @@ -0,0 +1,30 @@ +package lotto + +object LottoResultChecker { + fun checkWinningStatus(lottos: List, myLotto: Lotto, myBonus: Int) + : Pair, Int> { + val winningCounts = MutableList(7) { 0 } + var bonusWin = 0; + + for (lotto in lottos) { + val sameValueOfLotto = lotto.getNumbers().intersect(myLotto.getNumbers().toSet()) + val matchingCount = sameValueOfLotto.size + val containsBonus = myBonus in lotto.getNumbers() + + if(matchingCount>=3) + winningCounts[matchingCount]++ + if (matchingCount == 5 && containsBonus){ + bonusWin++ + winningCounts[matchingCount]-- + } + } + return Pair(winningCounts, bonusWin) + } + + fun calculateProfitRate(money: Int, winningCounts: List, bonusWin: Int): Double { + val profit = + winningCounts[3] * 5000 + winningCounts[4] * 50000 + winningCounts[5] * 1500000 + winningCounts[6] * 2000000000 + bonusWin * 30000000 + val profitRate = profit.toDouble() / money + return profitRate + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Store.kt b/src/main/kotlin/lotto/Store.kt index 9c93bf1ce..eea794e50 100644 --- a/src/main/kotlin/lotto/Store.kt +++ b/src/main/kotlin/lotto/Store.kt @@ -4,9 +4,9 @@ import camp.nextstep.edu.missionutils.Randoms object Store { fun buyLotto(money: Int): List { - val numberOfLottoPurchased = money/1000 + val numberOfLottoPurchased = money / 1000 val purchasedLottos = mutableListOf() - repeat(numberOfLottoPurchased){ + repeat(numberOfLottoPurchased) { purchasedLottos.add(generateLotto()) } return purchasedLottos @@ -14,7 +14,7 @@ object Store { fun generateLotto(): Lotto { var numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) - while (numbers.distinct().size != numbers.size){ + while (numbers.distinct().size != numbers.size) { numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) } val lotto = Lotto(numbers.sorted()) diff --git a/src/test/kotlin/lotto/LottoResultCheckerTest.kt b/src/test/kotlin/lotto/LottoResultCheckerTest.kt new file mode 100644 index 000000000..55899bcae --- /dev/null +++ b/src/test/kotlin/lotto/LottoResultCheckerTest.kt @@ -0,0 +1,34 @@ +package lotto + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class LottoResultCheckerTest { + @Test + fun `checkWinningStatus함수가 의도한 값을 반환하는지 확인`() { + val lottos = listOf( + Lotto(listOf(1, 2, 3, 4, 5, 6)), // 1등 + Lotto(listOf(1, 2, 3, 4, 5, 7)), // 2등 (보너스 포함) + Lotto(listOf(1, 2, 3, 4, 5, 8)), // 3등 + Lotto(listOf(1, 2, 3, 4, 9, 10)), // 4등 + Lotto(listOf(1, 2, 3, 11, 12, 13)) // 5등 + ) + + val myLotto = Lotto(listOf(1, 2, 3, 4, 5, 6)) + val myBonus = 7 + + val (winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) + + assertEquals(listOf(0, 0, 0, 1, 1, 1, 1), winningCounts) + assertEquals(1, bonusWin) + } + + @Test + fun `수익률 정상적으로 계산하는지 확인`() { + val winningCounts = listOf(0, 0, 0, 1, 1, 1, 1) + val bonusWin = 1 + val money = 5000 + val profitRate = LottoResultChecker.calculateProfitRate(money, winningCounts, bonusWin) + assertEquals(profitRate, (2000000000 + 30000000 + 1500000 + 50000 + 5000)/money.toDouble()) + } +} \ No newline at end of file From 5609780c8fddad67e5126051e5ec5e2b83a4fb2c Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 05:26:23 +0900 Subject: [PATCH 07/26] feat: add get purchase price and get lotto numbers --- README.md | 16 +++-- src/main/kotlin/lotto/ExceptionProcess.kt | 31 ++++++++++ src/main/kotlin/lotto/InputView.kt | 22 +++++++ src/test/kotlin/lotto/InputViewTest.kt | 74 +++++++++++++++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/lotto/ExceptionProcess.kt create mode 100644 src/main/kotlin/lotto/InputView.kt create mode 100644 src/test/kotlin/lotto/InputViewTest.kt diff --git a/README.md b/README.md index 259a7c988..8b7c854cc 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,18 @@ - [x] 구입 금액에 해당하는 만큼 로또를 발행한다. - [x] 로또 1장의 가격은 1,000원이다. -- [ ] 로또 구입 금액을 입력받는다. - - [ ] [예외] 가격이 숫자가 아닌 경우. - - [ ] [예외] 가격이 음수인 경우. - - [ ] [예외] 가격이 1000원 미만인 경우. +- [x] 로또 구입 금액을 입력받는다. + - [x] [예외] 가격이 숫자가 아닌 경우. + - [x] [예외] 가격이 음수인 경우. + - [x] [예외] 가격이 1000원 미만인 경우. + +- [x] 당첨 번호를 입력받는다. + - [x] [예외] 문자인 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 숫자가 중복인 경우. + - [x] [예외] 6개의 숫자가 아닌 경우. + +- [ ] 보너스 번호를 입력받는다. - [x] 당첨 여부를 비교한다. ``` diff --git a/src/main/kotlin/lotto/ExceptionProcess.kt b/src/main/kotlin/lotto/ExceptionProcess.kt new file mode 100644 index 000000000..4252d78e5 --- /dev/null +++ b/src/main/kotlin/lotto/ExceptionProcess.kt @@ -0,0 +1,31 @@ +package lotto + +import lotto.Lotto.LottoErrorMessages + +object ExceptionProcess { + fun validPrice(input: String) { + try { + val price = input.toInt() + require(price >= 1000) { "로또 한 개의 가격은 1000원 이상이어야 합니다" } + } catch (e: NumberFormatException) { + throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("[ERROR]: ${e.message}") + } + } + + fun validLottoNumber(input: String){ + try { + var numbers = input.split(",").map { it.toInt() } + numbers = numbers.sorted() + require(numbers.size == 6) { LottoErrorMessages.INVALID_SIZE } + require(numbers.distinct().size == numbers.size) { LottoErrorMessages.DUPLICATE_NUMBERS } + require(numbers.all { it in 1..45 }) { LottoErrorMessages.OUT_OF_RANGE } + require(numbers == numbers.sorted()) { LottoErrorMessages.NOT_SORTED } + } catch (e: NumberFormatException) { + throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("[ERROR]: ${e.message}") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/InputView.kt b/src/main/kotlin/lotto/InputView.kt new file mode 100644 index 000000000..9d284ef3d --- /dev/null +++ b/src/main/kotlin/lotto/InputView.kt @@ -0,0 +1,22 @@ +package lotto + +import camp.nextstep.edu.missionutils.Console + +class InputView { + fun getPrice(): Int { + while (true) { + val input = Console.readLine() + ExceptionProcess.validPrice(input) + return input.toInt() + } + } + + fun getLottoNumber(): List { + while (true) { + val input = Console.readLine() + ExceptionProcess.validPrice(input) + return input.split(",").map { it.toInt() } + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/InputViewTest.kt b/src/test/kotlin/lotto/InputViewTest.kt new file mode 100644 index 000000000..5804e7887 --- /dev/null +++ b/src/test/kotlin/lotto/InputViewTest.kt @@ -0,0 +1,74 @@ +package lotto + +import camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest +import camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest +import camp.nextstep.edu.missionutils.test.NsTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class InputViewTest { + @Test + fun `구매금액이 숫자가 아닌 경우 오류`() { + assertThrows { + ExceptionProcess.validPrice("hello") + } + } + + @Test + fun `구매금액이 음수인 경우 오류`() { + assertThrows { + ExceptionProcess.validPrice("-1000") + } + } + + @Test + fun `구매금액이 1000원 미만인 경우 오류`() { + assertThrows { + ExceptionProcess.validPrice("900") + } + } + + @Test + fun `당첨번호가 1~45사이의 숫자가 아니면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1,2,3,4,5,333") + } + } + + @Test + fun `당첨번호의 개수가 6개가 넘어가면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1, 2, 3, 4, 5, 6, 7") + } + } + + @Test + fun `당첨번호의 개수가 6개 미만이면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1, 2, 3, 4, 5") + } + } + + // TODO: 테스트가 통과하도록 프로덕션 코드 구현 + @Test + fun `당첨번호에 중복된 숫자가 있으면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1, 2, 3, 4, 5, 5") + } + } + + @Test + fun `당첨번호가 오름차순이 아니면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1, 3, 2, 4, 5, 6") + } + } + + @Test + fun `당첨번호가 문자이면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1, 3, 2, 4, 5, 6") + } + } +} \ No newline at end of file From c6478b0e4cac97b25bf3a202b2c1441c0bb44320 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 05:36:24 +0900 Subject: [PATCH 08/26] feat: add get bonusNumber function --- README.md | 4 +++- src/main/kotlin/lotto/ExceptionProcess.kt | 14 ++++++++++++- src/main/kotlin/lotto/InputView.kt | 10 ++++++++- src/test/kotlin/lotto/InputViewTest.kt | 25 +++++++++++++++-------- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8b7c854cc..3937b1bfb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ - [x] 로또 구입 금액을 입력받는다. - [x] [예외] 가격이 숫자가 아닌 경우. - - [x] [예외] 가격이 음수인 경우. - [x] [예외] 가격이 1000원 미만인 경우. - [x] 당첨 번호를 입력받는다. @@ -27,6 +26,9 @@ - [x] [예외] 6개의 숫자가 아닌 경우. - [ ] 보너스 번호를 입력받는다. + - [x] [예외] 가격이 숫자가 아닌 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [ ] [추가 예외] 당첨번호와 중복될 경우 - [x] 당첨 여부를 비교한다. ``` diff --git a/src/main/kotlin/lotto/ExceptionProcess.kt b/src/main/kotlin/lotto/ExceptionProcess.kt index 4252d78e5..529055eff 100644 --- a/src/main/kotlin/lotto/ExceptionProcess.kt +++ b/src/main/kotlin/lotto/ExceptionProcess.kt @@ -14,7 +14,7 @@ object ExceptionProcess { } } - fun validLottoNumber(input: String){ + fun validLottoNumber(input: String) { try { var numbers = input.split(",").map { it.toInt() } numbers = numbers.sorted() @@ -28,4 +28,16 @@ object ExceptionProcess { throw IllegalArgumentException("[ERROR]: ${e.message}") } } + + fun validBonusNumber(input: String) { + try { + val bonusNumber = input.toInt() + require(bonusNumber in 1..45) { LottoErrorMessages.OUT_OF_RANGE } + + } catch (e: NumberFormatException) { + throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("[ERROR]: ${e.message}") + } + } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/InputView.kt b/src/main/kotlin/lotto/InputView.kt index 9d284ef3d..6c60ee039 100644 --- a/src/main/kotlin/lotto/InputView.kt +++ b/src/main/kotlin/lotto/InputView.kt @@ -14,9 +14,17 @@ class InputView { fun getLottoNumber(): List { while (true) { val input = Console.readLine() - ExceptionProcess.validPrice(input) + ExceptionProcess.validLottoNumber(input) return input.split(",").map { it.toInt() } } } + fun getBonusNumber(): Int { + while (true) { + val input = Console.readLine() + ExceptionProcess.validBonusNumber(input) + return input.toInt() + } + } + } \ No newline at end of file diff --git a/src/test/kotlin/lotto/InputViewTest.kt b/src/test/kotlin/lotto/InputViewTest.kt index 5804e7887..060dc8194 100644 --- a/src/test/kotlin/lotto/InputViewTest.kt +++ b/src/test/kotlin/lotto/InputViewTest.kt @@ -9,21 +9,15 @@ import org.junit.jupiter.api.assertThrows class InputViewTest { @Test - fun `구매금액이 숫자가 아닌 경우 오류`() { + fun `구매금액이 숫자가 아닌 경우 예외가 발생한다`() { assertThrows { ExceptionProcess.validPrice("hello") - } - } - - @Test - fun `구매금액이 음수인 경우 오류`() { - assertThrows { ExceptionProcess.validPrice("-1000") } } @Test - fun `구매금액이 1000원 미만인 경우 오류`() { + fun `구매금액이 1000원 미만인 경우 예외가 발생한다`() { assertThrows { ExceptionProcess.validPrice("900") } @@ -71,4 +65,19 @@ class InputViewTest { ExceptionProcess.validLottoNumber("1, 3, 2, 4, 5, 6") } } + + @Test + fun `보너스 번호가 숫자가 아닌 경우 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validPrice("hello") + ExceptionProcess.validPrice("-1000") + } + } + + @Test + fun `보너스 번호가 1~45사이의 숫자가 아니면 예외가 발생한다`() { + assertThrows { + ExceptionProcess.validLottoNumber("1,2,3,4,5,333") + } + } } \ No newline at end of file From ae452ee53d8365a5d56be276250dae7e2eed941d Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 05:53:38 +0900 Subject: [PATCH 09/26] feat: add LottoDraw class run method --- src/main/kotlin/lotto/Application.kt | 3 ++ src/main/kotlin/lotto/InputView.kt | 45 +++++++++++++++++++++------- src/main/kotlin/lotto/LottoDraw.kt | 13 ++++++++ 3 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/lotto/LottoDraw.kt diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index 151821c9c..b8caa6635 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -2,4 +2,7 @@ package lotto fun main() { // TODO: 프로그램 구현 + val inputView = InputView() + val lottoDraw = LottoDraw() + lottoDraw.run(inputView) } diff --git a/src/main/kotlin/lotto/InputView.kt b/src/main/kotlin/lotto/InputView.kt index 6c60ee039..4899024b0 100644 --- a/src/main/kotlin/lotto/InputView.kt +++ b/src/main/kotlin/lotto/InputView.kt @@ -1,30 +1,55 @@ package lotto import camp.nextstep.edu.missionutils.Console +import lotto.Lotto.LottoErrorMessages class InputView { fun getPrice(): Int { while (true) { - val input = Console.readLine() - ExceptionProcess.validPrice(input) - return input.toInt() + try { + val price = Console.readLine().toInt() + require(price >= 1000) { "로또 한 개의 가격은 1000원 이상이어야 합니다" } + return price + } catch (e: NumberFormatException) { + println("[ERROR]: 입력은 숫자여야 합니다") + } catch (e: IllegalArgumentException) { + println("[ERROR]: ${e.message}") + } } } fun getLottoNumber(): List { while (true) { - val input = Console.readLine() - ExceptionProcess.validLottoNumber(input) - return input.split(",").map { it.toInt() } + try { + println("당첨 번호를 입력하세요 (예: 1, 2, 3, 4, 5, 6):") + val input = Console.readLine().split(",").map { it.trim().toInt() } + + // 입력 검증 로직: 각 조건에 맞는 예외를 던지도록 추가 + requireNotNull(input) { "[ERROR]: 입력이 null입니다. 다시 입력해 주세요." } + require(input.size == 6) { "[ERROR]: 번호는 6개여야 합니다." } + require(input.distinct().size == input.size) { "[ERROR]: 중복된 번호가 있습니다." } + require(input.all { it in 1..45 }) { "[ERROR]: 모든 번호는 1~45 사이여야 합니다." } + require(input == input.sorted()) { "[ERROR]: 번호는 오름차순으로 입력해야 합니다." } + + return input // 입력이 유효하면 반환 + + } catch (e: Exception) { + println(e.message) // 예외 메시지를 출력 + } } } fun getBonusNumber(): Int { while (true) { - val input = Console.readLine() - ExceptionProcess.validBonusNumber(input) - return input.toInt() + try { + val bonusNumber = Console.readLine().toInt() + require(bonusNumber in 1..45) { LottoErrorMessages.OUT_OF_RANGE } + return bonusNumber + } catch (e: NumberFormatException) { + throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("[ERROR]: ${e.message}") + } } } - } \ No newline at end of file diff --git a/src/main/kotlin/lotto/LottoDraw.kt b/src/main/kotlin/lotto/LottoDraw.kt new file mode 100644 index 000000000..f687e9862 --- /dev/null +++ b/src/main/kotlin/lotto/LottoDraw.kt @@ -0,0 +1,13 @@ +package lotto + +class LottoDraw { + fun run(inputView: InputView) { + val price = inputView.getPrice() + val myLotto = Lotto(inputView.getLottoNumber()) + val myBonus = inputView.getBonusNumber() + + val lottos = Store.buyLotto(price) + val(winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) + val profitRatio = LottoResultChecker.calculateProfitRate(price, winningCounts, bonusWin) + } +} \ No newline at end of file From 448651670afff77254aa6b567c3a8216518df217 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 06:16:46 +0900 Subject: [PATCH 10/26] feat: add OutputView class --- README.md | 8 ++++---- src/main/kotlin/lotto/Application.kt | 3 ++- src/main/kotlin/lotto/InputView.kt | 4 +++- src/main/kotlin/lotto/Lotto.kt | 4 ++++ src/main/kotlin/lotto/LottoDraw.kt | 13 ++++++++++--- src/main/kotlin/lotto/OutputView.kt | 26 ++++++++++++++++++++++++++ src/main/kotlin/lotto/Store.kt | 10 ++++++---- 7 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/lotto/OutputView.kt diff --git a/README.md b/README.md index 3937b1bfb..2da89c75b 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,10 @@ - [x] 수익률을 구한다. -- [ ] 사용자가 잘못된 값을 입력할 경우 - - [ ] `IllegalArgumentException`을 발생시킨다. - - [ ] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. - - [ ] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. +- [x] 사용자가 잘못된 값을 입력할 경우 + - [x] `IllegalArgumentException`을 발생시킨다. + - [x] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - [x] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. # 입출력 요구 사항 ## 입력 diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index b8caa6635..58becb6d0 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -3,6 +3,7 @@ package lotto fun main() { // TODO: 프로그램 구현 val inputView = InputView() + val outputView = OutputView() val lottoDraw = LottoDraw() - lottoDraw.run(inputView) + lottoDraw.run(inputView, outputView) } diff --git a/src/main/kotlin/lotto/InputView.kt b/src/main/kotlin/lotto/InputView.kt index 4899024b0..1c156905a 100644 --- a/src/main/kotlin/lotto/InputView.kt +++ b/src/main/kotlin/lotto/InputView.kt @@ -5,6 +5,7 @@ import lotto.Lotto.LottoErrorMessages class InputView { fun getPrice(): Int { + println("구입금액을 입력해 주세요.") while (true) { try { val price = Console.readLine().toInt() @@ -19,9 +20,9 @@ class InputView { } fun getLottoNumber(): List { + println("당첨 번호를 입력해 주세요.") while (true) { try { - println("당첨 번호를 입력하세요 (예: 1, 2, 3, 4, 5, 6):") val input = Console.readLine().split(",").map { it.trim().toInt() } // 입력 검증 로직: 각 조건에 맞는 예외를 던지도록 추가 @@ -40,6 +41,7 @@ class InputView { } fun getBonusNumber(): Int { + println("보너스 번호를 입력해 주세요.") while (true) { try { val bonusNumber = Console.readLine().toInt() diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt index 7f9a54030..0a8f52204 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/Lotto.kt @@ -12,6 +12,10 @@ class Lotto(private val numbers: List) { return numbers } + override fun toString(): String { + return numbers.toString() + } + object LottoErrorMessages { const val INVALID_SIZE = "[ERROR] 로또 번호는 6개여야 합니다." const val DUPLICATE_NUMBERS = "[ERROR] 로또 번호는 중복되지 않아야 합니다." diff --git a/src/main/kotlin/lotto/LottoDraw.kt b/src/main/kotlin/lotto/LottoDraw.kt index f687e9862..cfdf2f45f 100644 --- a/src/main/kotlin/lotto/LottoDraw.kt +++ b/src/main/kotlin/lotto/LottoDraw.kt @@ -1,13 +1,20 @@ package lotto class LottoDraw { - fun run(inputView: InputView) { + fun run(inputView: InputView, outputView: OutputView) { val price = inputView.getPrice() + + val store = Store() + val lottos = store.buyLotto(price) + + outputView.purchasedMessage(store.numberOfLottoPurchased) + outputView.lottoList(lottos) + val myLotto = Lotto(inputView.getLottoNumber()) val myBonus = inputView.getBonusNumber() - val lottos = Store.buyLotto(price) - val(winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) + val (winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) val profitRatio = LottoResultChecker.calculateProfitRate(price, winningCounts, bonusWin) + outputView.resultStatistics(winningCounts, bonusWin, profitRatio) } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/OutputView.kt b/src/main/kotlin/lotto/OutputView.kt new file mode 100644 index 000000000..2ae894b10 --- /dev/null +++ b/src/main/kotlin/lotto/OutputView.kt @@ -0,0 +1,26 @@ +package lotto + +class OutputView { + fun purchasedMessage(num: Int) { + println("$num 개를 구매했습니다.") + } + + fun lottoList(lottos: List) { + for (lotto in lottos) { + println(lotto) + } + } + + fun resultStatistics(winningCounts: List, bonusWin: Int, profitRatio: Double) { + println( + "당첨 통계\n" + + "---" + ) + println("${3}개 일치 (5,000원) - ${winningCounts[3]}개\n" + + "${4}개 일치 (50,000원) - ${winningCounts[4]}개\n" + + "${5}개 일치 (1,500,000원) - ${winningCounts[5]}개\n" + + "${5}개 일치, 보너스 볼 일치 (30,000,000원) - ${bonusWin}개\n" + + "${6}개 일치 (2,000,000,000원) - ${winningCounts[6]}개\n") + println("총 수익률은 ${profitRatio}입니다.") + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Store.kt b/src/main/kotlin/lotto/Store.kt index eea794e50..5f6808430 100644 --- a/src/main/kotlin/lotto/Store.kt +++ b/src/main/kotlin/lotto/Store.kt @@ -2,17 +2,19 @@ package lotto import camp.nextstep.edu.missionutils.Randoms -object Store { +class Store { + var numberOfLottoPurchased: Int = 0 + val purchasedLottos = mutableListOf() + fun buyLotto(money: Int): List { - val numberOfLottoPurchased = money / 1000 - val purchasedLottos = mutableListOf() + numberOfLottoPurchased = money / 1000 repeat(numberOfLottoPurchased) { purchasedLottos.add(generateLotto()) } return purchasedLottos } - fun generateLotto(): Lotto { + private fun generateLotto(): Lotto { var numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) while (numbers.distinct().size != numbers.size) { numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) From 08eaaea6f8452106f8f25a4b2ebcc123a4161249 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 06:25:32 +0900 Subject: [PATCH 11/26] =?UTF-8?q?fix(OutputView):=20=EC=98=A4=ED=83=88?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/kotlin/lotto/LottoResultChecker.kt | 2 +- src/main/kotlin/lotto/OutputView.kt | 4 ++-- src/main/kotlin/lotto/Store.kt | 2 +- src/test/kotlin/lotto/StoreTest.kt | 5 +++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2da89c75b..740f20ad7 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ - 기본적으로 Kotlin Style Guide를 원칙으로 한다. ## 프로그래밍 요구 사항 2 -- [ ] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. +- [x] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. - [ ] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. diff --git a/src/main/kotlin/lotto/LottoResultChecker.kt b/src/main/kotlin/lotto/LottoResultChecker.kt index db9aefbd3..11ab8c6b0 100644 --- a/src/main/kotlin/lotto/LottoResultChecker.kt +++ b/src/main/kotlin/lotto/LottoResultChecker.kt @@ -25,6 +25,6 @@ object LottoResultChecker { val profit = winningCounts[3] * 5000 + winningCounts[4] * 50000 + winningCounts[5] * 1500000 + winningCounts[6] * 2000000000 + bonusWin * 30000000 val profitRate = profit.toDouble() / money - return profitRate + return profitRate * 100 } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/OutputView.kt b/src/main/kotlin/lotto/OutputView.kt index 2ae894b10..23359c6b3 100644 --- a/src/main/kotlin/lotto/OutputView.kt +++ b/src/main/kotlin/lotto/OutputView.kt @@ -2,7 +2,7 @@ package lotto class OutputView { fun purchasedMessage(num: Int) { - println("$num 개를 구매했습니다.") + println("${num}개를 구매했습니다.") } fun lottoList(lottos: List) { @@ -21,6 +21,6 @@ class OutputView { "${5}개 일치 (1,500,000원) - ${winningCounts[5]}개\n" + "${5}개 일치, 보너스 볼 일치 (30,000,000원) - ${bonusWin}개\n" + "${6}개 일치 (2,000,000,000원) - ${winningCounts[6]}개\n") - println("총 수익률은 ${profitRatio}입니다.") + println("총 수익률은 ${profitRatio}%입니다.") } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/Store.kt b/src/main/kotlin/lotto/Store.kt index 5f6808430..16cccdf0c 100644 --- a/src/main/kotlin/lotto/Store.kt +++ b/src/main/kotlin/lotto/Store.kt @@ -14,7 +14,7 @@ class Store { return purchasedLottos } - private fun generateLotto(): Lotto { + fun generateLotto(): Lotto { var numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) while (numbers.distinct().size != numbers.size) { numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6) diff --git a/src/test/kotlin/lotto/StoreTest.kt b/src/test/kotlin/lotto/StoreTest.kt index f8b16f2f0..082f4e2cd 100644 --- a/src/test/kotlin/lotto/StoreTest.kt +++ b/src/test/kotlin/lotto/StoreTest.kt @@ -5,7 +5,8 @@ import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.assertj.core.api.Assertions.assertThat class StoreTest { - val lotto = Store.generateLotto() + val store = Store() + val lotto = store.generateLotto() @Test fun `생성한 로또가 예외를 발생시키지는 않는지 검사한다`() { assertDoesNotThrow { @@ -15,7 +16,7 @@ class StoreTest { @Test fun `2000원 냈을 때 로또 2개를 반환하는지 검사한다`() { - assertThat(Store.buyLotto(2000)).hasSize(2) + assertThat(store.buyLotto(2000)).hasSize(2) } } \ No newline at end of file From 48bbe941ae2ea0ec77514e82e7e8bd0060cdbaee Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 06:39:29 +0900 Subject: [PATCH 12/26] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=EB=B0=8D=20=EC=9A=94=EA=B5=AC=20=EC=82=AC=ED=95=AD2?= =?UTF-8?q?=20=EB=B0=98=EC=98=81,=20mvc=EB=AA=A8=EB=8D=B8=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 82 +++++++++++-------- src/main/kotlin/lotto/Application.kt | 4 + .../lotto/{ => controller}/LottoDraw.kt | 7 +- .../{ => controller}/LottoResultChecker.kt | 8 +- src/main/kotlin/lotto/{ => model}/Lotto.kt | 2 +- src/main/kotlin/lotto/{ => model}/Store.kt | 2 +- .../inputValidator.kt} | 6 +- src/main/kotlin/lotto/{ => view}/InputView.kt | 4 +- .../kotlin/lotto/{ => view}/OutputView.kt | 4 +- src/test/kotlin/lotto/InputViewTest.kt | 29 +++---- .../kotlin/lotto/LottoResultCheckerTest.kt | 2 + src/test/kotlin/lotto/LottoTest.kt | 1 + src/test/kotlin/lotto/StoreTest.kt | 1 + 13 files changed, 90 insertions(+), 62 deletions(-) rename src/main/kotlin/lotto/{ => controller}/LottoDraw.kt (83%) rename src/main/kotlin/lotto/{ => controller}/LottoResultChecker.kt (87%) rename src/main/kotlin/lotto/{ => model}/Lotto.kt (97%) rename src/main/kotlin/lotto/{ => model}/Store.kt (97%) rename src/main/kotlin/lotto/{ExceptionProcess.kt => util/inputValidator.kt} (95%) rename src/main/kotlin/lotto/{ => view}/InputView.kt (97%) rename src/main/kotlin/lotto/{ => view}/OutputView.kt (95%) diff --git a/README.md b/README.md index 740f20ad7..5d63f21ed 100644 --- a/README.md +++ b/README.md @@ -3,34 +3,35 @@ # 기능 구현 목록 - [x] 로또를 발행한다. - - [x] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. - - [x] 오름차순으로 정렬한다. + - [x] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. + - [x] 오름차순으로 정렬한다. - [x] 로또가 알맞은 형식인지 검사한다. - - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [x] [예외] 숫자가 중복인 경우. - - [x] [예외] 6개의 숫자가 아닌 경우. - - [x] [예외] 오름차순이 아닌 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 숫자가 중복인 경우. + - [x] [예외] 6개의 숫자가 아닌 경우. + - [x] [예외] 오름차순이 아닌 경우. - [x] 구입 금액에 해당하는 만큼 로또를 발행한다. - [x] 로또 1장의 가격은 1,000원이다. - [x] 로또 구입 금액을 입력받는다. - - [x] [예외] 가격이 숫자가 아닌 경우. - - [x] [예외] 가격이 1000원 미만인 경우. + - [x] [예외] 가격이 숫자가 아닌 경우. + - [x] [예외] 가격이 1000원 미만인 경우. - [x] 당첨 번호를 입력받는다. - - [x] [예외] 문자인 경우. - - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [x] [예외] 숫자가 중복인 경우. - - [x] [예외] 6개의 숫자가 아닌 경우. + - [x] [예외] 문자인 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 숫자가 중복인 경우. + - [x] [예외] 6개의 숫자가 아닌 경우. - [ ] 보너스 번호를 입력받는다. - - [x] [예외] 가격이 숫자가 아닌 경우. - - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [ ] [추가 예외] 당첨번호와 중복될 경우 + - [x] [예외] 가격이 숫자가 아닌 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [ ] [추가 예외] 당첨번호와 중복될 경우 - [x] 당첨 여부를 비교한다. + ``` - 1등: 6개 번호 일치 / 2,000,000,000원 - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 @@ -38,31 +39,38 @@ - 4등: 4개 번호 일치 / 50,000원 - 5등: 3개 번호 일치 / 5,000원 ``` + - [x] 사용자가 구매한 로또 번호와 당첨 번호를 비교한다. - - [x] 당첨 내역을 구한다. - + - [x] 당첨 내역을 구한다. + - [x] 수익률을 구한다. - [x] 사용자가 잘못된 값을 입력할 경우 - - [x] `IllegalArgumentException`을 발생시킨다. - - [x] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. - - [x] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. - + - [x] `IllegalArgumentException`을 발생시킨다. + - [x] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - [x] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. + # 입출력 요구 사항 + ## 입력 + - 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. `14000` + - 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다. `1,2,3,4,5,6` + - 보너스 번호를 입력 받는다. `7` ## 출력 + - 발행한 로또 수량 및 번호를 출력한다. - - 로또 번호는 오름차순으로 정렬하여 보여준다. + - 로또 번호는 오름차순으로 정렬하여 보여준다. + ``` 8개를 구매했습니다. [8, 21, 23, 41, 42, 43] @@ -76,6 +84,7 @@ ``` - 당첨 내역을 출력한다. + ``` 3개 일치 (5,000원) - 1개 4개 일치 (50,000원) - 0개 @@ -83,16 +92,17 @@ 5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 6개 일치 (2,000,000,000원) - 0개 ``` + - 수익률을 출력한다. - - 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%) - - `총 수익률은 62.5%입니다.` - -- 예외 상황 시 에러 문구를 출력해야 한다. - - 단, 에러 문구는 "[ERROR]"로 시작해야 한다. - `[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.` + - 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%) + - `총 수익률은 62.5%입니다.` +- 예외 상황 시 에러 문구를 출력해야 한다. + - 단, 에러 문구는 "[ERROR]"로 시작해야 한다. + `[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.` # 프로그래밍 요구 사항 1 + - Kotlin 1.9.24에서 실행 가능해야 한다. - Java 코드가 아닌 Kotlin 코드로만 구현해야 한다. - 프로그램 실행의 시작점은 Application의 main()이다. @@ -103,13 +113,14 @@ - 기본적으로 Kotlin Style Guide를 원칙으로 한다. ## 프로그래밍 요구 사항 2 + - [x] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. -- [ ] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- [x] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. - JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다. - - 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다. + - 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다. ``` JUnit 5 User Guide AssertJ User Guide @@ -118,16 +129,18 @@ ``` ## 프로그래밍 요구 사항 3 + - [ ] 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. - [ ] 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. - [ ] else를 지양한다. - - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. - - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. + - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - [ ] Enum 클래스를 적용하여 프로그램을 구현한다. - [ ] 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. - - 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다. + - 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다. # 피드백 + - [ ] 기능 목록을 재검토한다. - [ ] 기능 목록을 작성할 때 클래스 설계와 구현, 메서드 설계와 구현 등은 포함하지 않는다. - [ ] 예외상황도 함께 정리한다. @@ -137,7 +150,8 @@ - [ ] 프로퍼티, init블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다. - [ ] 변수 이름에 자료형은 사용하지 않는다. 변수 이름은 의미를 명확히 드러낼 수 있도록 한다. - [ ] 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다. (회고때 포함) - - [ ] 테스트 작성 과정을 통해 구현한 기능의 문제를 빠르게 발견할 수 있을 뿐만 아니라, 코드의 구조와 의도를 명확히 이해하는 데도 도움을 받을 수 있다. 학습 도구로도 활용할 수 있는데, 수 많은 테스트의 장점 중 본인이 가장 공감하는 작성 이유를 작성해 본다. + - [ ] 테스트 작성 과정을 통해 구현한 기능의 문제를 빠르게 발견할 수 있을 뿐만 아니라, 코드의 구조와 의도를 명확히 이해하는 데도 도움을 받을 수 있다. 학습 도구로도 활용할 수 있는데, 수 많은 + 테스트의 장점 중 본인이 가장 공감하는 작성 이유를 작성해 본다. - [ ] 처음부터 큰 단위 테스트를 만들지 않는다. → 테스트 주도 개발을 하자 - [ ] 작은 단위 테스트 : 무작위 값이 4 이상이면 자동차가 전진한다. - [ ] 큰 단위 테스트 : 자동차 경주 게임을 시작하여, 사용자가 이름과 진행횟수를 입력하고, 게임을진행한 후 결과를 확인한다. \ No newline at end of file diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index 58becb6d0..b096585c6 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,9 @@ package lotto +import lotto.controller.LottoDraw +import lotto.view.InputView +import lotto.view.OutputView + fun main() { // TODO: 프로그램 구현 val inputView = InputView() diff --git a/src/main/kotlin/lotto/LottoDraw.kt b/src/main/kotlin/lotto/controller/LottoDraw.kt similarity index 83% rename from src/main/kotlin/lotto/LottoDraw.kt rename to src/main/kotlin/lotto/controller/LottoDraw.kt index cfdf2f45f..58882ed8d 100644 --- a/src/main/kotlin/lotto/LottoDraw.kt +++ b/src/main/kotlin/lotto/controller/LottoDraw.kt @@ -1,4 +1,9 @@ -package lotto +package lotto.controller + +import lotto.model.Lotto +import lotto.model.Store +import lotto.view.InputView +import lotto.view.OutputView class LottoDraw { fun run(inputView: InputView, outputView: OutputView) { diff --git a/src/main/kotlin/lotto/LottoResultChecker.kt b/src/main/kotlin/lotto/controller/LottoResultChecker.kt similarity index 87% rename from src/main/kotlin/lotto/LottoResultChecker.kt rename to src/main/kotlin/lotto/controller/LottoResultChecker.kt index 11ab8c6b0..7c14fd5c1 100644 --- a/src/main/kotlin/lotto/LottoResultChecker.kt +++ b/src/main/kotlin/lotto/controller/LottoResultChecker.kt @@ -1,4 +1,6 @@ -package lotto +package lotto.controller + +import lotto.model.Lotto object LottoResultChecker { fun checkWinningStatus(lottos: List, myLotto: Lotto, myBonus: Int) @@ -11,9 +13,9 @@ object LottoResultChecker { val matchingCount = sameValueOfLotto.size val containsBonus = myBonus in lotto.getNumbers() - if(matchingCount>=3) + if (matchingCount >= 3) winningCounts[matchingCount]++ - if (matchingCount == 5 && containsBonus){ + if (matchingCount == 5 && containsBonus) { bonusWin++ winningCounts[matchingCount]-- } diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/model/Lotto.kt similarity index 97% rename from src/main/kotlin/lotto/Lotto.kt rename to src/main/kotlin/lotto/model/Lotto.kt index 0a8f52204..2f4eaebf0 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/model/Lotto.kt @@ -1,4 +1,4 @@ -package lotto +package lotto.model class Lotto(private val numbers: List) { init { diff --git a/src/main/kotlin/lotto/Store.kt b/src/main/kotlin/lotto/model/Store.kt similarity index 97% rename from src/main/kotlin/lotto/Store.kt rename to src/main/kotlin/lotto/model/Store.kt index 16cccdf0c..7bbb1d80c 100644 --- a/src/main/kotlin/lotto/Store.kt +++ b/src/main/kotlin/lotto/model/Store.kt @@ -1,4 +1,4 @@ -package lotto +package lotto.model import camp.nextstep.edu.missionutils.Randoms diff --git a/src/main/kotlin/lotto/ExceptionProcess.kt b/src/main/kotlin/lotto/util/inputValidator.kt similarity index 95% rename from src/main/kotlin/lotto/ExceptionProcess.kt rename to src/main/kotlin/lotto/util/inputValidator.kt index 529055eff..4be5562a6 100644 --- a/src/main/kotlin/lotto/ExceptionProcess.kt +++ b/src/main/kotlin/lotto/util/inputValidator.kt @@ -1,8 +1,8 @@ -package lotto +package lotto.util -import lotto.Lotto.LottoErrorMessages +import lotto.model.Lotto.LottoErrorMessages -object ExceptionProcess { +object inputValidator { fun validPrice(input: String) { try { val price = input.toInt() diff --git a/src/main/kotlin/lotto/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt similarity index 97% rename from src/main/kotlin/lotto/InputView.kt rename to src/main/kotlin/lotto/view/InputView.kt index 1c156905a..55ab899de 100644 --- a/src/main/kotlin/lotto/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -1,7 +1,7 @@ -package lotto +package lotto.view import camp.nextstep.edu.missionutils.Console -import lotto.Lotto.LottoErrorMessages +import lotto.model.Lotto.LottoErrorMessages class InputView { fun getPrice(): Int { diff --git a/src/main/kotlin/lotto/OutputView.kt b/src/main/kotlin/lotto/view/OutputView.kt similarity index 95% rename from src/main/kotlin/lotto/OutputView.kt rename to src/main/kotlin/lotto/view/OutputView.kt index 23359c6b3..6fe5ebe89 100644 --- a/src/main/kotlin/lotto/OutputView.kt +++ b/src/main/kotlin/lotto/view/OutputView.kt @@ -1,4 +1,6 @@ -package lotto +package lotto.view + +import lotto.model.Lotto class OutputView { fun purchasedMessage(num: Int) { diff --git a/src/test/kotlin/lotto/InputViewTest.kt b/src/test/kotlin/lotto/InputViewTest.kt index 060dc8194..abf56cb39 100644 --- a/src/test/kotlin/lotto/InputViewTest.kt +++ b/src/test/kotlin/lotto/InputViewTest.kt @@ -1,9 +1,6 @@ package lotto -import camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest -import camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest -import camp.nextstep.edu.missionutils.test.NsTest -import org.assertj.core.api.Assertions.assertThat +import lotto.util.inputValidator import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -11,36 +8,36 @@ class InputViewTest { @Test fun `구매금액이 숫자가 아닌 경우 예외가 발생한다`() { assertThrows { - ExceptionProcess.validPrice("hello") - ExceptionProcess.validPrice("-1000") + inputValidator.validPrice("hello") + inputValidator.validPrice("-1000") } } @Test fun `구매금액이 1000원 미만인 경우 예외가 발생한다`() { assertThrows { - ExceptionProcess.validPrice("900") + inputValidator.validPrice("900") } } @Test fun `당첨번호가 1~45사이의 숫자가 아니면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1,2,3,4,5,333") + inputValidator.validLottoNumber("1,2,3,4,5,333") } } @Test fun `당첨번호의 개수가 6개가 넘어가면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1, 2, 3, 4, 5, 6, 7") + inputValidator.validLottoNumber("1, 2, 3, 4, 5, 6, 7") } } @Test fun `당첨번호의 개수가 6개 미만이면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1, 2, 3, 4, 5") + inputValidator.validLottoNumber("1, 2, 3, 4, 5") } } @@ -48,36 +45,36 @@ class InputViewTest { @Test fun `당첨번호에 중복된 숫자가 있으면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1, 2, 3, 4, 5, 5") + inputValidator.validLottoNumber("1, 2, 3, 4, 5, 5") } } @Test fun `당첨번호가 오름차순이 아니면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1, 3, 2, 4, 5, 6") + inputValidator.validLottoNumber("1, 3, 2, 4, 5, 6") } } @Test fun `당첨번호가 문자이면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1, 3, 2, 4, 5, 6") + inputValidator.validLottoNumber("1, 3, 2, 4, 5, 6") } } @Test fun `보너스 번호가 숫자가 아닌 경우 예외가 발생한다`() { assertThrows { - ExceptionProcess.validPrice("hello") - ExceptionProcess.validPrice("-1000") + inputValidator.validPrice("hello") + inputValidator.validPrice("-1000") } } @Test fun `보너스 번호가 1~45사이의 숫자가 아니면 예외가 발생한다`() { assertThrows { - ExceptionProcess.validLottoNumber("1,2,3,4,5,333") + inputValidator.validLottoNumber("1,2,3,4,5,333") } } } \ No newline at end of file diff --git a/src/test/kotlin/lotto/LottoResultCheckerTest.kt b/src/test/kotlin/lotto/LottoResultCheckerTest.kt index 55899bcae..47ffd3276 100644 --- a/src/test/kotlin/lotto/LottoResultCheckerTest.kt +++ b/src/test/kotlin/lotto/LottoResultCheckerTest.kt @@ -1,5 +1,7 @@ package lotto +import lotto.controller.LottoResultChecker +import lotto.model.Lotto import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 4311ad5e8..c526c65d4 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,5 +1,6 @@ package lotto +import lotto.model.Lotto import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/lotto/StoreTest.kt b/src/test/kotlin/lotto/StoreTest.kt index 082f4e2cd..f67362543 100644 --- a/src/test/kotlin/lotto/StoreTest.kt +++ b/src/test/kotlin/lotto/StoreTest.kt @@ -1,5 +1,6 @@ package lotto +import lotto.model.Store import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.assertj.core.api.Assertions.assertThat From 335787614ef7668472f2ef7ae17110a259c56e0b Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 17:43:10 +0900 Subject: [PATCH 13/26] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/kotlin/lotto/controller/LottoDraw.kt | 6 +- src/main/kotlin/lotto/model/Lotto.kt | 18 ++--- src/main/kotlin/lotto/util/ErrorMessages.kt | 13 +++ src/main/kotlin/lotto/util/inputValidator.kt | 43 ---------- src/main/kotlin/lotto/view/InputView.kt | 45 ++++++----- src/test/kotlin/lotto/InputViewTest.kt | 80 ------------------- 7 files changed, 48 insertions(+), 159 deletions(-) create mode 100644 src/main/kotlin/lotto/util/ErrorMessages.kt delete mode 100644 src/main/kotlin/lotto/util/inputValidator.kt delete mode 100644 src/test/kotlin/lotto/InputViewTest.kt diff --git a/README.md b/README.md index 5d63f21ed..fc1915cab 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ - [ ] 보너스 번호를 입력받는다. - [x] [예외] 가격이 숫자가 아닌 경우. - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [ ] [추가 예외] 당첨번호와 중복될 경우 + - [ ] [예외] 당첨번호와 중복될 경우 - [x] 당첨 여부를 비교한다. diff --git a/src/main/kotlin/lotto/controller/LottoDraw.kt b/src/main/kotlin/lotto/controller/LottoDraw.kt index 58882ed8d..8048c7c55 100644 --- a/src/main/kotlin/lotto/controller/LottoDraw.kt +++ b/src/main/kotlin/lotto/controller/LottoDraw.kt @@ -12,12 +12,12 @@ class LottoDraw { val store = Store() val lottos = store.buyLotto(price) - outputView.purchasedMessage(store.numberOfLottoPurchased) - outputView.lottoList(lottos) - val myLotto = Lotto(inputView.getLottoNumber()) val myBonus = inputView.getBonusNumber() + outputView.purchasedMessage(store.numberOfLottoPurchased) + outputView.lottoList(lottos) + val (winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) val profitRatio = LottoResultChecker.calculateProfitRate(price, winningCounts, bonusWin) outputView.resultStatistics(winningCounts, bonusWin, profitRatio) diff --git a/src/main/kotlin/lotto/model/Lotto.kt b/src/main/kotlin/lotto/model/Lotto.kt index 2f4eaebf0..366f5d5b1 100644 --- a/src/main/kotlin/lotto/model/Lotto.kt +++ b/src/main/kotlin/lotto/model/Lotto.kt @@ -1,11 +1,13 @@ package lotto.model +import lotto.util.ErrorMessages + class Lotto(private val numbers: List) { init { - require(numbers.size == 6) { LottoErrorMessages.INVALID_SIZE } - require(numbers.distinct().size == numbers.size) { LottoErrorMessages.DUPLICATE_NUMBERS } - require(numbers.all { it in 1..45 }) { LottoErrorMessages.OUT_OF_RANGE } - require(numbers == numbers.sorted()) { LottoErrorMessages.NOT_SORTED } + require(numbers.size == 6) { ErrorMessages.INVALID_NUMBER_SIZE } + require(numbers.distinct().size == numbers.size) { ErrorMessages.DUPLICATE_NUMBERS } + require(numbers.all { it in 1..45 }) { ErrorMessages.OUT_OF_RANGE } + require(numbers == numbers.sorted()) { ErrorMessages.NOT_SORTED } } fun getNumbers(): List { @@ -15,12 +17,4 @@ class Lotto(private val numbers: List) { override fun toString(): String { return numbers.toString() } - - object LottoErrorMessages { - const val INVALID_SIZE = "[ERROR] 로또 번호는 6개여야 합니다." - const val DUPLICATE_NUMBERS = "[ERROR] 로또 번호는 중복되지 않아야 합니다." - const val OUT_OF_RANGE = "[ERROR] 모든 로또 번호는 1부터 45 사이여야 합니다." - const val NOT_SORTED = "[ERROR] 로또 번호는 오름차순으로 정렬되어 있어야 합니다." - } - } diff --git a/src/main/kotlin/lotto/util/ErrorMessages.kt b/src/main/kotlin/lotto/util/ErrorMessages.kt new file mode 100644 index 000000000..f0cdfc741 --- /dev/null +++ b/src/main/kotlin/lotto/util/ErrorMessages.kt @@ -0,0 +1,13 @@ +package lotto.util + +object ErrorMessages { + const val INVALID_NUMBER_SIZE = "[ERROR] 로또 번호는 6개여야 합니다." + const val DUPLICATE_NUMBERS = "[ERROR] 로또 번호는 중복되지 않아야 합니다." + const val OUT_OF_RANGE = "[ERROR] 모든 로또 번호는 1부터 45 사이여야 합니다." + const val NOT_SORTED = "[ERROR] 로또 번호는 오름차순으로 정렬되어 있어야 합니다." + const val NULL_PRICE = "[ERROR]: 구입금액을 입력해 주세요." + const val NOT_INT = "[ERROR]: 입력이 숫자가 아닙니다." + const val MINIMUM_PRICE = "[ERROR]: 로또 한 개의 가격은 1000원 이상이어야 합니다" + const val NULL_LOTTO_NUMBER = "[ERROR]: 당첨 번호를 입력해 주세요." + const val NULL_BONUS_NUMBER = "[ERROR]: 보너스 번호를 입력해 주세요." +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/util/inputValidator.kt b/src/main/kotlin/lotto/util/inputValidator.kt deleted file mode 100644 index 4be5562a6..000000000 --- a/src/main/kotlin/lotto/util/inputValidator.kt +++ /dev/null @@ -1,43 +0,0 @@ -package lotto.util - -import lotto.model.Lotto.LottoErrorMessages - -object inputValidator { - fun validPrice(input: String) { - try { - val price = input.toInt() - require(price >= 1000) { "로또 한 개의 가격은 1000원 이상이어야 합니다" } - } catch (e: NumberFormatException) { - throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") - } catch (e: IllegalArgumentException) { - throw IllegalArgumentException("[ERROR]: ${e.message}") - } - } - - fun validLottoNumber(input: String) { - try { - var numbers = input.split(",").map { it.toInt() } - numbers = numbers.sorted() - require(numbers.size == 6) { LottoErrorMessages.INVALID_SIZE } - require(numbers.distinct().size == numbers.size) { LottoErrorMessages.DUPLICATE_NUMBERS } - require(numbers.all { it in 1..45 }) { LottoErrorMessages.OUT_OF_RANGE } - require(numbers == numbers.sorted()) { LottoErrorMessages.NOT_SORTED } - } catch (e: NumberFormatException) { - throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") - } catch (e: IllegalArgumentException) { - throw IllegalArgumentException("[ERROR]: ${e.message}") - } - } - - fun validBonusNumber(input: String) { - try { - val bonusNumber = input.toInt() - require(bonusNumber in 1..45) { LottoErrorMessages.OUT_OF_RANGE } - - } catch (e: NumberFormatException) { - throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") - } catch (e: IllegalArgumentException) { - throw IllegalArgumentException("[ERROR]: ${e.message}") - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index 55ab899de..aad3da6ca 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -1,20 +1,22 @@ package lotto.view import camp.nextstep.edu.missionutils.Console -import lotto.model.Lotto.LottoErrorMessages +import lotto.util.ErrorMessages class InputView { fun getPrice(): Int { println("구입금액을 입력해 주세요.") while (true) { + val input = Console.readLine() try { - val price = Console.readLine().toInt() - require(price >= 1000) { "로또 한 개의 가격은 1000원 이상이어야 합니다" } + val price = input.toIntOrNull() + + require(input != "") { ErrorMessages.NULL_PRICE } + require(price != null) { ErrorMessages.NOT_INT } + require(price >= 1000) { ErrorMessages.MINIMUM_PRICE } return price - } catch (e: NumberFormatException) { - println("[ERROR]: 입력은 숫자여야 합니다") } catch (e: IllegalArgumentException) { - println("[ERROR]: ${e.message}") + println(e.message) } } } @@ -23,19 +25,20 @@ class InputView { println("당첨 번호를 입력해 주세요.") while (true) { try { - val input = Console.readLine().split(",").map { it.trim().toInt() } + val input = Console.readLine() + val lottoNumber = input.split(",").map { it.trim().toIntOrNull() } - // 입력 검증 로직: 각 조건에 맞는 예외를 던지도록 추가 - requireNotNull(input) { "[ERROR]: 입력이 null입니다. 다시 입력해 주세요." } - require(input.size == 6) { "[ERROR]: 번호는 6개여야 합니다." } - require(input.distinct().size == input.size) { "[ERROR]: 중복된 번호가 있습니다." } - require(input.all { it in 1..45 }) { "[ERROR]: 모든 번호는 1~45 사이여야 합니다." } - require(input == input.sorted()) { "[ERROR]: 번호는 오름차순으로 입력해야 합니다." } + require(input != "") { ErrorMessages.NULL_LOTTO_NUMBER } + require(lottoNumber.all { it != null }) { ErrorMessages.NOT_INT } + require(lottoNumber.all { it in 1..45 }) { ErrorMessages.OUT_OF_RANGE } + require(lottoNumber.size == 6) { ErrorMessages.INVALID_NUMBER_SIZE } + require(lottoNumber.distinct().size == lottoNumber.size) { ErrorMessages.DUPLICATE_NUMBERS } + val notNullLottoNumber: List = lottoNumber.filterNotNull().sorted() - return input // 입력이 유효하면 반환 + return notNullLottoNumber } catch (e: Exception) { - println(e.message) // 예외 메시지를 출력 + println(e.message) } } } @@ -44,13 +47,15 @@ class InputView { println("보너스 번호를 입력해 주세요.") while (true) { try { - val bonusNumber = Console.readLine().toInt() - require(bonusNumber in 1..45) { LottoErrorMessages.OUT_OF_RANGE } + val input = Console.readLine() + val bonusNumber = input.toIntOrNull() + + require(input != "") { ErrorMessages.NULL_BONUS_NUMBER } + require(bonusNumber != null) { ErrorMessages.NOT_INT } + require(bonusNumber in 1..45) { ErrorMessages.OUT_OF_RANGE } return bonusNumber - } catch (e: NumberFormatException) { - throw IllegalArgumentException("[ERROR]: 입력은 숫자여야 합니다") } catch (e: IllegalArgumentException) { - throw IllegalArgumentException("[ERROR]: ${e.message}") + throw IllegalArgumentException(e.message) } } } diff --git a/src/test/kotlin/lotto/InputViewTest.kt b/src/test/kotlin/lotto/InputViewTest.kt deleted file mode 100644 index abf56cb39..000000000 --- a/src/test/kotlin/lotto/InputViewTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -package lotto - -import lotto.util.inputValidator -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -class InputViewTest { - @Test - fun `구매금액이 숫자가 아닌 경우 예외가 발생한다`() { - assertThrows { - inputValidator.validPrice("hello") - inputValidator.validPrice("-1000") - } - } - - @Test - fun `구매금액이 1000원 미만인 경우 예외가 발생한다`() { - assertThrows { - inputValidator.validPrice("900") - } - } - - @Test - fun `당첨번호가 1~45사이의 숫자가 아니면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1,2,3,4,5,333") - } - } - - @Test - fun `당첨번호의 개수가 6개가 넘어가면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1, 2, 3, 4, 5, 6, 7") - } - } - - @Test - fun `당첨번호의 개수가 6개 미만이면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1, 2, 3, 4, 5") - } - } - - // TODO: 테스트가 통과하도록 프로덕션 코드 구현 - @Test - fun `당첨번호에 중복된 숫자가 있으면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1, 2, 3, 4, 5, 5") - } - } - - @Test - fun `당첨번호가 오름차순이 아니면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1, 3, 2, 4, 5, 6") - } - } - - @Test - fun `당첨번호가 문자이면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1, 3, 2, 4, 5, 6") - } - } - - @Test - fun `보너스 번호가 숫자가 아닌 경우 예외가 발생한다`() { - assertThrows { - inputValidator.validPrice("hello") - inputValidator.validPrice("-1000") - } - } - - @Test - fun `보너스 번호가 1~45사이의 숫자가 아니면 예외가 발생한다`() { - assertThrows { - inputValidator.validLottoNumber("1,2,3,4,5,333") - } - } -} \ No newline at end of file From 4d90189d88b4b146bc4e41daa28c4bf65e7f80b7 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 17:53:14 +0900 Subject: [PATCH 14/26] =?UTF-8?q?feat:=20=EB=8B=B9=EC=B2=A8=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EC=99=80=20=EB=B3=B4=EB=84=88=EC=8A=A4=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EA=B0=80=20=EC=A4=91=EB=B3=B5=EC=9D=B4=EB=A9=B4=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/main/kotlin/lotto/controller/LottoDraw.kt | 3 ++- src/main/kotlin/lotto/util/ErrorMessages.kt | 11 ++++++----- src/main/kotlin/lotto/view/InputView.kt | 7 +++++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fc1915cab..b496b0dc1 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ - [x] [예외] 숫자가 중복인 경우. - [x] [예외] 6개의 숫자가 아닌 경우. -- [ ] 보너스 번호를 입력받는다. +- [x] 보너스 번호를 입력받는다. - [x] [예외] 가격이 숫자가 아닌 경우. - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [ ] [예외] 당첨번호와 중복될 경우 + - [x] [예외] 당첨번호와 중복될 경우 - [x] 당첨 여부를 비교한다. diff --git a/src/main/kotlin/lotto/controller/LottoDraw.kt b/src/main/kotlin/lotto/controller/LottoDraw.kt index 8048c7c55..56b99540e 100644 --- a/src/main/kotlin/lotto/controller/LottoDraw.kt +++ b/src/main/kotlin/lotto/controller/LottoDraw.kt @@ -2,6 +2,7 @@ package lotto.controller import lotto.model.Lotto import lotto.model.Store +import lotto.util.ErrorMessages import lotto.view.InputView import lotto.view.OutputView @@ -13,7 +14,7 @@ class LottoDraw { val lottos = store.buyLotto(price) val myLotto = Lotto(inputView.getLottoNumber()) - val myBonus = inputView.getBonusNumber() + val myBonus = inputView.getBonusNumber(myLotto) outputView.purchasedMessage(store.numberOfLottoPurchased) outputView.lottoList(lottos) diff --git a/src/main/kotlin/lotto/util/ErrorMessages.kt b/src/main/kotlin/lotto/util/ErrorMessages.kt index f0cdfc741..33bd2cdfa 100644 --- a/src/main/kotlin/lotto/util/ErrorMessages.kt +++ b/src/main/kotlin/lotto/util/ErrorMessages.kt @@ -5,9 +5,10 @@ object ErrorMessages { const val DUPLICATE_NUMBERS = "[ERROR] 로또 번호는 중복되지 않아야 합니다." const val OUT_OF_RANGE = "[ERROR] 모든 로또 번호는 1부터 45 사이여야 합니다." const val NOT_SORTED = "[ERROR] 로또 번호는 오름차순으로 정렬되어 있어야 합니다." - const val NULL_PRICE = "[ERROR]: 구입금액을 입력해 주세요." - const val NOT_INT = "[ERROR]: 입력이 숫자가 아닙니다." - const val MINIMUM_PRICE = "[ERROR]: 로또 한 개의 가격은 1000원 이상이어야 합니다" - const val NULL_LOTTO_NUMBER = "[ERROR]: 당첨 번호를 입력해 주세요." - const val NULL_BONUS_NUMBER = "[ERROR]: 보너스 번호를 입력해 주세요." + const val NULL_PRICE = "[ERROR] 구입금액을 입력해 주세요." + const val NOT_INT = "[ERROR] 입력이 숫자가 아닙니다." + const val MINIMUM_PRICE = "[ERROR] 로또 한 개의 가격은 1000원 이상이어야 합니다" + const val NULL_LOTTO_NUMBER = "[ERROR] 당첨 번호를 입력해 주세요." + const val NULL_BONUS_NUMBER = "[ERROR] 보너스 번호를 입력해 주세요." + const val DUPLICATED_BONUS = "[ERROR] 보너스 번호는 로또 번호와 중복될 수 없습니다." } \ No newline at end of file diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index aad3da6ca..b73d48817 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -1,6 +1,7 @@ package lotto.view import camp.nextstep.edu.missionutils.Console +import lotto.model.Lotto import lotto.util.ErrorMessages class InputView { @@ -43,7 +44,7 @@ class InputView { } } - fun getBonusNumber(): Int { + fun getBonusNumber(myLotto: Lotto): Int { println("보너스 번호를 입력해 주세요.") while (true) { try { @@ -53,9 +54,11 @@ class InputView { require(input != "") { ErrorMessages.NULL_BONUS_NUMBER } require(bonusNumber != null) { ErrorMessages.NOT_INT } require(bonusNumber in 1..45) { ErrorMessages.OUT_OF_RANGE } + require(bonusNumber !in myLotto.getNumbers()) { ErrorMessages.DUPLICATED_BONUS } + return bonusNumber } catch (e: IllegalArgumentException) { - throw IllegalArgumentException(e.message) + println(e.message) } } } From 8e35461bf9ddec9ba2bfec1911e15b4f30e2df4e Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 20:28:00 +0900 Subject: [PATCH 15/26] =?UTF-8?q?feat:=20inputView=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EB=A5=BC=20throw=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B5=AC=EB=AC=B8=EC=9D=84=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/util/InputValidate.kt | 26 +++++++++++++++++++++ src/main/kotlin/lotto/view/InputView.kt | 23 +++++------------- 2 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/lotto/util/InputValidate.kt diff --git a/src/main/kotlin/lotto/util/InputValidate.kt b/src/main/kotlin/lotto/util/InputValidate.kt new file mode 100644 index 000000000..96ac9c00e --- /dev/null +++ b/src/main/kotlin/lotto/util/InputValidate.kt @@ -0,0 +1,26 @@ +package lotto.util + +import lotto.model.Lotto + +object InputValidate { + fun checkPrice(input: String, price: Int?) { + require(input != "") { ErrorMessages.NULL_PRICE } + require(price != null) { ErrorMessages.NOT_INT } + require(price >= 1000) { ErrorMessages.MINIMUM_PRICE } + } + + fun checkMyLotto(input: String, lottoNumber: List) { + require(input != "") { ErrorMessages.NULL_LOTTO_NUMBER } + require(lottoNumber.all { it != null }) { ErrorMessages.NOT_INT } + require(lottoNumber.all { it in 1..45 }) { ErrorMessages.OUT_OF_RANGE } + require(lottoNumber.size == 6) { ErrorMessages.INVALID_NUMBER_SIZE } + require(lottoNumber.distinct().size == lottoNumber.size) { ErrorMessages.DUPLICATE_NUMBERS } + } + + fun checkMyBonus(input: String, bonusNumber: Int?, myLotto: Lotto) { + require(input != "") { ErrorMessages.NULL_BONUS_NUMBER } + require(bonusNumber != null) { ErrorMessages.NOT_INT } + require(bonusNumber in 1..45) { ErrorMessages.OUT_OF_RANGE } + require(bonusNumber !in myLotto.getNumbers()) { ErrorMessages.DUPLICATED_BONUS } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index b73d48817..550ab5313 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -3,6 +3,7 @@ package lotto.view import camp.nextstep.edu.missionutils.Console import lotto.model.Lotto import lotto.util.ErrorMessages +import lotto.util.InputValidate class InputView { fun getPrice(): Int { @@ -11,11 +12,8 @@ class InputView { val input = Console.readLine() try { val price = input.toIntOrNull() - - require(input != "") { ErrorMessages.NULL_PRICE } - require(price != null) { ErrorMessages.NOT_INT } - require(price >= 1000) { ErrorMessages.MINIMUM_PRICE } - return price + InputValidate.checkPrice(input, price) + return price ?: 0 } catch (e: IllegalArgumentException) { println(e.message) } @@ -28,16 +26,10 @@ class InputView { try { val input = Console.readLine() val lottoNumber = input.split(",").map { it.trim().toIntOrNull() } + InputValidate.checkMyLotto(input, lottoNumber) - require(input != "") { ErrorMessages.NULL_LOTTO_NUMBER } - require(lottoNumber.all { it != null }) { ErrorMessages.NOT_INT } - require(lottoNumber.all { it in 1..45 }) { ErrorMessages.OUT_OF_RANGE } - require(lottoNumber.size == 6) { ErrorMessages.INVALID_NUMBER_SIZE } - require(lottoNumber.distinct().size == lottoNumber.size) { ErrorMessages.DUPLICATE_NUMBERS } val notNullLottoNumber: List = lottoNumber.filterNotNull().sorted() - return notNullLottoNumber - } catch (e: Exception) { println(e.message) } @@ -51,12 +43,9 @@ class InputView { val input = Console.readLine() val bonusNumber = input.toIntOrNull() - require(input != "") { ErrorMessages.NULL_BONUS_NUMBER } - require(bonusNumber != null) { ErrorMessages.NOT_INT } - require(bonusNumber in 1..45) { ErrorMessages.OUT_OF_RANGE } - require(bonusNumber !in myLotto.getNumbers()) { ErrorMessages.DUPLICATED_BONUS } + InputValidate.checkMyBonus(input, bonusNumber, myLotto) - return bonusNumber + return bonusNumber ?: 0 } catch (e: IllegalArgumentException) { println(e.message) } From 0f31fd8845240ff7d4b88c624c62cf6bcc9b23d6 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 20:44:34 +0900 Subject: [PATCH 16/26] =?UTF-8?q?refactor:=20Lotto=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=82=B4=EB=B6=80=EC=9D=98=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/controller/LottoDraw.kt | 2 +- src/main/kotlin/lotto/util/InputValidate.kt | 8 -------- src/main/kotlin/lotto/view/InputView.kt | 15 ++++++++------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/lotto/controller/LottoDraw.kt b/src/main/kotlin/lotto/controller/LottoDraw.kt index 56b99540e..caa4a1aff 100644 --- a/src/main/kotlin/lotto/controller/LottoDraw.kt +++ b/src/main/kotlin/lotto/controller/LottoDraw.kt @@ -13,7 +13,7 @@ class LottoDraw { val store = Store() val lottos = store.buyLotto(price) - val myLotto = Lotto(inputView.getLottoNumber()) + val myLotto = inputView.getMyLotto() val myBonus = inputView.getBonusNumber(myLotto) outputView.purchasedMessage(store.numberOfLottoPurchased) diff --git a/src/main/kotlin/lotto/util/InputValidate.kt b/src/main/kotlin/lotto/util/InputValidate.kt index 96ac9c00e..d9a9fe563 100644 --- a/src/main/kotlin/lotto/util/InputValidate.kt +++ b/src/main/kotlin/lotto/util/InputValidate.kt @@ -9,14 +9,6 @@ object InputValidate { require(price >= 1000) { ErrorMessages.MINIMUM_PRICE } } - fun checkMyLotto(input: String, lottoNumber: List) { - require(input != "") { ErrorMessages.NULL_LOTTO_NUMBER } - require(lottoNumber.all { it != null }) { ErrorMessages.NOT_INT } - require(lottoNumber.all { it in 1..45 }) { ErrorMessages.OUT_OF_RANGE } - require(lottoNumber.size == 6) { ErrorMessages.INVALID_NUMBER_SIZE } - require(lottoNumber.distinct().size == lottoNumber.size) { ErrorMessages.DUPLICATE_NUMBERS } - } - fun checkMyBonus(input: String, bonusNumber: Int?, myLotto: Lotto) { require(input != "") { ErrorMessages.NULL_BONUS_NUMBER } require(bonusNumber != null) { ErrorMessages.NOT_INT } diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index 550ab5313..99d08048f 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -20,17 +20,18 @@ class InputView { } } - fun getLottoNumber(): List { + fun getMyLotto(): Lotto { println("당첨 번호를 입력해 주세요.") while (true) { try { - val input = Console.readLine() - val lottoNumber = input.split(",").map { it.trim().toIntOrNull() } - InputValidate.checkMyLotto(input, lottoNumber) - + val input = Console.readLine().split(",") + val lottoNumber = input.map { it.trim().toIntOrNull() } val notNullLottoNumber: List = lottoNumber.filterNotNull().sorted() - return notNullLottoNumber - } catch (e: Exception) { + + require(lottoNumber.all { it != null }) { ErrorMessages.NOT_INT } + + return Lotto(notNullLottoNumber) + } catch (e: IllegalArgumentException) { println(e.message) } } From b2c5ef3b481dc0c0df0f914ff6f9857e29968677 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 21:46:36 +0900 Subject: [PATCH 17/26] =?UTF-8?q?refactor:=20Enum=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../lotto/controller/LottoResultChecker.kt | 47 ++++++++++++------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b496b0dc1..2e2db20bb 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ - [ ] else를 지양한다. - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. -- [ ] Enum 클래스를 적용하여 프로그램을 구현한다. +- [x] Enum 클래스를 적용하여 프로그램을 구현한다. - [ ] 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. - 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다. diff --git a/src/main/kotlin/lotto/controller/LottoResultChecker.kt b/src/main/kotlin/lotto/controller/LottoResultChecker.kt index 7c14fd5c1..dadd3ad3c 100644 --- a/src/main/kotlin/lotto/controller/LottoResultChecker.kt +++ b/src/main/kotlin/lotto/controller/LottoResultChecker.kt @@ -3,30 +3,43 @@ package lotto.controller import lotto.model.Lotto object LottoResultChecker { - fun checkWinningStatus(lottos: List, myLotto: Lotto, myBonus: Int) - : Pair, Int> { - val winningCounts = MutableList(7) { 0 } - var bonusWin = 0; + + fun checkWinningStatus(lottos: List, myLotto: Lotto, myBonus: Int): List { + val ranking = MutableList(6) { 0 } for (lotto in lottos) { - val sameValueOfLotto = lotto.getNumbers().intersect(myLotto.getNumbers().toSet()) - val matchingCount = sameValueOfLotto.size - val containsBonus = myBonus in lotto.getNumbers() - - if (matchingCount >= 3) - winningCounts[matchingCount]++ - if (matchingCount == 5 && containsBonus) { - bonusWin++ - winningCounts[matchingCount]-- + val (duplicateCount, isBonus) = compareLotto(lotto, myLotto, myBonus) + when (duplicateCount) { + 3 -> ranking[5]++ + 4 -> ranking[4]++ + 5 -> if (isBonus) ranking[2]++ else ranking[3]++ + 6 -> ranking[1]++ } } - return Pair(winningCounts, bonusWin) + return ranking + } + + private fun compareLotto(purchasedLotto: Lotto, myLotto: Lotto, myBonus: Int): Pair { + val sameValueOfLotto = purchasedLotto.getNumbers().intersect(myLotto.getNumbers().toSet()) + val duplicateCount = sameValueOfLotto.size + val isBonus = myBonus in purchasedLotto.getNumbers() + + return Pair(duplicateCount, isBonus) } - fun calculateProfitRate(money: Int, winningCounts: List, bonusWin: Int): Double { - val profit = - winningCounts[3] * 5000 + winningCounts[4] * 50000 + winningCounts[5] * 1500000 + winningCounts[6] * 2000000000 + bonusWin * 30000000 + fun calculateProfitRate(money: Int, ranking: List): Double { + val profit = RankPerPrice.entries.toTypedArray().sumOf { rank -> + (ranking.getOrNull(rank.ranked) ?: 0) * rank.price + } val profitRate = profit.toDouble() / money return profitRate * 100 } + + enum class RankPerPrice(val ranked: Int, val price: Int) { + FIRST(1, 2000000000), + SECOND(2, 30000000), + THIRD(3, 1500000), + FORTH(4, 50000), + FIFTH(5, 5000); + } } \ No newline at end of file From 88c340764d2bebcfea7e978fddaac7eb54205b8d Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 21:56:40 +0900 Subject: [PATCH 18/26] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- src/main/kotlin/lotto/controller/LottoDraw.kt | 6 +++--- src/main/kotlin/lotto/view/InputView.kt | 7 ++++--- src/main/kotlin/lotto/view/OutputView.kt | 12 ++++++------ src/test/kotlin/lotto/LottoResultCheckerTest.kt | 6 +++--- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2e2db20bb..92dda58fd 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,9 @@ ## 프로그래밍 요구 사항 3 -- [ ] 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. -- [ ] 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. -- [ ] else를 지양한다. +- [x] 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. +- [x] 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +- [x] else를 지양한다. - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - [x] Enum 클래스를 적용하여 프로그램을 구현한다. diff --git a/src/main/kotlin/lotto/controller/LottoDraw.kt b/src/main/kotlin/lotto/controller/LottoDraw.kt index caa4a1aff..ef576accb 100644 --- a/src/main/kotlin/lotto/controller/LottoDraw.kt +++ b/src/main/kotlin/lotto/controller/LottoDraw.kt @@ -19,8 +19,8 @@ class LottoDraw { outputView.purchasedMessage(store.numberOfLottoPurchased) outputView.lottoList(lottos) - val (winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) - val profitRatio = LottoResultChecker.calculateProfitRate(price, winningCounts, bonusWin) - outputView.resultStatistics(winningCounts, bonusWin, profitRatio) + val ranking = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) + val profitRatio = LottoResultChecker.calculateProfitRate(price, ranking) + outputView.resultStatistics(ranking, profitRatio) } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index 99d08048f..1d3b7429b 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -24,10 +24,11 @@ class InputView { println("당첨 번호를 입력해 주세요.") while (true) { try { - val input = Console.readLine().split(",") - val lottoNumber = input.map { it.trim().toIntOrNull() } - val notNullLottoNumber: List = lottoNumber.filterNotNull().sorted() + val input = Console.readLine() + require(input != "") { ErrorMessages.NULL_LOTTO_NUMBER } + val lottoNumber = input.split(",").map { it.trim().toIntOrNull() } + val notNullLottoNumber: List = lottoNumber.filterNotNull().sorted() require(lottoNumber.all { it != null }) { ErrorMessages.NOT_INT } return Lotto(notNullLottoNumber) diff --git a/src/main/kotlin/lotto/view/OutputView.kt b/src/main/kotlin/lotto/view/OutputView.kt index 6fe5ebe89..8facf990d 100644 --- a/src/main/kotlin/lotto/view/OutputView.kt +++ b/src/main/kotlin/lotto/view/OutputView.kt @@ -13,16 +13,16 @@ class OutputView { } } - fun resultStatistics(winningCounts: List, bonusWin: Int, profitRatio: Double) { + fun resultStatistics(ranking: List, profitRatio: Double) { println( "당첨 통계\n" + "---" ) - println("${3}개 일치 (5,000원) - ${winningCounts[3]}개\n" + - "${4}개 일치 (50,000원) - ${winningCounts[4]}개\n" + - "${5}개 일치 (1,500,000원) - ${winningCounts[5]}개\n" + - "${5}개 일치, 보너스 볼 일치 (30,000,000원) - ${bonusWin}개\n" + - "${6}개 일치 (2,000,000,000원) - ${winningCounts[6]}개\n") + println("${3}개 일치 (5,000원) - ${ranking[5]}개\n" + + "${4}개 일치 (50,000원) - ${ranking[4]}개\n" + + "${5}개 일치 (1,500,000원) - ${ranking[3]}개\n" + + "${5}개 일치, 보너스 볼 일치 (30,000,000원) - ${ranking[2]}개\n" + + "${6}개 일치 (2,000,000,000원) - ${ranking[1]}개\n") println("총 수익률은 ${profitRatio}%입니다.") } } \ No newline at end of file diff --git a/src/test/kotlin/lotto/LottoResultCheckerTest.kt b/src/test/kotlin/lotto/LottoResultCheckerTest.kt index 47ffd3276..2ccc085c8 100644 --- a/src/test/kotlin/lotto/LottoResultCheckerTest.kt +++ b/src/test/kotlin/lotto/LottoResultCheckerTest.kt @@ -27,10 +27,10 @@ class LottoResultCheckerTest { @Test fun `수익률 정상적으로 계산하는지 확인`() { - val winningCounts = listOf(0, 0, 0, 1, 1, 1, 1) + val ranking = listOf(0, 1, 1, 1, 1, 1) val bonusWin = 1 val money = 5000 - val profitRate = LottoResultChecker.calculateProfitRate(money, winningCounts, bonusWin) - assertEquals(profitRate, (2000000000 + 30000000 + 1500000 + 50000 + 5000)/money.toDouble()) + val profitRate = LottoResultChecker.calculateProfitRate(money, ranking) + assertEquals(profitRate, (2000000000 + 30000000 + 1500000 + 50000 + 5000) / money.toDouble()) } } \ No newline at end of file From b400112c4b49d7d587f43ed01c8adf39484c87b7 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:12:05 +0900 Subject: [PATCH 19/26] =?UTF-8?q?test:=20LottoResultChecker=EC=99=80=20Lot?= =?UTF-8?q?to=EC=9D=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/controller/LottoResultChecker.kt | 2 +- src/test/kotlin/lotto/LottoResultCheckerTest.kt | 16 ++++++++++++---- src/test/kotlin/lotto/LottoTest.kt | 14 +++++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/lotto/controller/LottoResultChecker.kt b/src/main/kotlin/lotto/controller/LottoResultChecker.kt index dadd3ad3c..ac1fbb4c8 100644 --- a/src/main/kotlin/lotto/controller/LottoResultChecker.kt +++ b/src/main/kotlin/lotto/controller/LottoResultChecker.kt @@ -19,7 +19,7 @@ object LottoResultChecker { return ranking } - private fun compareLotto(purchasedLotto: Lotto, myLotto: Lotto, myBonus: Int): Pair { + fun compareLotto(purchasedLotto: Lotto, myLotto: Lotto, myBonus: Int): Pair { val sameValueOfLotto = purchasedLotto.getNumbers().intersect(myLotto.getNumbers().toSet()) val duplicateCount = sameValueOfLotto.size val isBonus = myBonus in purchasedLotto.getNumbers() diff --git a/src/test/kotlin/lotto/LottoResultCheckerTest.kt b/src/test/kotlin/lotto/LottoResultCheckerTest.kt index 2ccc085c8..0e624d969 100644 --- a/src/test/kotlin/lotto/LottoResultCheckerTest.kt +++ b/src/test/kotlin/lotto/LottoResultCheckerTest.kt @@ -19,16 +19,24 @@ class LottoResultCheckerTest { val myLotto = Lotto(listOf(1, 2, 3, 4, 5, 6)) val myBonus = 7 - val (winningCounts, bonusWin) = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) + val ranking = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) - assertEquals(listOf(0, 0, 0, 1, 1, 1, 1), winningCounts) - assertEquals(1, bonusWin) + assertEquals(listOf(0, 1, 1, 1, 1, 1), ranking) + } + + @Test + fun `compareLotto 겹치는 개수 정상적으로 계산하는지 확인`() { + val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6)) + val myLotto = Lotto(listOf(1, 2, 3, 4, 5, 9)) + val myBonus = 6 + val (duplicateCount, isBonus) = LottoResultChecker.compareLotto(lotto, myLotto, myBonus) + assertEquals(duplicateCount, 5) + assertEquals(isBonus, true) } @Test fun `수익률 정상적으로 계산하는지 확인`() { val ranking = listOf(0, 1, 1, 1, 1, 1) - val bonusWin = 1 val money = 5000 val profitRate = LottoResultChecker.calculateProfitRate(money, ranking) assertEquals(profitRate, (2000000000 + 30000000 + 1500000 + 50000 + 5000) / money.toDouble()) diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index c526c65d4..6c86a1457 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,6 +1,7 @@ package lotto import lotto.model.Lotto +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -29,7 +30,6 @@ class LottoTest { } } - // TODO: 테스트가 통과하도록 프로덕션 코드 구현 @Test fun `로또 번호에 중복된 숫자가 있으면 예외가 발생한다`() { assertThrows { @@ -45,4 +45,16 @@ class LottoTest { } // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + @Test + fun `getNumbers 함수에서 list가 정상 반환되는지 확인`() { + val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6)) + assertEquals(lotto.getNumbers(), listOf(1, 2, 3, 4, 5, 6)) + } + + @Test + fun `toString 함수가 해쉬코드가 아닌 값을 반환하는지 확인`() { + val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6)) + assertEquals(lotto.toString(), "[1, 2, 3, 4, 5, 6]") + + } } From ae6756f76bb1c97725ea8575b3b5e21d3bfe5353 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:14:49 +0900 Subject: [PATCH 20/26] =?UTF-8?q?test:=20StoreTest=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/kotlin/lotto/StoreTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/lotto/StoreTest.kt b/src/test/kotlin/lotto/StoreTest.kt index f67362543..ff633ef5c 100644 --- a/src/test/kotlin/lotto/StoreTest.kt +++ b/src/test/kotlin/lotto/StoreTest.kt @@ -11,7 +11,7 @@ class StoreTest { @Test fun `생성한 로또가 예외를 발생시키지는 않는지 검사한다`() { assertDoesNotThrow { - lotto + store.generateLotto() } } From 90f748fcabfaf9cdfe0aed68848c600c3691cd30 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:26:27 +0900 Subject: [PATCH 21/26] =?UTF-8?q?test:=20InputValidateTest=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 --- .../kotlin/lotto/controller/LottoDrawTest.kt | 12 ++++ .../kotlin/lotto/util/InputValidateTest.kt | 64 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/test/kotlin/lotto/controller/LottoDrawTest.kt create mode 100644 src/test/kotlin/lotto/util/InputValidateTest.kt diff --git a/src/test/kotlin/lotto/controller/LottoDrawTest.kt b/src/test/kotlin/lotto/controller/LottoDrawTest.kt new file mode 100644 index 000000000..5153dbb71 --- /dev/null +++ b/src/test/kotlin/lotto/controller/LottoDrawTest.kt @@ -0,0 +1,12 @@ +package lotto.controller + +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +class LottoDrawTest { + + @Test + fun run() { + } +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/util/InputValidateTest.kt b/src/test/kotlin/lotto/util/InputValidateTest.kt new file mode 100644 index 000000000..fe1cb2b20 --- /dev/null +++ b/src/test/kotlin/lotto/util/InputValidateTest.kt @@ -0,0 +1,64 @@ +package lotto.util + +import lotto.model.Lotto +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.assertThrows + +class InputValidateTest { + + @Test + fun `checkPrice 돈을 안내면 예외가 발생한다`() { + assertThrows { + InputValidate.checkPrice("", null) + } + } + + @Test + fun `checkPrice 문자를 입력하면 예외가 발생한다`() { + assertThrows { + InputValidate.checkPrice("text", null) + } + } + + @Test + fun `checkPrice 1000원보다 적은 돈을 내면 예외가 발생한다`() { + assertThrows { + InputValidate.checkPrice("900", 900) + } + } + + + @Test + fun `checkMyBonus 값을 입력하지 않으면 예외가 발생한다`() { + val lotto = Lotto(listOf(1,2,3,4,5,6)) + assertThrows { + InputValidate.checkMyBonus("", null, lotto) + } + } + + @Test + fun `checkMyBonus 문자를 입력하면 예외가 발생한다`() { + val lotto = Lotto(listOf(1,2,3,4,5,6)) + assertThrows { + InputValidate.checkMyBonus("text", null, lotto) + } + } + + @Test + fun `checkMyBonus 1과 45사이의 값이 아니면 예외가 발생한다`() { + val lotto = Lotto(listOf(1,2,3,4,5,6)) + assertThrows { + InputValidate.checkMyBonus("900", 900, lotto) + } + } + + @Test + fun `checkMyBonus 보너스 번호와 당첨번호가 겹치면 예외가 발생한다`() { + val lotto = Lotto(listOf(1,2,3,4,5,6)) + assertThrows { + InputValidate.checkMyBonus("3", 3, lotto) + } + } +} \ No newline at end of file From 41d0d1ebf2fb96c76ef8a52e879cdc6bf5ec1d60 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:30:12 +0900 Subject: [PATCH 22/26] =?UTF-8?q?style:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=93=A4=EC=9D=84=20=ED=8C=A8=ED=82=A4=EC=A7=80=EC=97=90=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/test/kotlin/lotto/controller/LottoDrawTest.kt | 2 +- .../kotlin/lotto/{ => controller}/LottoResultCheckerTest.kt | 3 +-- src/test/kotlin/lotto/{ => model}/LottoTest.kt | 3 +-- src/test/kotlin/lotto/{ => model}/StoreTest.kt | 3 +-- 5 files changed, 5 insertions(+), 8 deletions(-) rename src/test/kotlin/lotto/{ => controller}/LottoResultCheckerTest.kt (96%) rename src/test/kotlin/lotto/{ => model}/LottoTest.kt (97%) rename src/test/kotlin/lotto/{ => model}/StoreTest.kt (93%) diff --git a/README.md b/README.md index 92dda58fd..23d2a57d5 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ - 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - [x] Enum 클래스를 적용하여 프로그램을 구현한다. -- [ ] 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. +- [x] 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. - 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다. # 피드백 diff --git a/src/test/kotlin/lotto/controller/LottoDrawTest.kt b/src/test/kotlin/lotto/controller/LottoDrawTest.kt index 5153dbb71..65cb680e0 100644 --- a/src/test/kotlin/lotto/controller/LottoDrawTest.kt +++ b/src/test/kotlin/lotto/controller/LottoDrawTest.kt @@ -7,6 +7,6 @@ import org.junit.jupiter.api.Assertions.* class LottoDrawTest { @Test - fun run() { + fun `run 함수가 `() { } } \ No newline at end of file diff --git a/src/test/kotlin/lotto/LottoResultCheckerTest.kt b/src/test/kotlin/lotto/controller/LottoResultCheckerTest.kt similarity index 96% rename from src/test/kotlin/lotto/LottoResultCheckerTest.kt rename to src/test/kotlin/lotto/controller/LottoResultCheckerTest.kt index 0e624d969..b8e4d66af 100644 --- a/src/test/kotlin/lotto/LottoResultCheckerTest.kt +++ b/src/test/kotlin/lotto/controller/LottoResultCheckerTest.kt @@ -1,6 +1,5 @@ -package lotto +package lotto.controller -import lotto.controller.LottoResultChecker import lotto.model.Lotto import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/model/LottoTest.kt similarity index 97% rename from src/test/kotlin/lotto/LottoTest.kt rename to src/test/kotlin/lotto/model/LottoTest.kt index 6c86a1457..2d890fba0 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/model/LottoTest.kt @@ -1,6 +1,5 @@ -package lotto +package lotto.model -import lotto.model.Lotto import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/lotto/StoreTest.kt b/src/test/kotlin/lotto/model/StoreTest.kt similarity index 93% rename from src/test/kotlin/lotto/StoreTest.kt rename to src/test/kotlin/lotto/model/StoreTest.kt index ff633ef5c..1f7852886 100644 --- a/src/test/kotlin/lotto/StoreTest.kt +++ b/src/test/kotlin/lotto/model/StoreTest.kt @@ -1,6 +1,5 @@ -package lotto +package lotto.model -import lotto.model.Store import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.assertj.core.api.Assertions.assertThat From 26ed47a76026e905a94f85afa5c4f25e7d1ba012 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:34:14 +0900 Subject: [PATCH 23/26] =?UTF-8?q?style:=20print=EA=B0=92=EC=9D=98=20?= =?UTF-8?q?=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- src/main/kotlin/lotto/view/InputView.kt | 12 +++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 23d2a57d5..83e51f5ef 100644 --- a/README.md +++ b/README.md @@ -141,11 +141,11 @@ # 피드백 -- [ ] 기능 목록을 재검토한다. - - [ ] 기능 목록을 작성할 때 클래스 설계와 구현, 메서드 설계와 구현 등은 포함하지 않는다. - - [ ] 예외상황도 함께 정리한다. -- [ ] 기능 목록을 업데이트한다. 기능을 구현하면서 문서를 지속적으로 업데이트한다. -- [ ] **문자열이나 숫자 값을 하드 코딩하지 않는다. 상수를 정의하고 의미있는 이름을 부여하여 해당 값이 어떤 역할을 하는지 명확히 드러낸다.** +- [x] 기능 목록을 재검토한다. + - [x] 기능 목록을 작성할 때 클래스 설계와 구현, 메서드 설계와 구현 등은 포함하지 않는다. + - [x] 예외상황도 함께 정리한다. +- [x] 기능 목록을 업데이트한다. 기능을 구현하면서 문서를 지속적으로 업데이트한다. +- [X] **문자열이나 숫자 값을 하드 코딩하지 않는다. 상수를 정의하고 의미있는 이름을 부여하여 해당 값이 어떤 역할을 하는지 명확히 드러낸다.** - [ ] 구현 순서도 코딩 컨벤션이다. - [ ] 프로퍼티, init블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다. - [ ] 변수 이름에 자료형은 사용하지 않는다. 변수 이름은 의미를 명확히 드러낼 수 있도록 한다. diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index 1d3b7429b..19da8f4d6 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -7,7 +7,7 @@ import lotto.util.InputValidate class InputView { fun getPrice(): Int { - println("구입금액을 입력해 주세요.") + println(GET_PRICE) while (true) { val input = Console.readLine() try { @@ -21,7 +21,7 @@ class InputView { } fun getMyLotto(): Lotto { - println("당첨 번호를 입력해 주세요.") + println(GET_MY_LOTTO) while (true) { try { val input = Console.readLine() @@ -39,7 +39,7 @@ class InputView { } fun getBonusNumber(myLotto: Lotto): Int { - println("보너스 번호를 입력해 주세요.") + println(GET_BONUS_NUMBER) while (true) { try { val input = Console.readLine() @@ -53,4 +53,10 @@ class InputView { } } } + + companion object{ + const val GET_PRICE = "구입금액을 입력해 주세요." + const val GET_MY_LOTTO = "당첨 번호를 입력해 주세요." + const val GET_BONUS_NUMBER = "보너스 번호를 입력해 주세요." + } } \ No newline at end of file From ed6fc515fdf7cd58f3af72ef056eee89cd38b109 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:37:55 +0900 Subject: [PATCH 24/26] =?UTF-8?q?chore:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=A3=BC=EC=8B=A0=20=EB=82=B4=EC=9A=A9=20=EA=B2=80=ED=86=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++------ src/main/kotlin/lotto/controller/LottoDraw.kt | 2 +- src/main/kotlin/lotto/view/OutputView.kt | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 83e51f5ef..f2f94315a 100644 --- a/README.md +++ b/README.md @@ -146,12 +146,12 @@ - [x] 예외상황도 함께 정리한다. - [x] 기능 목록을 업데이트한다. 기능을 구현하면서 문서를 지속적으로 업데이트한다. - [X] **문자열이나 숫자 값을 하드 코딩하지 않는다. 상수를 정의하고 의미있는 이름을 부여하여 해당 값이 어떤 역할을 하는지 명확히 드러낸다.** -- [ ] 구현 순서도 코딩 컨벤션이다. - - [ ] 프로퍼티, init블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다. -- [ ] 변수 이름에 자료형은 사용하지 않는다. 변수 이름은 의미를 명확히 드러낼 수 있도록 한다. +- [x] 구현 순서도 코딩 컨벤션이다. + - [x] 프로퍼티, init블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다. +- [x] 변수 이름에 자료형은 사용하지 않는다. 변수 이름은 의미를 명확히 드러낼 수 있도록 한다. - [ ] 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다. (회고때 포함) - [ ] 테스트 작성 과정을 통해 구현한 기능의 문제를 빠르게 발견할 수 있을 뿐만 아니라, 코드의 구조와 의도를 명확히 이해하는 데도 도움을 받을 수 있다. 학습 도구로도 활용할 수 있는데, 수 많은 테스트의 장점 중 본인이 가장 공감하는 작성 이유를 작성해 본다. -- [ ] 처음부터 큰 단위 테스트를 만들지 않는다. → 테스트 주도 개발을 하자 - - [ ] 작은 단위 테스트 : 무작위 값이 4 이상이면 자동차가 전진한다. - - [ ] 큰 단위 테스트 : 자동차 경주 게임을 시작하여, 사용자가 이름과 진행횟수를 입력하고, 게임을진행한 후 결과를 확인한다. \ No newline at end of file +- [x] 처음부터 큰 단위 테스트를 만들지 않는다. → 테스트 주도 개발을 하자 + - [x] 작은 단위 테스트 : 무작위 값이 4 이상이면 자동차가 전진한다. + - [x] 큰 단위 테스트 : 자동차 경주 게임을 시작하여, 사용자가 이름과 진행횟수를 입력하고, 게임을진행한 후 결과를 확인한다. \ No newline at end of file diff --git a/src/main/kotlin/lotto/controller/LottoDraw.kt b/src/main/kotlin/lotto/controller/LottoDraw.kt index ef576accb..aa3a4f7a2 100644 --- a/src/main/kotlin/lotto/controller/LottoDraw.kt +++ b/src/main/kotlin/lotto/controller/LottoDraw.kt @@ -17,7 +17,7 @@ class LottoDraw { val myBonus = inputView.getBonusNumber(myLotto) outputView.purchasedMessage(store.numberOfLottoPurchased) - outputView.lottoList(lottos) + outputView.purchasedLotto(lottos) val ranking = LottoResultChecker.checkWinningStatus(lottos, myLotto, myBonus) val profitRatio = LottoResultChecker.calculateProfitRate(price, ranking) diff --git a/src/main/kotlin/lotto/view/OutputView.kt b/src/main/kotlin/lotto/view/OutputView.kt index 8facf990d..69bf04886 100644 --- a/src/main/kotlin/lotto/view/OutputView.kt +++ b/src/main/kotlin/lotto/view/OutputView.kt @@ -7,7 +7,7 @@ class OutputView { println("${num}개를 구매했습니다.") } - fun lottoList(lottos: List) { + fun purchasedLotto(lottos: List) { for (lotto in lottos) { println(lotto) } From 8cea67d25e3a1241012013f4fe8cd5b11e65eb95 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 22:44:41 +0900 Subject: [PATCH 25/26] =?UTF-8?q?docs:=20=EA=B0=80=EB=8F=85=EC=84=B1?= =?UTF-8?q?=EC=9E=88=EA=B2=8C=20README.md=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 71 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index f2f94315a..62cc89be0 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,55 @@ # kotlin-lotto-precourse # 기능 구현 목록 +### 1. 입력 -- [x] 로또를 발행한다. - - [x] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. - - [x] 오름차순으로 정렬한다. - -- [x] 로또가 알맞은 형식인지 검사한다. - - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [x] [예외] 숫자가 중복인 경우. - - [x] [예외] 6개의 숫자가 아닌 경우. - - [x] [예외] 오름차순이 아닌 경우. - -- [x] 구입 금액에 해당하는 만큼 로또를 발행한다. -- [x] 로또 1장의 가격은 1,000원이다. - [x] 로또 구입 금액을 입력받는다. - - [x] [예외] 가격이 숫자가 아닌 경우. - - [x] [예외] 가격이 1000원 미만인 경우. + - [x] [예외] 가격이 숫자가 아닌 경우. + - [x] [예외] 가격이 1000원 미만인 경우. - [x] 당첨 번호를 입력받는다. - - [x] [예외] 문자인 경우. - - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [x] [예외] 숫자가 중복인 경우. - - [x] [예외] 6개의 숫자가 아닌 경우. + - [x] [예외] 문자인 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 숫자가 중복인 경우. + - [x] [예외] 6개의 숫자가 아닌 경우. - [x] 보너스 번호를 입력받는다. - - [x] [예외] 가격이 숫자가 아닌 경우. - - [x] [예외] 1~45사이의 숫자가 아닌 경우. - - [x] [예외] 당첨번호와 중복될 경우 + - [x] [예외] 가격이 숫자가 아닌 경우. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 당첨번호와 중복될 경우 -- [x] 당첨 여부를 비교한다. +- [x] 사용자가 잘못된 값을 입력할 경우 + - [x] `IllegalArgumentException`을 발생시킨다. + - [x] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + - [x] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. -``` - - 1등: 6개 번호 일치 / 2,000,000,000원 - - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 - - 3등: 5개 번호 일치 / 1,500,000원 - - 4등: 4개 번호 일치 / 50,000원 - - 5등: 3개 번호 일치 / 5,000원 -``` +### 2. 로또 발행 -- [x] 사용자가 구매한 로또 번호와 당첨 번호를 비교한다. - - [x] 당첨 내역을 구한다. +- [x] 로또를 발행한다. + - [x] 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. + - [x] 오름차순으로 정렬한다. + - [x] 구입 금액에 해당하는 만큼 로또를 발행한다. + - [x] 로또 1장의 가격은 1,000원이다. + +- [x] 로또가 알맞은 형식인지 검사한다. + - [x] [예외] 1~45사이의 숫자가 아닌 경우. + - [x] [예외] 숫자가 중복인 경우. + - [x] [예외] 6개의 숫자가 아닌 경우. + - [x] [예외] 오름차순이 아닌 경우. + +### 3. 당첨 내역 저장 + +- [x] 당첨 여부를 비교한다. + - [x] 로또 리스트에서 로또 하나씩 사용자가 구매한 로또 번호와 당첨 번호를 비교한다. + - [x] 몇등을 했는지 당첨 내역을 구한다. - [x] 수익률을 구한다. + - [x] 당첨 내역을 토대로 수익을 구한다. -- [x] 사용자가 잘못된 값을 입력할 경우 - - [x] `IllegalArgumentException`을 발생시킨다. - - [x] "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. - - [x] `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다. +### 4. 출력 +- [x] n개일치가 몇 번 있는지 출력한다. +- [x] 수익률을 출력한다. # 입출력 요구 사항 From 1038ac87ec077a4e00ced6b59f58710bab9035e2 Mon Sep 17 00:00:00 2001 From: Grove1212 Date: Mon, 4 Nov 2024 23:56:24 +0900 Subject: [PATCH 26/26] docs: update README.md --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 62cc89be0..78e748504 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ # 기능 구현 목록 ### 1. 입력 - - - [x] 로또 구입 금액을 입력받는다. - [x] [예외] 가격이 숫자가 아닌 경우. - [x] [예외] 가격이 1000원 미만인 경우. @@ -150,8 +148,8 @@ - [x] 구현 순서도 코딩 컨벤션이다. - [x] 프로퍼티, init블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다. - [x] 변수 이름에 자료형은 사용하지 않는다. 변수 이름은 의미를 명확히 드러낼 수 있도록 한다. -- [ ] 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다. (회고때 포함) - - [ ] 테스트 작성 과정을 통해 구현한 기능의 문제를 빠르게 발견할 수 있을 뿐만 아니라, 코드의 구조와 의도를 명확히 이해하는 데도 도움을 받을 수 있다. 학습 도구로도 활용할 수 있는데, 수 많은 +- [x] 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다. (회고때 포함) + - [x] 테스트 작성 과정을 통해 구현한 기능의 문제를 빠르게 발견할 수 있을 뿐만 아니라, 코드의 구조와 의도를 명확히 이해하는 데도 도움을 받을 수 있다. 학습 도구로도 활용할 수 있는데, 수 많은 테스트의 장점 중 본인이 가장 공감하는 작성 이유를 작성해 본다. - [x] 처음부터 큰 단위 테스트를 만들지 않는다. → 테스트 주도 개발을 하자 - [x] 작은 단위 테스트 : 무작위 값이 4 이상이면 자동차가 전진한다.