From b374be427229fdc068e2bb0c04c8d63b4eec1d62 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sat, 2 Nov 2024 22:54:34 +0900 Subject: [PATCH 01/31] =?UTF-8?q?docs(README.md):=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs(README.md): 구현기능 목록 작성 README.md 구현기능 목록 작성 - 구현 기능목록 (구매가능 금액 최대값 SAFE INTEGER로 변경) --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15bb106b5..e2b5d53ba 100644 --- a/README.md +++ b/README.md @@ -1 +1,47 @@ -# javascript-lotto-precourse +# [3주차] 로또 + +---- + +## 1. 구현할 기능 목록 + +---- + +### 1. 입력값 받기 : 로또 구입 금액 입력받기 +### 2. 구매한 로또의 총 갯수를 구하고, 문구를 출력한다. +### 3. 구매한 로또들의 랜덤 번호 생성 +- 구매한 로또번호들의 6가지 랜덤 번호를 생성한다 +- 오름차순으로 정렬하여 저장한다 +- 구매한 로또 번호를 출력한다. +### 4. 당첨 번호를 입력받는다 +### 5. 보너스 번호를 입력받는다. +### 6. 당첨 통계를 구한다 +- 각각의 index가 일치한 숫자의 갯수를 의미하는 길이가 8인 배열을 만든다. (index = 6은 2등을, index = 7은 1등을 의미한다.) +- 구매한 로또들을 순회 + - 하나의 로또에서 몇개의 번호가 맞았는지 구한다. + - 배열[일치한 갯수] 에 값을 증가시킨다. + - 만약 5개 맞았다면, 보너스 번호 일치 여부를 확인한다 + - 만약 6개 맞았다면, index = 7에 저장한다. +### 7. 수익률을 계산한다 + +- 배열을 사용하여 (index = 7 은 1등 ... index = 3 은 5등) 당첨금의 합을 구한다 +- 당첨금/지불한 값으로 수익률을 계산한다 +- 소수점 둘째 자리에서 반올림한다 + +---- + +## 2. 예외 케이스 +### 1. 로또 구입 금액 입력받기 +- 1000원 단위 금액이 아닌 경우 +- 양의 정수를 입력했는가(/^[1-9]\d*$/) +- 최대값 제한 (SAFE INTEGER 범위) + +### 4. 로또 당첨 번호 입력 +- 당첨번호가 6개가 아닌 경우 +- 양의 정수를 입력했는가 (/^[1-9]\d*$/) +- 당첨번호가 유효범위 내에 있지 않은 경우 (1~45의 정수가 아님) +- 중복된 수를 입력한 경우 + +### 5. 보너스 번호 입력 +- 양의 정수를 입력했는가 +- 당첨번호가 유효범위 내에 있지 않은 경우 +- 당첨 번호와 중복되는지 여부 \ No newline at end of file From 114bf5f9296b5afa87df6c4ca3ef56a2b25dc89c Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sat, 2 Nov 2024 23:11:34 +0900 Subject: [PATCH 02/31] =?UTF-8?q?feat=20:=20=EB=A1=9C=EB=98=90=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=EA=B8=88=EC=95=A1=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 로또 구입금액 입력 - 로또 구입 금액 입력받기 - 로또 구입 금액 validation 체크 - 숫자(양의 정수)값을 입력했는가? - 최대값 범위 내의 정수를 입력했는가? - 1000원 단위의 값을 입력했는가? --- src/App.js | 10 ++++++++-- src/constants/constants.js | 13 +++++++++++++ src/utils/InputHandler.js | 8 ++++++++ src/validation/validationCheck.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/constants/constants.js create mode 100644 src/utils/InputHandler.js create mode 100644 src/validation/validationCheck.js diff --git a/src/App.js b/src/App.js index 091aa0a5d..929e048ec 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,11 @@ +import {purchasePriceValidation} from "./validation/validationCheck.js"; +import {InputHandler} from "./utils/InputHandler.js"; +import {INSTRUCTION} from "./constants/constants.js"; + class App { - async run() {} + async run() { + const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceValidation.isValidPurchasePrice); + } } -export default App; +export default App; \ No newline at end of file diff --git a/src/constants/constants.js b/src/constants/constants.js new file mode 100644 index 000000000..bca2a78ee --- /dev/null +++ b/src/constants/constants.js @@ -0,0 +1,13 @@ +export const ERROR_CODE = { + NOT_POSITIVE_NUMBER: "[ERROR] 1이상의 숫자를 입력해주세요.", + OUT_OF_RANGE: (min, max) => `[ERROR] ${min}과 ${max}사이의 값을 입력해주세요.`, + NOT_DIVIDED_BY_VALUE: (value) => `[ERROR] ${value}단위의 금액을 입력해주세요.` +}; + +export const PURCHASE_PRICE = { + MIN_CURR_UNIT: 1000, +} + +export const INSTRUCTION = { + GET_PURCHASE_PRICE: "구입금액을 입력해 주세요.\n", +} \ No newline at end of file diff --git a/src/utils/InputHandler.js b/src/utils/InputHandler.js new file mode 100644 index 000000000..8baa669b3 --- /dev/null +++ b/src/utils/InputHandler.js @@ -0,0 +1,8 @@ +import {Console} from '@woowacourse/mission-utils' + +export const InputHandler = { + async getInput(instruction, validationCheck = (x) => x) { + const input = await Console.readLineAsync(instruction) + return validationCheck(input) + } +} \ No newline at end of file diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js new file mode 100644 index 000000000..3f88a0127 --- /dev/null +++ b/src/validation/validationCheck.js @@ -0,0 +1,30 @@ +import {PURCHASE_PRICE, ERROR_CODE} from "../constants/constants.js"; + +export const validationCheck = { + isPositiveNumber(number) { + if (!/^[1-9]\d*$/.test(number)) { + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) + } + return Number(number); + }, + isInRange(number, minValue = 1, maxValue = Number.MAX_SAFE_INTEGER) { + if (!(Number(number) <= maxValue && Number(number) >= minValue)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(minValue, maxValue)); + } + return Number(number); + }, + isDividedNumberByValue(number, value) { + if (number % value !== 0) { + throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(value)) + } + return Number(number); + } +} + +export const purchasePriceValidation = { + isValidPurchasePrice: (purchasePrice) => { + purchasePrice = validationCheck.isPositiveNumber(purchasePrice) + purchasePrice = validationCheck.isInRange(purchasePrice) + return validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT) + } +} \ No newline at end of file From ef6e5516c6f3cecd65c697c3763d6f9eec0c090b Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sat, 2 Nov 2024 23:12:26 +0900 Subject: [PATCH 03/31] =?UTF-8?q?test=20:=20=EB=A1=9C=EB=98=90=20=EA=B5=AC?= =?UTF-8?q?=EC=9E=85=EA=B8=88=EC=95=A1=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test : 로또 구입금액 단위 테스트 - 로또 구입 금액 validation 체크에 대한 단위테스트 - 숫자(양의 정수)값을 입력했는가? - 최대값 범위 내의 정수를 입력했는가? - 1000원 단위의 값을 입력했는가? --- __tests__/unitTest/validation.test.js | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 __tests__/unitTest/validation.test.js diff --git a/__tests__/unitTest/validation.test.js b/__tests__/unitTest/validation.test.js new file mode 100644 index 000000000..83e7c1a70 --- /dev/null +++ b/__tests__/unitTest/validation.test.js @@ -0,0 +1,40 @@ +import {purchasePriceValidation} from "../../src/validation/validationCheck.js"; +import {ERROR_CODE, PURCHASE_PRICE} from "../../src/constants/constants.js"; + +describe("로또 구입 금액 테스트", () => { + + test("정상 테스트", () => { + const purchasePrice = "1000" + expect(purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toEqual(1000); + }); + + test("정상 테스트", () => { + const purchasePrice = 9007199254740000 + expect(purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toEqual(9007199254740000); + }); + + test("예외 테스트 : 숫자가 아닌 값", () => { + const purchasePrice = "12ab" + expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 0 이하의 값 ", () => { + const purchasePrice = "0" + expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 음수값 ", () => { + const purchasePrice = "-1000" + expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 1000단위가 아닌 금액", () => { + const purchasePrice = "9007199254740991" + expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + }); + + test("예외 테스트 : 범위를 벗어난 값", () => { + const purchasePrice = "9007199254740993" + expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + }); +}); From a3f96a41b00e4e74357a81bfb62d61a0a1833d79 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sat, 2 Nov 2024 23:18:25 +0900 Subject: [PATCH 04/31] =?UTF-8?q?refactor(purchasePrice)=20:=20=EB=A1=9C?= =?UTF-8?q?=EB=98=90=20=EA=B5=AC=EC=9E=85=20=EA=B8=88=EC=95=A1=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=B2=B4=ED=81=AC=20utils=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(purchasePrice) : 로또 구입 금액 유효체크 utils로 변경 - 로또 구입금액 유효체크 purchasePriceUtils 내 구현으로 변경 --- src/App.js | 4 ++-- src/utils/purchasePrice.utils.js | 10 ++++++++++ src/validation/validationCheck.js | 8 -------- 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 src/utils/purchasePrice.utils.js diff --git a/src/App.js b/src/App.js index 929e048ec..ee6a34bd5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,10 @@ -import {purchasePriceValidation} from "./validation/validationCheck.js"; import {InputHandler} from "./utils/InputHandler.js"; import {INSTRUCTION} from "./constants/constants.js"; +import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; class App { async run() { - const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceValidation.isValidPurchasePrice); + const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceUtils.validate); } } diff --git a/src/utils/purchasePrice.utils.js b/src/utils/purchasePrice.utils.js new file mode 100644 index 000000000..0ff41d38f --- /dev/null +++ b/src/utils/purchasePrice.utils.js @@ -0,0 +1,10 @@ +import {PURCHASE_PRICE} from "../constants/constants.js"; +import {validationCheck} from "../validation/validationCheck.js"; + +export const purchasePriceUtils = { + validate(purchasePrice) { + purchasePrice = validationCheck.isPositiveNumber(purchasePrice) + purchasePrice = validationCheck.isInRange(purchasePrice) + return validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT) + } +} \ No newline at end of file diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js index 3f88a0127..db701469b 100644 --- a/src/validation/validationCheck.js +++ b/src/validation/validationCheck.js @@ -20,11 +20,3 @@ export const validationCheck = { return Number(number); } } - -export const purchasePriceValidation = { - isValidPurchasePrice: (purchasePrice) => { - purchasePrice = validationCheck.isPositiveNumber(purchasePrice) - purchasePrice = validationCheck.isInRange(purchasePrice) - return validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT) - } -} \ No newline at end of file From 04675d35a3907a463e2d11077143d36687c08778 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sat, 2 Nov 2024 23:31:01 +0900 Subject: [PATCH 05/31] =?UTF-8?q?feat=20:=20=EA=B5=AC=EB=A7=A4=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=98=90=EC=9D=98=20=EC=B4=9D=20=EA=B0=AF=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EA=B5=AC=ED=95=98=EA=B3=A0,=20=EB=AC=B8=EA=B5=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 구매한 로또의 총 갯수를 구하고, 문구를 출력 - 구매 금액으로 구매한 로또 갯수를 구함 - 구매한 갯수를 출력 --- src/App.js | 3 +++ src/constants/constants.js | 1 + src/utils/purchasePrice.utils.js | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/App.js b/src/App.js index ee6a34bd5..718ba2f68 100644 --- a/src/App.js +++ b/src/App.js @@ -1,10 +1,13 @@ import {InputHandler} from "./utils/InputHandler.js"; import {INSTRUCTION} from "./constants/constants.js"; import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; +import {Console} from '@woowacourse/mission-utils' class App { async run() { const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceUtils.validate); + const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); + Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); } } diff --git a/src/constants/constants.js b/src/constants/constants.js index bca2a78ee..08c92a080 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -10,4 +10,5 @@ export const PURCHASE_PRICE = { export const INSTRUCTION = { GET_PURCHASE_PRICE: "구입금액을 입력해 주세요.\n", + PRINT_LOTTO_AMOUNT: (lottoAmount) => `${lottoAmount}개를 구매했습니다.\n`, } \ No newline at end of file diff --git a/src/utils/purchasePrice.utils.js b/src/utils/purchasePrice.utils.js index 0ff41d38f..b67c2d610 100644 --- a/src/utils/purchasePrice.utils.js +++ b/src/utils/purchasePrice.utils.js @@ -6,5 +6,9 @@ export const purchasePriceUtils = { purchasePrice = validationCheck.isPositiveNumber(purchasePrice) purchasePrice = validationCheck.isInRange(purchasePrice) return validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT) + }, + + getLottoAmount(purchasePrice) { + return purchasePrice / PURCHASE_PRICE.MIN_CURR_UNIT; } } \ No newline at end of file From 4dbf7ecfe778e74e045a36e5d0d0c369918e2e9b Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 14:04:43 +0900 Subject: [PATCH 06/31] =?UTF-8?q?docs(README.md):=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=97=90=EB=9F=AC=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20(=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=83=9D=EC=84=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs(README.md): 구현기능 에러 케이스 추가 (로또 번호 생성) - 로또 번호가 6개가 아닌 경우 - 로또 번호에 중복이 있는 경우 --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e2b5d53ba..8e69e42e0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ### 2. 구매한 로또의 총 갯수를 구하고, 문구를 출력한다. ### 3. 구매한 로또들의 랜덤 번호 생성 - 구매한 로또번호들의 6가지 랜덤 번호를 생성한다 -- 오름차순으로 정렬하여 저장한다 +- 오름차순으로 정렬하여 저장한다 - 구매한 로또 번호를 출력한다. ### 4. 당첨 번호를 입력받는다 ### 5. 보너스 번호를 입력받는다. @@ -35,6 +35,10 @@ - 양의 정수를 입력했는가(/^[1-9]\d*$/) - 최대값 제한 (SAFE INTEGER 범위) +### 3. 구매한 로또들의 랜덤번호 생성 +- 로또 번호의 수가 6개인지 확인 +- 로또 번호에 중복이 없는지 확인 + ### 4. 로또 당첨 번호 입력 - 당첨번호가 6개가 아닌 경우 - 양의 정수를 입력했는가 (/^[1-9]\d*$/) From 025923abe2f6801ddf75443b81f735ad6449d3cf Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 14:05:37 +0900 Subject: [PATCH 07/31] =?UTF-8?q?feat=20:=20=EA=B5=AC=EB=A7=A4=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=98=90=EB=93=A4=EC=9D=98=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 구매한 로또들의 랜덤 번호 생성 - 구매한 로또번호들의 6개 랜덤 번호를 생성 - 로또번호 6개 갯수 확인 - 로또번호 중복여부 확인 - 오름차순으로 정렬 - 구매한 로또 번호를 출력 --- src/App.js | 8 ++++++++ src/Lotto.js | 28 ++++++++++++++++++---------- src/constants/constants.js | 12 ++++++++++-- src/utils/lotto.utils.js | 14 ++++++++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 src/utils/lotto.utils.js diff --git a/src/App.js b/src/App.js index 718ba2f68..36acbfcb4 100644 --- a/src/App.js +++ b/src/App.js @@ -2,12 +2,20 @@ import {InputHandler} from "./utils/InputHandler.js"; import {INSTRUCTION} from "./constants/constants.js"; import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' +import {lottoUtils} from "./utils/lotto.utils.js"; class App { async run() { const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceUtils.validate); const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); + + const lottos = lottoUtils.generateNLottos(lottoAmount); + + lottos.map((lotto) => { + lotto.print() + }) + } } diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e..96c385ef1 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,18 +1,26 @@ +import {Console} from '@woowacourse/mission-utils' +import {ERROR_CODE, LOTTO} from "./constants/constants.js"; + class Lotto { - #numbers; + #numbers; - constructor(numbers) { - this.#validate(numbers); - this.#numbers = numbers; - } + constructor(numbers) { + this.#validate(numbers); + this.#numbers = numbers; + } - #validate(numbers) { - if (numbers.length !== 6) { - throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); + #validate(numbers) { + if (numbers.length !== 6) { + throw new Error(ERROR_CODE.LOTTO_SIZE_OUT_OF_RANGE(LOTTO.SIZE)); + } + if (new Set(numbers).size !== numbers.length) { + throw new Error(ERROR_CODE.LOTTO_NUMBER_DUPLICATE); + } } - } - // TODO: 추가 기능 구현 + print() { + Console.print(this.#numbers); + } } export default Lotto; diff --git a/src/constants/constants.js b/src/constants/constants.js index 08c92a080..f078aa895 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -1,14 +1,22 @@ export const ERROR_CODE = { NOT_POSITIVE_NUMBER: "[ERROR] 1이상의 숫자를 입력해주세요.", OUT_OF_RANGE: (min, max) => `[ERROR] ${min}과 ${max}사이의 값을 입력해주세요.`, - NOT_DIVIDED_BY_VALUE: (value) => `[ERROR] ${value}단위의 금액을 입력해주세요.` + NOT_DIVIDED_BY_VALUE: (value) => `[ERROR] ${value}단위의 금액을 입력해주세요.`, + LOTTO_SIZE_OUT_OF_RANGE: (size) => `[ERROR] 로또 번호는 ${size}개여야 합니다.`, + LOTTO_NUMBER_DUPLICATE: `[ERROR] 로또 번호에 중복이 있습니다.` }; export const PURCHASE_PRICE = { MIN_CURR_UNIT: 1000, } +export const LOTTO = { + SIZE: 6, + MIN_NUMBER: 1, + MAX_NUMBER: 45, +} + export const INSTRUCTION = { GET_PURCHASE_PRICE: "구입금액을 입력해 주세요.\n", - PRINT_LOTTO_AMOUNT: (lottoAmount) => `${lottoAmount}개를 구매했습니다.\n`, + PRINT_LOTTO_AMOUNT: (lottoAmount) => `\n${lottoAmount}개를 구매했습니다.`, } \ No newline at end of file diff --git a/src/utils/lotto.utils.js b/src/utils/lotto.utils.js new file mode 100644 index 000000000..94bf58ff3 --- /dev/null +++ b/src/utils/lotto.utils.js @@ -0,0 +1,14 @@ +import {LOTTO} from "../constants/constants.js"; +import {Random} from '@woowacourse/mission-utils'; +import Lotto from "../Lotto.js"; + +export const lottoUtils = { + generateNLottos(n) { + let lottos = [] + Array(n).fill().map(() => { + const lotto = Random.pickUniqueNumbersInRange(LOTTO.MIN_NUMBER, LOTTO.MAX_NUMBER, LOTTO.SIZE) + lottos.push(new Lotto(lotto.sort((a, b) => a - b))) + }) + return lottos; + } +} \ No newline at end of file From ddee4b87402447bb5185c4afca3fa8ee78662607 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 14:06:34 +0900 Subject: [PATCH 08/31] =?UTF-8?q?test(Lotto)=20:=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test(Lotto) : 로또 클래스 테스트 추가 - 로또 번호가 6개인지 확인 - 로또 번호에 중복이 없는지 확인 - 로또 번호를 오름차순에 맞게 출력하는지 확인 --- __tests__/LottoTest.js | 64 +++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 409aaf69b..d06ad0a62 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,18 +1,54 @@ import Lotto from "../src/Lotto"; +import {MissionUtils} from "@woowacourse/mission-utils"; +import {lottoUtils} from "../src/utils/lotto.utils.js"; +import {ERROR_CODE, LOTTO} from "../src/constants/constants.js"; + +const getLogSpy = () => { + const logSpy = jest.spyOn(MissionUtils.Console, "print"); + logSpy.mockClear(); + return logSpy; +}; + +const mockRandoms = (numbers) => { + MissionUtils.Random.pickUniqueNumbersInRange = jest.fn(); + numbers.reduce((acc, number) => { + return acc.mockReturnValueOnce(number); + }, MissionUtils.Random.pickUniqueNumbersInRange); +}; describe("로또 클래스 테스트", () => { - test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow("[ERROR]"); - }); - - // TODO: 테스트가 통과하도록 프로덕션 코드 구현 - test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => { - expect(() => { - new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow("[ERROR]"); - }); - - // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + test("예외 테스트 : 로또 번호의 개수가 6개가 넘어가는 경우", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 6, 7]); + }).toThrow(ERROR_CODE.LOTTO_SIZE_OUT_OF_RANGE(LOTTO.SIZE)); + }); + + test("예외 테스트 : 로또 번호의 개수가 6개 이하인 경우", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5]); + }).toThrow(ERROR_CODE.LOTTO_SIZE_OUT_OF_RANGE(LOTTO.SIZE)); + }); + + test("예외 테스트 : 로또 번호 중복이 있는 경우", () => { + expect(() => { + new Lotto([1, 2, 3, 4, 5, 5]); + }).toThrow(ERROR_CODE.LOTTO_NUMBER_DUPLICATE); + }); + + test("정상 케이스 : 로또 오름차순 정렬 후 출력", () => { + const logSpy = getLogSpy(); + + const RANDOM_NUMBERS_TO_END = [7, 1, 43, 24, 35, 6]; + mockRandoms([RANDOM_NUMBERS_TO_END]); + + const log = [1, 6, 7, 24, 35, 43] + + const lottos = lottoUtils.generateNLottos(1) + lottos.forEach(lotto => { + lotto.print() + expect(logSpy).toHaveBeenCalledWith(log); + }) + + }) + }); From 9733435094cded3a470779ba02605c89b67d904f Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 14:50:46 +0900 Subject: [PATCH 09/31] =?UTF-8?q?refactor=20:=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20validationCh?= =?UTF-8?q?eck=20=EA=B0=9D=EC=B2=B4=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : 로또 유효성 검사 validationCheck 객체 사용 - 로또 유효성 검사 validationCheck객체 사용으로 변경 - ERROR_CODE이름 변경 --- __tests__/LottoTest.js | 6 +++--- src/Lotto.js | 11 ++++++----- src/constants/constants.js | 4 ++-- src/validation/validationCheck.js | 6 ++++++ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index d06ad0a62..bb88ec9fe 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -20,19 +20,19 @@ describe("로또 클래스 테스트", () => { test("예외 테스트 : 로또 번호의 개수가 6개가 넘어가는 경우", () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow(ERROR_CODE.LOTTO_SIZE_OUT_OF_RANGE(LOTTO.SIZE)); + }).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(LOTTO.SIZE)); }); test("예외 테스트 : 로또 번호의 개수가 6개 이하인 경우", () => { expect(() => { new Lotto([1, 2, 3, 4, 5]); - }).toThrow(ERROR_CODE.LOTTO_SIZE_OUT_OF_RANGE(LOTTO.SIZE)); + }).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(LOTTO.SIZE)); }); test("예외 테스트 : 로또 번호 중복이 있는 경우", () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow(ERROR_CODE.LOTTO_NUMBER_DUPLICATE); + }).toThrow(ERROR_CODE.NUMBER_DUPLICATE); }); test("정상 케이스 : 로또 오름차순 정렬 후 출력", () => { diff --git a/src/Lotto.js b/src/Lotto.js index 96c385ef1..f85757677 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,5 +1,6 @@ import {Console} from '@woowacourse/mission-utils' -import {ERROR_CODE, LOTTO} from "./constants/constants.js"; +import {ERROR_CODE, LOTTO, WINNING_NUMBER} from "./constants/constants.js"; +import {validationCheck} from "./validation/validationCheck.js"; class Lotto { #numbers; @@ -10,11 +11,11 @@ class Lotto { } #validate(numbers) { - if (numbers.length !== 6) { - throw new Error(ERROR_CODE.LOTTO_SIZE_OUT_OF_RANGE(LOTTO.SIZE)); + if (!validationCheck.isCorrectSize(numbers, LOTTO.SIZE)) { + throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(LOTTO.SIZE)); } - if (new Set(numbers).size !== numbers.length) { - throw new Error(ERROR_CODE.LOTTO_NUMBER_DUPLICATE); + if (validationCheck.hasDuplicates(numbers)) { + throw new Error(ERROR_CODE.NUMBER_DUPLICATE) } } diff --git a/src/constants/constants.js b/src/constants/constants.js index f078aa895..3dfeae6b7 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -2,8 +2,8 @@ export const ERROR_CODE = { NOT_POSITIVE_NUMBER: "[ERROR] 1이상의 숫자를 입력해주세요.", OUT_OF_RANGE: (min, max) => `[ERROR] ${min}과 ${max}사이의 값을 입력해주세요.`, NOT_DIVIDED_BY_VALUE: (value) => `[ERROR] ${value}단위의 금액을 입력해주세요.`, - LOTTO_SIZE_OUT_OF_RANGE: (size) => `[ERROR] 로또 번호는 ${size}개여야 합니다.`, - LOTTO_NUMBER_DUPLICATE: `[ERROR] 로또 번호에 중복이 있습니다.` + SIZE_OUT_OF_RANGE: (size) => `[ERROR] 번호는 ${size}개여야 합니다.`, + NUMBER_DUPLICATE: `[ERROR] 번호에 중복이 있습니다.` }; export const PURCHASE_PRICE = { diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js index db701469b..58407edbd 100644 --- a/src/validation/validationCheck.js +++ b/src/validation/validationCheck.js @@ -18,5 +18,11 @@ export const validationCheck = { throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(value)) } return Number(number); + }, + hasDuplicates(numbers) { + return new Set(numbers).size !== numbers.length; + }, + isCorrectSize(number, size) { + return number.length === size; } } From 05eeb53bc227ad9abd325ba5a060cae635cc7738 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 14:56:59 +0900 Subject: [PATCH 10/31] =?UTF-8?q?refactor=20:=20purchasePrice=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Error발생은 validate함수에서 발생시킴 - validationCheck는 boolean형 리턴으로 변경(함수명을 더 직관적으로 표현) --- src/utils/purchasePrice.utils.js | 13 +++++++++---- src/validation/validationCheck.js | 15 +++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/utils/purchasePrice.utils.js b/src/utils/purchasePrice.utils.js index b67c2d610..069317240 100644 --- a/src/utils/purchasePrice.utils.js +++ b/src/utils/purchasePrice.utils.js @@ -1,11 +1,16 @@ -import {PURCHASE_PRICE} from "../constants/constants.js"; +import {ERROR_CODE, PURCHASE_PRICE} from "../constants/constants.js"; import {validationCheck} from "../validation/validationCheck.js"; export const purchasePriceUtils = { validate(purchasePrice) { - purchasePrice = validationCheck.isPositiveNumber(purchasePrice) - purchasePrice = validationCheck.isInRange(purchasePrice) - return validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT) + if (!validationCheck.isPositiveNumber(purchasePrice)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); + if (!validationCheck.isInRange(purchasePrice)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + } + if (!validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT)) + throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + return Number(purchasePrice); }, getLottoAmount(purchasePrice) { diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js index 58407edbd..87e844abe 100644 --- a/src/validation/validationCheck.js +++ b/src/validation/validationCheck.js @@ -2,22 +2,13 @@ import {PURCHASE_PRICE, ERROR_CODE} from "../constants/constants.js"; export const validationCheck = { isPositiveNumber(number) { - if (!/^[1-9]\d*$/.test(number)) { - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) - } - return Number(number); + return /^[1-9]\d*$/.test(number) }, isInRange(number, minValue = 1, maxValue = Number.MAX_SAFE_INTEGER) { - if (!(Number(number) <= maxValue && Number(number) >= minValue)) { - throw new Error(ERROR_CODE.OUT_OF_RANGE(minValue, maxValue)); - } - return Number(number); + return Number(number) <= maxValue && Number(number) >= minValue }, isDividedNumberByValue(number, value) { - if (number % value !== 0) { - throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(value)) - } - return Number(number); + return Number(number) % Number(value) === 0 }, hasDuplicates(numbers) { return new Set(numbers).size !== numbers.length; From 909879dbd758e031fe3376fc6f44c4ea9a598a00 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 15:08:37 +0900 Subject: [PATCH 11/31] =?UTF-8?q?feat=20:=20=EB=A1=9C=EB=98=90=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 로또 당첨 번호를 입력 및 유효성 검증 - 양의 정수(문자, 음수 등 금지)체크 - 1~45 까지의 정수 입력 체크 - 번호 갯수 6개 체크 - 중복값 여부 --- src/App.js | 5 ++++- src/constants/constants.js | 7 +++++++ src/utils/InputHandler.js | 6 +++--- src/utils/winningNumbers.utils.js | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/utils/winningNumbers.utils.js diff --git a/src/App.js b/src/App.js index 36acbfcb4..104a2a0e1 100644 --- a/src/App.js +++ b/src/App.js @@ -3,6 +3,7 @@ import {INSTRUCTION} from "./constants/constants.js"; import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' import {lottoUtils} from "./utils/lotto.utils.js"; +import {winningNumbersUtils} from "./utils/winningNumbers.utils.js"; class App { async run() { @@ -11,11 +12,13 @@ class App { Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); const lottos = lottoUtils.generateNLottos(lottoAmount); - lottos.map((lotto) => { lotto.print() }) + const winningNumbers = await InputHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, + winningNumbersUtils.validate, + (str) => str.split(',')); } } diff --git a/src/constants/constants.js b/src/constants/constants.js index 3dfeae6b7..3c79b20cd 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -16,7 +16,14 @@ export const LOTTO = { MAX_NUMBER: 45, } +export const WINNING_NUMBER = { + SIZE: 6, + MIN_NUMBER: 1, + MAX_NUMBER: 45, +} + export const INSTRUCTION = { GET_PURCHASE_PRICE: "구입금액을 입력해 주세요.\n", PRINT_LOTTO_AMOUNT: (lottoAmount) => `\n${lottoAmount}개를 구매했습니다.`, + GET_WINNING_NUMBERS: "당첨 번호를 입력해 주세요.\n", } \ No newline at end of file diff --git a/src/utils/InputHandler.js b/src/utils/InputHandler.js index 8baa669b3..99b629759 100644 --- a/src/utils/InputHandler.js +++ b/src/utils/InputHandler.js @@ -1,8 +1,8 @@ import {Console} from '@woowacourse/mission-utils' export const InputHandler = { - async getInput(instruction, validationCheck = (x) => x) { + async getInput(instruction, validationCheck = (x) => x, process = (x) => x) { const input = await Console.readLineAsync(instruction) - return validationCheck(input) - } + return validationCheck(process(input)) + }, } \ No newline at end of file diff --git a/src/utils/winningNumbers.utils.js b/src/utils/winningNumbers.utils.js new file mode 100644 index 000000000..a66d8494f --- /dev/null +++ b/src/utils/winningNumbers.utils.js @@ -0,0 +1,19 @@ +import {validationCheck} from "../validation/validationCheck.js"; +import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../constants/constants.js"; + +export const winningNumbersUtils = { + validate(winningNumbers) { + winningNumbers.forEach((number) => { + if (!validationCheck.isPositiveNumber(number)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) + if (!validationCheck.isInRange(number, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) + throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + }) + if (!validationCheck.isCorrectSize(winningNumbers, WINNING_NUMBER.SIZE)) + throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + if (validationCheck.hasDuplicates(winningNumbers)) { + throw new Error(ERROR_CODE.NUMBER_DUPLICATE) + } + return winningNumbers; + }, +} \ No newline at end of file From 438b1a3b6cd8c7805b3d30cd4abcd535e56a787f Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 15:09:46 +0900 Subject: [PATCH 12/31] =?UTF-8?q?test(winningNumber)=20:=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EB=B2=88=ED=98=B8=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test(winningNumber) : 당첨 번호 유효성 테스트 양의 정수(문자, 음수 등 금지)체크 - 1~45 까지의 정수 입력 체크 - 번호 갯수 6개 체크 - 중복값 여부 --- __tests__/unitTest/validation.test.js | 64 +++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/__tests__/unitTest/validation.test.js b/__tests__/unitTest/validation.test.js index 83e7c1a70..95df8f9bc 100644 --- a/__tests__/unitTest/validation.test.js +++ b/__tests__/unitTest/validation.test.js @@ -1,40 +1,86 @@ -import {purchasePriceValidation} from "../../src/validation/validationCheck.js"; -import {ERROR_CODE, PURCHASE_PRICE} from "../../src/constants/constants.js"; +import {purchasePriceUtils} from "../../src/utils/purchasePrice.utils.js"; +import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../../src/constants/constants.js"; +import {winningNumbersUtils} from "../../src/utils/winningNumbers.utils.js"; describe("로또 구입 금액 테스트", () => { test("정상 테스트", () => { const purchasePrice = "1000" - expect(purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toEqual(1000); + expect(purchasePriceUtils.validate(purchasePrice)).toEqual(1000); }); test("정상 테스트", () => { const purchasePrice = 9007199254740000 - expect(purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toEqual(9007199254740000); + expect(purchasePriceUtils.validate(purchasePrice)).toEqual(9007199254740000); }); test("예외 테스트 : 숫자가 아닌 값", () => { const purchasePrice = "12ab" - expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 0 이하의 값 ", () => { const purchasePrice = "0" - expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 음수값 ", () => { const purchasePrice = "-1000" - expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 1000단위가 아닌 금액", () => { const purchasePrice = "9007199254740991" - expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); }); test("예외 테스트 : 범위를 벗어난 값", () => { const purchasePrice = "9007199254740993" - expect(() => purchasePriceValidation.isValidPurchasePrice(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + }); +}); + + +describe.only("당첨 번호 유효성 테스트", () => { + + test("정상 테스트", () => { + const winningNumber = [1, 2, 3, 4, 5, 6] + expect(winningNumbersUtils.validate(winningNumber)).toEqual(winningNumber); + }); + + test("정상 테스트", () => { + const winningNumber = [3, 12, 14, 35, 41, 45] + expect(winningNumbersUtils.validate(winningNumber)).toEqual(winningNumber); + }); + + + test("예외 테스트 : 양의 정수가 아닌 값 포함", () => { + const winningNumber = [3, -12, 14, 35, 0, 42] + expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 양의 정수가 아닌 값 포함", () => { + const winningNumber = ["1ab", 12, 14, 35, 41, 42] + expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 유효범위(1~45) 이상의 값이 포함된 경우", () => { + const winningNumber = [1, 12, 14, 35, 41, 46] + expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + }); + + test("예외 테스트 : 당첨 번호 값이 6개 미만 경우", () => { + const winningNumber = [1, 12, 14, 35, 41] + expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + }); + + test("예외 테스트 : 당첨 번호 값이 7개 이상 경우", () => { + const winningNumber = [1, 12, 14, 35, 41, 42, 43] + expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + }); + + test("예외 테스트 : 당첨 번호에 중복값이 존재하는 경우", () => { + const winningNumber = [1, 12, 12, 35, 41, 42] + expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NUMBER_DUPLICATE); }); }); From 04792a890fbce35f8f77f1aa42e9ffba2a4c2964 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 15:51:04 +0900 Subject: [PATCH 13/31] =?UTF-8?q?feat=20:=20=EB=B3=B4=EB=84=88=EC=8A=A4=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 보너스 번호를 입력 - 보너스 번호 입력 - 보너스 번호 유효성 검사 - 양의 정수가 아닌 값 여부 - 1~45사이 범위에 있는지 여부 - 당첨번호와의 중복여부 --- src/App.js | 5 +++++ src/constants/constants.js | 4 +++- src/utils/bonusNumber.utils.js | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/utils/bonusNumber.utils.js diff --git a/src/App.js b/src/App.js index 104a2a0e1..1ba7fac16 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' import {lottoUtils} from "./utils/lotto.utils.js"; import {winningNumbersUtils} from "./utils/winningNumbers.utils.js"; +import {bonusNumberUtils} from "./utils/bonusNumber.utils.js"; class App { async run() { @@ -19,6 +20,10 @@ class App { const winningNumbers = await InputHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, winningNumbersUtils.validate, (str) => str.split(',')); + + const bonusNumber = await InputHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, + bonusNumberUtils.validate); + bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumbers); } } diff --git a/src/constants/constants.js b/src/constants/constants.js index 3c79b20cd..752568231 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -3,7 +3,8 @@ export const ERROR_CODE = { OUT_OF_RANGE: (min, max) => `[ERROR] ${min}과 ${max}사이의 값을 입력해주세요.`, NOT_DIVIDED_BY_VALUE: (value) => `[ERROR] ${value}단위의 금액을 입력해주세요.`, SIZE_OUT_OF_RANGE: (size) => `[ERROR] 번호는 ${size}개여야 합니다.`, - NUMBER_DUPLICATE: `[ERROR] 번호에 중복이 있습니다.` + NUMBER_DUPLICATE: `[ERROR] 번호에 중복이 있습니다.`, + BONUS_NUMBER_DUPLICATE: '[ERROR] 보너스 번호가 당첨번호와 중복됩니다.' }; export const PURCHASE_PRICE = { @@ -26,4 +27,5 @@ export const INSTRUCTION = { GET_PURCHASE_PRICE: "구입금액을 입력해 주세요.\n", PRINT_LOTTO_AMOUNT: (lottoAmount) => `\n${lottoAmount}개를 구매했습니다.`, GET_WINNING_NUMBERS: "당첨 번호를 입력해 주세요.\n", + GET_BONUS_NUMBER: "보너스 번호를 입력해 주세요.\n" } \ No newline at end of file diff --git a/src/utils/bonusNumber.utils.js b/src/utils/bonusNumber.utils.js new file mode 100644 index 000000000..6819ea619 --- /dev/null +++ b/src/utils/bonusNumber.utils.js @@ -0,0 +1,17 @@ +import {validationCheck} from "../validation/validationCheck.js"; +import {ERROR_CODE, WINNING_NUMBER} from "../constants/constants.js"; + +export const bonusNumberUtils = { + validate(bonusNumber) { + if (!validationCheck.isPositiveNumber(bonusNumber)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); + if (!validationCheck.isInRange(bonusNumber, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + } + return Number(bonusNumber); + }, + validateWithWinningNumbers(bonusNumber, winningNumber) { + if (validationCheck.hasDuplicates([...winningNumber, bonusNumber])) + throw new Error(ERROR_CODE.BONUS_NUMBER_DUPLICATE) + } +} \ No newline at end of file From c9fbb8b53eda9caa5343c9a7b596dab495abcede Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 15:51:41 +0900 Subject: [PATCH 14/31] =?UTF-8?q?test(bonusNumber)=20:=20=EB=B3=B4?= =?UTF-8?q?=EB=84=88=EC=8A=A4=EB=B2=88=ED=98=B8=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test(bonusNumber) : 보너스번호 유효성 검사 - 양의 정수가 아닌 값 여부 - 1~45사이 범위에 있는지 여부 - 당첨번호와의 중복여부 --- __tests__/unitTest/validation.test.js | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/__tests__/unitTest/validation.test.js b/__tests__/unitTest/validation.test.js index 95df8f9bc..f49ddd162 100644 --- a/__tests__/unitTest/validation.test.js +++ b/__tests__/unitTest/validation.test.js @@ -1,6 +1,7 @@ import {purchasePriceUtils} from "../../src/utils/purchasePrice.utils.js"; import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../../src/constants/constants.js"; import {winningNumbersUtils} from "../../src/utils/winningNumbers.utils.js"; +import {bonusNumberUtils} from "../../src/utils/bonusNumber.utils.js"; describe("로또 구입 금액 테스트", () => { @@ -41,7 +42,7 @@ describe("로또 구입 금액 테스트", () => { }); -describe.only("당첨 번호 유효성 테스트", () => { +describe("당첨 번호 유효성 테스트", () => { test("정상 테스트", () => { const winningNumber = [1, 2, 3, 4, 5, 6] @@ -84,3 +85,33 @@ describe.only("당첨 번호 유효성 테스트", () => { expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NUMBER_DUPLICATE); }); }); + +describe.only("보너스 번호 유효성 테스트", () => { + + test("정상 테스트", () => { + const bonusNumber = 43 + expect(bonusNumberUtils.validate(bonusNumber)).toEqual(bonusNumber); + }); + + test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { + const bonusNumber = "0" + expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { + const bonusNumber = "abc" + expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + }); + + test("예외 테스트 : 보너스 번호가 범위를 넘어간 경우(1~45)", () => { + const bonusNumber = 46 + expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + }); + + test("예외 테스트 : 당첨 번호에 중복값이 존재하는 경우", () => { + const winningNumber = [1, 12, 14, 35, 41, 42] + const bonusNumber = 1 + expect(() => bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumber)).toThrow(ERROR_CODE.BONUS_NUMBER_DUPLICATE); + }); + +}); From f00ee198133acb66208779aaaa1fd88424b12ebb Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 22:20:35 +0900 Subject: [PATCH 15/31] =?UTF-8?q?fixed=20:=20=EB=8B=B9=EC=B2=A8=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20number=20array=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed : 당첨번호 number array로 변경 - 당첨 번호 string array to number array --- src/utils/winningNumbers.utils.js | 2 +- src/validation/validationCheck.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/winningNumbers.utils.js b/src/utils/winningNumbers.utils.js index a66d8494f..e85a21fb8 100644 --- a/src/utils/winningNumbers.utils.js +++ b/src/utils/winningNumbers.utils.js @@ -14,6 +14,6 @@ export const winningNumbersUtils = { if (validationCheck.hasDuplicates(winningNumbers)) { throw new Error(ERROR_CODE.NUMBER_DUPLICATE) } - return winningNumbers; + return winningNumbers.map(Number); }, } \ No newline at end of file diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js index 87e844abe..e78e9bee8 100644 --- a/src/validation/validationCheck.js +++ b/src/validation/validationCheck.js @@ -2,7 +2,7 @@ import {PURCHASE_PRICE, ERROR_CODE} from "../constants/constants.js"; export const validationCheck = { isPositiveNumber(number) { - return /^[1-9]\d*$/.test(number) + return /^[1-9]\d*$/.test(number.trim()) }, isInRange(number, minValue = 1, maxValue = Number.MAX_SAFE_INTEGER) { return Number(number) <= maxValue && Number(number) >= minValue From 6c60e7714ed1dd96e0f767dc98729a36ce960c51 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 23:04:18 +0900 Subject: [PATCH 16/31] =?UTF-8?q?feat=20:=20=EB=8B=B9=EC=B2=A8=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=EA=B5=AC=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 당첨 통계구하기 - index가 당첨 번호 수를 의미하는 배열을 생성 - 배열에 저장된 값은 해당 갯수로 당첨된 로또의 수 --- src/App.js | 3 +++ src/Lotto.js | 21 +++++++++++++++++++++ src/utils/lotto.utils.js | 13 +++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index 1ba7fac16..a0f2dc82f 100644 --- a/src/App.js +++ b/src/App.js @@ -24,6 +24,9 @@ class App { const bonusNumber = await InputHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, bonusNumberUtils.validate); bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumbers); + + const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); + } } diff --git a/src/Lotto.js b/src/Lotto.js index f85757677..3c9df247f 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -10,6 +10,10 @@ class Lotto { this.#numbers = numbers; } + getNumbers() { + return this.#numbers; + } + #validate(numbers) { if (!validationCheck.isCorrectSize(numbers, LOTTO.SIZE)) { throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(LOTTO.SIZE)); @@ -22,6 +26,23 @@ class Lotto { print() { Console.print(this.#numbers); } + + countLottoMatches(winningNumbers) { + return this.#numbers.filter(num => winningNumbers.includes(num)).length + } + + isBonusNumberMatch(bonusNumber) { + return this.#numbers.includes(bonusNumber); + } + + getLottoResult(winningNumbers, bonusNumber) { + const matchNumber = this.countLottoMatches(winningNumbers); + if (matchNumber === 5 && this.isBonusNumberMatch(bonusNumber) || matchNumber === 6) { + return matchNumber + 1; + } + return matchNumber; + } + } export default Lotto; diff --git a/src/utils/lotto.utils.js b/src/utils/lotto.utils.js index 94bf58ff3..c92b9284a 100644 --- a/src/utils/lotto.utils.js +++ b/src/utils/lotto.utils.js @@ -1,5 +1,5 @@ -import {LOTTO} from "../constants/constants.js"; -import {Random} from '@woowacourse/mission-utils'; +import {INSTRUCTION, LOTTO} from "../constants/constants.js"; +import {Random, Console} from '@woowacourse/mission-utils'; import Lotto from "../Lotto.js"; export const lottoUtils = { @@ -10,5 +10,14 @@ export const lottoUtils = { lottos.push(new Lotto(lotto.sort((a, b) => a - b))) }) return lottos; + }, + getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) { + let lottoResult = Array(8).fill(0); + lottos.forEach((lotto) => { + const matchNumber = lotto.getLottoResult(winningNumbers, bonusNumber); + lottoResult[matchNumber]++ + }) + return lottoResult; } + } \ No newline at end of file From 3154fa3262abdbb79529ca30005d37bffd6f4edb Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Sun, 3 Nov 2024 23:05:23 +0900 Subject: [PATCH 17/31] =?UTF-8?q?test=20:=20=EB=8B=B9=EC=B2=A8=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test : 당첨 통계 테스트 - index가 당첨 번호 수를 의미하는 배열을 생성 - 배열에 저장된 값은 해당 갯수로 당첨된 로또의 수 해당 배열을 제대로 생성해내는지 테스트 --- __tests__/LottoTest.js | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index bb88ec9fe..cbd3e880f 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -52,3 +52,106 @@ describe("로또 클래스 테스트", () => { }) }); + + +describe.only("당첨 케이스 테스트", () => { + test("1등 당첨 케이스", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 38]) + ] + const winningNumbers = [1, 3, 5, 8, 11, 38] + const bonusNumber = 39 + + const lottoResult = [0, 0, 0, 0, 0, 0, 0, 1] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + + test("2등 당첨 케이스", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 39]) + ] + const winningNumbers = [1, 3, 5, 8, 11, 38] + const bonusNumber = 39 + + const lottoResult = [0, 0, 0, 0, 0, 0, 1, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + + test("3등 당첨 케이스", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 39]) + ] + const winningNumbers = [1, 3, 5, 8, 11, 38] + const bonusNumber = 40 + + const lottoResult = [0, 0, 0, 0, 0, 1, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + + test("4등 당첨 케이스", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 39]) + ] + const winningNumbers = [1, 3, 5, 8, 14, 38] + const bonusNumber = 40 + + const lottoResult = [0, 0, 0, 0, 1, 0, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + + test("5등 당첨 케이스", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 39]) + ] + const winningNumbers = [1, 3, 5, 10, 14, 38] + const bonusNumber = 40 + + const lottoResult = [0, 0, 0, 1, 0, 0, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + + test("0개 일치 케이스", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 39]) + ] + const winningNumbers = [2, 4, 6, 10, 14, 38] + const bonusNumber = 40 + + const lottoResult = [1, 0, 0, 0, 0, 0, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + + test("2개 이상 로또 ", () => { + const lottos = [ + new Lotto([1, 3, 5, 8, 11, 40]), + new Lotto([1, 2, 6, 8, 11, 39]) + ] + const winningNumbers = [1, 3, 5, 8, 11, 39] + const bonusNumber = 40 + + const lottoResult = [0, 0, 0, 0, 1, 0, 1, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) + + expect( + lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + ).toStrictEqual(lottoResult); + }); + +}); \ No newline at end of file From e37654e8fcde2f04de48633f8844a7aac986cfd9 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 00:30:21 +0900 Subject: [PATCH 18/31] =?UTF-8?q?feat=20:=20=EC=88=98=EC=9D=B5=EB=A5=A0=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat : 수익률 계산 - 당첨금의 합을 구함 - 수익률 계산 - 소수점 둘째 자리에서 반올림 --- src/App.js | 40 +++++++++++++++++++++++++------------- src/constants/constants.js | 14 +++++++++++-- src/utils/lotto.utils.js | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/App.js b/src/App.js index a0f2dc82f..7ba1219fe 100644 --- a/src/App.js +++ b/src/App.js @@ -8,25 +8,37 @@ import {bonusNumberUtils} from "./utils/bonusNumber.utils.js"; class App { async run() { - const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceUtils.validate); - const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); - Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); + try { + const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceUtils.validate); + const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); + Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); - const lottos = lottoUtils.generateNLottos(lottoAmount); - lottos.map((lotto) => { - lotto.print() - }) + const lottos = lottoUtils.generateNLottos(lottoAmount); + lottos.map((lotto) => { + lotto.print() + }) - const winningNumbers = await InputHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, - winningNumbersUtils.validate, - (str) => str.split(',')); + const winningNumbers = await InputHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, + winningNumbersUtils.validate, + (str) => str.split(',')); - const bonusNumber = await InputHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, - bonusNumberUtils.validate); - bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumbers); + const bonusNumber = await InputHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, + bonusNumberUtils.validate); + bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumbers); - const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); + const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); + let totalPrize = 0; + Console.print(INSTRUCTION.PRINT_TOTAL_WINNING_STATISTICS); + lottoResult.forEach((amount, index) => { + lottoUtils.printWinningStatistics(index, lottoUtils.getPrize(index), amount) + totalPrize += amount * lottoUtils.getPrize(index); + }) + const profitRate = lottoUtils.calculateProfitRate(totalPrize, purchasePrice); + Console.print(INSTRUCTION.PRINT_PROFIT_RATE(profitRate)) + } catch (error) { + Console.print(error.message) + } } } diff --git a/src/constants/constants.js b/src/constants/constants.js index 752568231..2e45480d5 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -15,8 +15,14 @@ export const LOTTO = { SIZE: 6, MIN_NUMBER: 1, MAX_NUMBER: 45, + FIRST_PRIZE: 2000000000, + SECOND_PRIZE: 30000000, + THIRD_PRIZE: 1500000, + FOURTH_PRIZE: 50000, + FIFTH_PRIZE: 5000, } + export const WINNING_NUMBER = { SIZE: 6, MIN_NUMBER: 1, @@ -25,7 +31,11 @@ export const WINNING_NUMBER = { export const INSTRUCTION = { GET_PURCHASE_PRICE: "구입금액을 입력해 주세요.\n", - PRINT_LOTTO_AMOUNT: (lottoAmount) => `\n${lottoAmount}개를 구매했습니다.`, + PRINT_LOTTO_AMOUNT: (lottoAmount) => `${lottoAmount}개를 구매했습니다.`, GET_WINNING_NUMBERS: "당첨 번호를 입력해 주세요.\n", - GET_BONUS_NUMBER: "보너스 번호를 입력해 주세요.\n" + GET_BONUS_NUMBER: "\n보너스 번호를 입력해 주세요.\n", + EXTRA_MESSAGE_SECOND_PRIZE: ", 보너스 볼 일치", + PRINT_WINNING_STATISTICS: (matchNumber, prize, matchAmount, extraMessage = "") => `${matchNumber}개 일치${extraMessage} (${prize}원) - ${matchAmount}개`, + PRINT_TOTAL_WINNING_STATISTICS: "\n당첨 통계\n---", + PRINT_PROFIT_RATE: (profitRate) => `총 수익률은 ${profitRate}%입니다.` } \ No newline at end of file diff --git a/src/utils/lotto.utils.js b/src/utils/lotto.utils.js index c92b9284a..1aaf615b8 100644 --- a/src/utils/lotto.utils.js +++ b/src/utils/lotto.utils.js @@ -18,6 +18,43 @@ export const lottoUtils = { lottoResult[matchNumber]++ }) return lottoResult; + }, + getPrize(matchNumber) { + switch (matchNumber) { + case 7: + return LOTTO.FIRST_PRIZE + case 6: + return LOTTO.SECOND_PRIZE + case 5: + return LOTTO.THIRD_PRIZE + case 4: + return LOTTO.FOURTH_PRIZE + case 3: + return LOTTO.FIFTH_PRIZE + default: + return 0 + } + }, + printWinningStatistics(matchNumber, prize, matchAmount) { + if (matchNumber < 3) return + if (matchNumber === 6) { + Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, this.makeMoneyFormat(prize), matchAmount, INSTRUCTION.EXTRA_MESSAGE_SECOND_PRIZE)) + return + } + if (matchNumber === 7) { + Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, this.makeMoneyFormat(prize), matchAmount)) + return + } + Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber, this.makeMoneyFormat(prize), matchAmount)) + }, + makeMoneyFormat(money, separator = ",") { + return money.toString().split("").reverse().join("") + .replace(/(.{3})(?=.)/g, `$1${separator}`) + .split("").reverse().join(""); + }, + calculateProfitRate(profit, purchasePrice) { + const profitRate = profit / purchasePrice * 100; + return Math.round(profitRate * 100) / 100; } } \ No newline at end of file From ae65245cb74b7a5a0d2bfd9ec6e92eb3b6ae6103 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 00:30:46 +0900 Subject: [PATCH 19/31] =?UTF-8?q?test=20:=20=EC=88=98=EC=9D=B5=EB=A5=A0=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test : 수익률 계산 테스트 --- __tests__/ApplicationTest.js | 146 ++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 72 deletions(-) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 872380c9c..c0b8277a0 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -1,97 +1,99 @@ import App from "../src/App.js"; -import { MissionUtils } from "@woowacourse/mission-utils"; +import {MissionUtils} from "@woowacourse/mission-utils"; +import {ERROR_CODE} from "../src/constants/constants.js"; -const mockQuestions = (inputs) => { - MissionUtils.Console.readLineAsync = jest.fn(); +export const mockQuestions = (inputs) => { + MissionUtils.Console.readLineAsync = jest.fn(); - MissionUtils.Console.readLineAsync.mockImplementation(() => { - const input = inputs.shift(); + MissionUtils.Console.readLineAsync.mockImplementation(() => { + const input = inputs.shift(); - return Promise.resolve(input); - }); + return Promise.resolve(input); + }); }; const mockRandoms = (numbers) => { - MissionUtils.Random.pickUniqueNumbersInRange = jest.fn(); - numbers.reduce((acc, number) => { - return acc.mockReturnValueOnce(number); - }, MissionUtils.Random.pickUniqueNumbersInRange); + MissionUtils.Random.pickUniqueNumbersInRange = jest.fn(); + numbers.reduce((acc, number) => { + return acc.mockReturnValueOnce(number); + }, MissionUtils.Random.pickUniqueNumbersInRange); }; const getLogSpy = () => { - const logSpy = jest.spyOn(MissionUtils.Console, "print"); - logSpy.mockClear(); - return logSpy; + const logSpy = jest.spyOn(MissionUtils.Console, "print"); + logSpy.mockClear(); + return logSpy; }; const runException = async (input) => { - // given - const logSpy = getLogSpy(); - - const RANDOM_NUMBERS_TO_END = [1, 2, 3, 4, 5, 6]; - const INPUT_NUMBERS_TO_END = ["1000", "1,2,3,4,5,6", "7"]; - - mockRandoms([RANDOM_NUMBERS_TO_END]); - mockQuestions([input, ...INPUT_NUMBERS_TO_END]); - - // when - const app = new App(); - await app.run(); - - // then - expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("[ERROR]")); -}; - -describe("로또 테스트", () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - test("기능 테스트", async () => { // given const logSpy = getLogSpy(); - mockRandoms([ - [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], - ]); - mockQuestions(["8000", "1,2,3,4,5,6", "7"]); + const RANDOM_NUMBERS_TO_END = [1, 2, 3, 4, 5, 6]; + const INPUT_NUMBERS_TO_END = ["1000", "1,2,3,4,5,6", "7"]; + + mockRandoms([RANDOM_NUMBERS_TO_END]); + mockQuestions([input, ...INPUT_NUMBERS_TO_END]); // when const app = new App(); await app.run(); // then - const logs = [ - "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개", - "총 수익률은 62.5%입니다.", - ]; - - logs.forEach((log) => { - expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log)); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("[ERROR]")); +}; + +describe.only("로또 테스트", () => { + beforeEach(() => { + jest.restoreAllMocks(); }); - }); - test("예외 테스트", async () => { - await runException("1000j"); - }); + test("기능 테스트", async () => { + // given + const logSpy = getLogSpy(); + + mockRandoms([ + [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], + ]); + mockQuestions(["8000", "1,2,3,4,5,6", "7"]); + + // when + const app = new App(); + await app.run(); + + // then + const logs = [ + "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개", + "총 수익률은 62.5%입니다.", + ]; + + logs.forEach((log) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log)); + }); + }); + + + test("예외 테스트", async () => { + await runException("1000j"); + }); }); From 5911aba53683c3c7f99c0264a3a5fe4c4e325255 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 00:31:39 +0900 Subject: [PATCH 20/31] =?UTF-8?q?fixed=20:=20=EC=96=91=EC=9D=98=20?= =?UTF-8?q?=EC=A0=95=EC=88=98=20=EC=B2=B4=ED=81=AC=EC=8B=9C=20=EA=B3=B5?= =?UTF-8?q?=EB=B0=B1=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed : 양의 정수 체크시 공백 오류 해결 - 양의 정수 체크시 공백 오류 문제 해결을 위해 trim추가 --- src/validation/validationCheck.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js index e78e9bee8..466eb47b7 100644 --- a/src/validation/validationCheck.js +++ b/src/validation/validationCheck.js @@ -1,8 +1,6 @@ -import {PURCHASE_PRICE, ERROR_CODE} from "../constants/constants.js"; - export const validationCheck = { isPositiveNumber(number) { - return /^[1-9]\d*$/.test(number.trim()) + return /^[1-9]\d*$/.test(number.toString().trim()) }, isInRange(number, minValue = 1, maxValue = Number.MAX_SAFE_INTEGER) { return Number(number) <= maxValue && Number(number) >= minValue From c7186f7a604b032cd66b1f8a25b6c5d2c1b2319b Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 00:32:16 +0900 Subject: [PATCH 21/31] =?UTF-8?q?fixed=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=98=95=EC=8B=9D=EC=9D=84=20=EC=9C=84=ED=95=B4=20array?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed : 테스트 형식을 위해 array출력 형식 변경 - 테스트에서 array 출력시 오류 - string으로 변경하여 출력 --- __tests__/LottoTest.js | 6 +++--- __tests__/unitTest/validation.test.js | 8 ++++---- src/Lotto.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index cbd3e880f..1d743ffe0 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -16,7 +16,7 @@ const mockRandoms = (numbers) => { }, MissionUtils.Random.pickUniqueNumbersInRange); }; -describe("로또 클래스 테스트", () => { +describe.skip("로또 클래스 테스트", () => { test("예외 테스트 : 로또 번호의 개수가 6개가 넘어가는 경우", () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 6, 7]); @@ -41,7 +41,7 @@ describe("로또 클래스 테스트", () => { const RANDOM_NUMBERS_TO_END = [7, 1, 43, 24, 35, 6]; mockRandoms([RANDOM_NUMBERS_TO_END]); - const log = [1, 6, 7, 24, 35, 43] + const log = "[1, 6, 7, 24, 35, 43]" const lottos = lottoUtils.generateNLottos(1) lottos.forEach(lotto => { @@ -54,7 +54,7 @@ describe("로또 클래스 테스트", () => { }); -describe.only("당첨 케이스 테스트", () => { +describe.skip("당첨 케이스 테스트", () => { test("1등 당첨 케이스", () => { const lottos = [ new Lotto([1, 3, 5, 8, 11, 38]) diff --git a/__tests__/unitTest/validation.test.js b/__tests__/unitTest/validation.test.js index f49ddd162..6a59faf35 100644 --- a/__tests__/unitTest/validation.test.js +++ b/__tests__/unitTest/validation.test.js @@ -3,7 +3,7 @@ import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../../src/constants/co import {winningNumbersUtils} from "../../src/utils/winningNumbers.utils.js"; import {bonusNumberUtils} from "../../src/utils/bonusNumber.utils.js"; -describe("로또 구입 금액 테스트", () => { +describe.skip("로또 구입 금액 테스트", () => { test("정상 테스트", () => { const purchasePrice = "1000" @@ -42,7 +42,7 @@ describe("로또 구입 금액 테스트", () => { }); -describe("당첨 번호 유효성 테스트", () => { +describe.skip("당첨 번호 유효성 테스트", () => { test("정상 테스트", () => { const winningNumber = [1, 2, 3, 4, 5, 6] @@ -90,7 +90,7 @@ describe.only("보너스 번호 유효성 테스트", () => { test("정상 테스트", () => { const bonusNumber = 43 - expect(bonusNumberUtils.validate(bonusNumber)).toEqual(bonusNumber); + expect(bonusNumberUtils.validate(bonusNumber)).toEqual(Number(bonusNumber)); }); test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { @@ -104,7 +104,7 @@ describe.only("보너스 번호 유효성 테스트", () => { }); test("예외 테스트 : 보너스 번호가 범위를 넘어간 경우(1~45)", () => { - const bonusNumber = 46 + const bonusNumber = "46" expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); }); diff --git a/src/Lotto.js b/src/Lotto.js index 3c9df247f..ae28fea2a 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -24,7 +24,7 @@ class Lotto { } print() { - Console.print(this.#numbers); + Console.print(`[${this.#numbers.join(", ")}]`); } countLottoMatches(winningNumbers) { From 1ac683d2fc6fb3f4c46d135b7a1323c294b61673 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 00:34:18 +0900 Subject: [PATCH 22/31] =?UTF-8?q?chore=20:=20=EA=B3=B5=EB=B0=B1,=20?= =?UTF-8?q?=EC=A4=84=20=EC=A0=9C=EA=B1=B0=20=EB=93=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore : 공백, 줄 제거 등 chore : 공백, 라인 제거, 불필요한 라이브러리 제거 등 chore : 공백, 라인 제거, 불필요한 라이브러리 제거 등 --- src/Lotto.js | 4 ---- src/utils/purchasePrice.utils.js | 1 - src/utils/winningNumbers.utils.js | 2 +- src/validation/validationCheck.js | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Lotto.js b/src/Lotto.js index ae28fea2a..90f8d3b80 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -10,10 +10,6 @@ class Lotto { this.#numbers = numbers; } - getNumbers() { - return this.#numbers; - } - #validate(numbers) { if (!validationCheck.isCorrectSize(numbers, LOTTO.SIZE)) { throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(LOTTO.SIZE)); diff --git a/src/utils/purchasePrice.utils.js b/src/utils/purchasePrice.utils.js index 069317240..c66c69301 100644 --- a/src/utils/purchasePrice.utils.js +++ b/src/utils/purchasePrice.utils.js @@ -12,7 +12,6 @@ export const purchasePriceUtils = { throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); return Number(purchasePrice); }, - getLottoAmount(purchasePrice) { return purchasePrice / PURCHASE_PRICE.MIN_CURR_UNIT; } diff --git a/src/utils/winningNumbers.utils.js b/src/utils/winningNumbers.utils.js index e85a21fb8..7c0e87306 100644 --- a/src/utils/winningNumbers.utils.js +++ b/src/utils/winningNumbers.utils.js @@ -1,5 +1,5 @@ import {validationCheck} from "../validation/validationCheck.js"; -import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../constants/constants.js"; +import {ERROR_CODE, WINNING_NUMBER} from "../constants/constants.js"; export const winningNumbersUtils = { validate(winningNumbers) { diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js index 466eb47b7..11dddd9f1 100644 --- a/src/validation/validationCheck.js +++ b/src/validation/validationCheck.js @@ -13,5 +13,5 @@ export const validationCheck = { }, isCorrectSize(number, size) { return number.length === size; - } + }, } From 5e38c73ce55069b0988cbfa557798d9bd1b2b96b Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 00:46:19 +0900 Subject: [PATCH 23/31] =?UTF-8?q?refactor=20:=20validation=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : validation 파일 통합 - 여러개의 utils 중 validate 파일통합 - validationCheck -> validator로 파일명 변경 --- __tests__/ApplicationTest.js | 2 +- __tests__/LottoTest.js | 4 +- __tests__/unitTest/validation.test.js | 50 +++++++++++----------- src/App.js | 12 +++--- src/Lotto.js | 6 +-- src/utils/InputHandler.js | 4 +- src/utils/bonusNumber.utils.js | 17 -------- src/utils/purchasePrice.utils.js | 13 +----- src/utils/winningNumbers.utils.js | 19 --------- src/validation/validationCheck.js | 17 -------- src/validation/validator.js | 61 +++++++++++++++++++++++++++ 11 files changed, 100 insertions(+), 105 deletions(-) delete mode 100644 src/utils/bonusNumber.utils.js delete mode 100644 src/utils/winningNumbers.utils.js delete mode 100644 src/validation/validationCheck.js create mode 100644 src/validation/validator.js diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index c0b8277a0..6951a40d9 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -43,7 +43,7 @@ const runException = async (input) => { expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("[ERROR]")); }; -describe.only("로또 테스트", () => { +describe("로또 테스트", () => { beforeEach(() => { jest.restoreAllMocks(); }); diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 1d743ffe0..f39886342 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -16,7 +16,7 @@ const mockRandoms = (numbers) => { }, MissionUtils.Random.pickUniqueNumbersInRange); }; -describe.skip("로또 클래스 테스트", () => { +describe("로또 클래스 테스트", () => { test("예외 테스트 : 로또 번호의 개수가 6개가 넘어가는 경우", () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 6, 7]); @@ -54,7 +54,7 @@ describe.skip("로또 클래스 테스트", () => { }); -describe.skip("당첨 케이스 테스트", () => { +describe("당첨 케이스 테스트", () => { test("1등 당첨 케이스", () => { const lottos = [ new Lotto([1, 3, 5, 8, 11, 38]) diff --git a/__tests__/unitTest/validation.test.js b/__tests__/unitTest/validation.test.js index 6a59faf35..ed481aaff 100644 --- a/__tests__/unitTest/validation.test.js +++ b/__tests__/unitTest/validation.test.js @@ -1,117 +1,115 @@ -import {purchasePriceUtils} from "../../src/utils/purchasePrice.utils.js"; import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../../src/constants/constants.js"; -import {winningNumbersUtils} from "../../src/utils/winningNumbers.utils.js"; -import {bonusNumberUtils} from "../../src/utils/bonusNumber.utils.js"; +import {validator} from "../../src/validation/validator.js"; -describe.skip("로또 구입 금액 테스트", () => { +describe("로또 구입 금액 테스트", () => { test("정상 테스트", () => { const purchasePrice = "1000" - expect(purchasePriceUtils.validate(purchasePrice)).toEqual(1000); + expect(validator.purchasePrice.validate(purchasePrice)).toEqual(1000); }); test("정상 테스트", () => { const purchasePrice = 9007199254740000 - expect(purchasePriceUtils.validate(purchasePrice)).toEqual(9007199254740000); + expect(validator.purchasePrice.validate(purchasePrice)).toEqual(9007199254740000); }); test("예외 테스트 : 숫자가 아닌 값", () => { const purchasePrice = "12ab" - expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 0 이하의 값 ", () => { const purchasePrice = "0" - expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 음수값 ", () => { const purchasePrice = "-1000" - expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 1000단위가 아닌 금액", () => { const purchasePrice = "9007199254740991" - expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); }); test("예외 테스트 : 범위를 벗어난 값", () => { const purchasePrice = "9007199254740993" - expect(() => purchasePriceUtils.validate(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); }); }); -describe.skip("당첨 번호 유효성 테스트", () => { +describe("당첨 번호 유효성 테스트", () => { test("정상 테스트", () => { const winningNumber = [1, 2, 3, 4, 5, 6] - expect(winningNumbersUtils.validate(winningNumber)).toEqual(winningNumber); + expect(validator.winningNumbers.validate(winningNumber)).toEqual(winningNumber); }); test("정상 테스트", () => { const winningNumber = [3, 12, 14, 35, 41, 45] - expect(winningNumbersUtils.validate(winningNumber)).toEqual(winningNumber); + expect(validator.winningNumbers.validate(winningNumber)).toEqual(winningNumber); }); test("예외 테스트 : 양의 정수가 아닌 값 포함", () => { const winningNumber = [3, -12, 14, 35, 0, 42] - expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 양의 정수가 아닌 값 포함", () => { const winningNumber = ["1ab", 12, 14, 35, 41, 42] - expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 유효범위(1~45) 이상의 값이 포함된 경우", () => { const winningNumber = [1, 12, 14, 35, 41, 46] - expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); }); test("예외 테스트 : 당첨 번호 값이 6개 미만 경우", () => { const winningNumber = [1, 12, 14, 35, 41] - expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); }); test("예외 테스트 : 당첨 번호 값이 7개 이상 경우", () => { const winningNumber = [1, 12, 14, 35, 41, 42, 43] - expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); }); test("예외 테스트 : 당첨 번호에 중복값이 존재하는 경우", () => { const winningNumber = [1, 12, 12, 35, 41, 42] - expect(() => winningNumbersUtils.validate(winningNumber)).toThrow(ERROR_CODE.NUMBER_DUPLICATE); + expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.NUMBER_DUPLICATE); }); }); -describe.only("보너스 번호 유효성 테스트", () => { +describe("보너스 번호 유효성 테스트", () => { test("정상 테스트", () => { const bonusNumber = 43 - expect(bonusNumberUtils.validate(bonusNumber)).toEqual(Number(bonusNumber)); + expect(validator.bonusNumbers.validate(bonusNumber)).toEqual(Number(bonusNumber)); }); test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { const bonusNumber = "0" - expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.bonusNumbers.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { const bonusNumber = "abc" - expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => validator.bonusNumbers.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 보너스 번호가 범위를 넘어간 경우(1~45)", () => { const bonusNumber = "46" - expect(() => bonusNumberUtils.validate(bonusNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + expect(() => validator.bonusNumbers.validate(bonusNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); }); test("예외 테스트 : 당첨 번호에 중복값이 존재하는 경우", () => { const winningNumber = [1, 12, 14, 35, 41, 42] const bonusNumber = 1 - expect(() => bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumber)).toThrow(ERROR_CODE.BONUS_NUMBER_DUPLICATE); + expect(() => validator.bonusNumbers.validateWithWinningNumbers(bonusNumber, winningNumber)).toThrow(ERROR_CODE.BONUS_NUMBER_DUPLICATE); }); }); diff --git a/src/App.js b/src/App.js index 7ba1219fe..d6a822f88 100644 --- a/src/App.js +++ b/src/App.js @@ -3,13 +3,12 @@ import {INSTRUCTION} from "./constants/constants.js"; import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' import {lottoUtils} from "./utils/lotto.utils.js"; -import {winningNumbersUtils} from "./utils/winningNumbers.utils.js"; -import {bonusNumberUtils} from "./utils/bonusNumber.utils.js"; +import {validator} from "./validation/validator.js"; class App { async run() { try { - const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceUtils.validate); + const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, validator.purchasePrice.validate); const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); @@ -19,12 +18,12 @@ class App { }) const winningNumbers = await InputHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, - winningNumbersUtils.validate, + validator.winningNumbers.validate, (str) => str.split(',')); const bonusNumber = await InputHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, - bonusNumberUtils.validate); - bonusNumberUtils.validateWithWinningNumbers(bonusNumber, winningNumbers); + validator.bonusNumbers.validate); + validator.bonusNumbers.validateWithWinningNumbers(bonusNumber, winningNumbers); const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); @@ -36,6 +35,7 @@ class App { }) const profitRate = lottoUtils.calculateProfitRate(totalPrize, purchasePrice); Console.print(INSTRUCTION.PRINT_PROFIT_RATE(profitRate)) + } catch (error) { Console.print(error.message) } diff --git a/src/Lotto.js b/src/Lotto.js index 90f8d3b80..1dc79b665 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,6 +1,6 @@ import {Console} from '@woowacourse/mission-utils' import {ERROR_CODE, LOTTO, WINNING_NUMBER} from "./constants/constants.js"; -import {validationCheck} from "./validation/validationCheck.js"; +import {validator} from "./validation/validator.js"; class Lotto { #numbers; @@ -11,10 +11,10 @@ class Lotto { } #validate(numbers) { - if (!validationCheck.isCorrectSize(numbers, LOTTO.SIZE)) { + if (!validator.isCorrectSize(numbers, LOTTO.SIZE)) { throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(LOTTO.SIZE)); } - if (validationCheck.hasDuplicates(numbers)) { + if (validator.hasDuplicates(numbers)) { throw new Error(ERROR_CODE.NUMBER_DUPLICATE) } } diff --git a/src/utils/InputHandler.js b/src/utils/InputHandler.js index 99b629759..06b028899 100644 --- a/src/utils/InputHandler.js +++ b/src/utils/InputHandler.js @@ -1,8 +1,8 @@ import {Console} from '@woowacourse/mission-utils' export const InputHandler = { - async getInput(instruction, validationCheck = (x) => x, process = (x) => x) { + async getInput(instruction, validator = (x) => x, process = (x) => x) { const input = await Console.readLineAsync(instruction) - return validationCheck(process(input)) + return validator(process(input)) }, } \ No newline at end of file diff --git a/src/utils/bonusNumber.utils.js b/src/utils/bonusNumber.utils.js deleted file mode 100644 index 6819ea619..000000000 --- a/src/utils/bonusNumber.utils.js +++ /dev/null @@ -1,17 +0,0 @@ -import {validationCheck} from "../validation/validationCheck.js"; -import {ERROR_CODE, WINNING_NUMBER} from "../constants/constants.js"; - -export const bonusNumberUtils = { - validate(bonusNumber) { - if (!validationCheck.isPositiveNumber(bonusNumber)) - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); - if (!validationCheck.isInRange(bonusNumber, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) { - throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); - } - return Number(bonusNumber); - }, - validateWithWinningNumbers(bonusNumber, winningNumber) { - if (validationCheck.hasDuplicates([...winningNumber, bonusNumber])) - throw new Error(ERROR_CODE.BONUS_NUMBER_DUPLICATE) - } -} \ No newline at end of file diff --git a/src/utils/purchasePrice.utils.js b/src/utils/purchasePrice.utils.js index c66c69301..f1e6c9375 100644 --- a/src/utils/purchasePrice.utils.js +++ b/src/utils/purchasePrice.utils.js @@ -1,17 +1,6 @@ -import {ERROR_CODE, PURCHASE_PRICE} from "../constants/constants.js"; -import {validationCheck} from "../validation/validationCheck.js"; +import {PURCHASE_PRICE} from "../constants/constants.js"; export const purchasePriceUtils = { - validate(purchasePrice) { - if (!validationCheck.isPositiveNumber(purchasePrice)) - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); - if (!validationCheck.isInRange(purchasePrice)) { - throw new Error(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); - } - if (!validationCheck.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT)) - throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); - return Number(purchasePrice); - }, getLottoAmount(purchasePrice) { return purchasePrice / PURCHASE_PRICE.MIN_CURR_UNIT; } diff --git a/src/utils/winningNumbers.utils.js b/src/utils/winningNumbers.utils.js deleted file mode 100644 index 7c0e87306..000000000 --- a/src/utils/winningNumbers.utils.js +++ /dev/null @@ -1,19 +0,0 @@ -import {validationCheck} from "../validation/validationCheck.js"; -import {ERROR_CODE, WINNING_NUMBER} from "../constants/constants.js"; - -export const winningNumbersUtils = { - validate(winningNumbers) { - winningNumbers.forEach((number) => { - if (!validationCheck.isPositiveNumber(number)) - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) - if (!validationCheck.isInRange(number, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) - throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); - }) - if (!validationCheck.isCorrectSize(winningNumbers, WINNING_NUMBER.SIZE)) - throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); - if (validationCheck.hasDuplicates(winningNumbers)) { - throw new Error(ERROR_CODE.NUMBER_DUPLICATE) - } - return winningNumbers.map(Number); - }, -} \ No newline at end of file diff --git a/src/validation/validationCheck.js b/src/validation/validationCheck.js deleted file mode 100644 index 11dddd9f1..000000000 --- a/src/validation/validationCheck.js +++ /dev/null @@ -1,17 +0,0 @@ -export const validationCheck = { - isPositiveNumber(number) { - return /^[1-9]\d*$/.test(number.toString().trim()) - }, - isInRange(number, minValue = 1, maxValue = Number.MAX_SAFE_INTEGER) { - return Number(number) <= maxValue && Number(number) >= minValue - }, - isDividedNumberByValue(number, value) { - return Number(number) % Number(value) === 0 - }, - hasDuplicates(numbers) { - return new Set(numbers).size !== numbers.length; - }, - isCorrectSize(number, size) { - return number.length === size; - }, -} diff --git a/src/validation/validator.js b/src/validation/validator.js new file mode 100644 index 000000000..bd5ba8d53 --- /dev/null +++ b/src/validation/validator.js @@ -0,0 +1,61 @@ +import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../constants/constants.js"; + +export const validator = { + isPositiveNumber(number) { + return /^[1-9]\d*$/.test(number.toString().trim()) + }, + isInRange(number, minValue = 1, maxValue = Number.MAX_SAFE_INTEGER) { + return Number(number) <= maxValue && Number(number) >= minValue + }, + isDividedNumberByValue(number, value) { + return Number(number) % Number(value) === 0 + }, + hasDuplicates(numbers) { + return new Set(numbers).size !== numbers.length; + }, + isCorrectSize(number, size) { + return number.length === size; + }, + purchasePrice: { + validate(purchasePrice) { + if (!validator.isPositiveNumber(purchasePrice)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); + if (!validator.isInRange(purchasePrice)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + } + if (!validator.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT)) + throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + return Number(purchasePrice); + } + }, + winningNumbers: { + validate(winningNumbers) { + winningNumbers.forEach((number) => { + if (!validator.isPositiveNumber(number)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) + if (!validator.isInRange(number, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) + throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + }) + if (!validator.isCorrectSize(winningNumbers, WINNING_NUMBER.SIZE)) + throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + if (validator.hasDuplicates(winningNumbers)) { + throw new Error(ERROR_CODE.NUMBER_DUPLICATE) + } + return winningNumbers.map(Number); + } + }, + bonusNumbers: { + validate(bonusNumber) { + if (!validator.isPositiveNumber(bonusNumber)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); + if (!validator.isInRange(bonusNumber, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + } + return Number(bonusNumber); + }, + validateWithWinningNumbers(bonusNumber, winningNumber) { + if (validator.hasDuplicates([...winningNumber, bonusNumber])) + throw new Error(ERROR_CODE.BONUS_NUMBER_DUPLICATE) + } + } +} From 899601e71aa3ba4b3ae7e635bd924b3435c45e3e Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 01:14:48 +0900 Subject: [PATCH 24/31] =?UTF-8?q?refactor=20:=20output(=EC=B6=9C=EB=A0=A5)?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : output(출력) 관련 분리 - App, lottoUtils에서 출력관련 IOHandler로 책임 분리 - InputHandler -> IOHandler로 변경 --- src/App.js | 9 ++++----- src/utils/IOHandler.js | 33 +++++++++++++++++++++++++++++++++ src/utils/InputHandler.js | 8 -------- src/utils/lotto.utils.js | 13 +------------ 4 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 src/utils/IOHandler.js delete mode 100644 src/utils/InputHandler.js diff --git a/src/App.js b/src/App.js index d6a822f88..308fc0996 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import {InputHandler} from "./utils/InputHandler.js"; +import {IOHandler} from "./utils/IOHandler.js"; import {INSTRUCTION} from "./constants/constants.js"; import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' @@ -8,7 +8,7 @@ import {validator} from "./validation/validator.js"; class App { async run() { try { - const purchasePrice = await InputHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, validator.purchasePrice.validate); + const purchasePrice = await IOHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, validator.purchasePrice.validate); const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); @@ -17,11 +17,10 @@ class App { lotto.print() }) - const winningNumbers = await InputHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, - validator.winningNumbers.validate, + const winningNumbers = await IOHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, validator.winningNumbers.validate, (str) => str.split(',')); + const bonusNumber = await IOHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, validator.bonusNumbers.validate); (str) => str.split(',')); - const bonusNumber = await InputHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, validator.bonusNumbers.validate); validator.bonusNumbers.validateWithWinningNumbers(bonusNumber, winningNumbers); diff --git a/src/utils/IOHandler.js b/src/utils/IOHandler.js new file mode 100644 index 000000000..20c31187d --- /dev/null +++ b/src/utils/IOHandler.js @@ -0,0 +1,33 @@ +import {Console} from '@woowacourse/mission-utils' +import {INSTRUCTION} from "../constants/constants.js"; +import {lottoUtils} from "./lotto.utils.js"; + +export const IOHandler = { + async getInput(instruction, validator = (x) => x, process = (x) => x) { + const input = await Console.readLineAsync(instruction) + return validator(process(input)) + }, + printLottoArray(lottos){ + lottos.map(lotto => { + lotto.print() + }) + }, + printWinningStatistics(matchNumber, prize, matchAmount) { + if (matchNumber < 3) return + if (matchNumber === 6) { + Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, lottoUtils.makeMoneyFormat(prize), matchAmount, INSTRUCTION.EXTRA_MESSAGE_SECOND_PRIZE)) + return + } + if (matchNumber === 7) { + Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, lottoUtils.makeMoneyFormat(prize), matchAmount)) + return + } + Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber, lottoUtils.makeMoneyFormat(prize), matchAmount)) + }, + printWinningStatisticsAll(lottoResult){ + Console.print(INSTRUCTION.PRINT_TOTAL_WINNING_STATISTICS); + lottoResult.forEach((amount, index) => { + this.printWinningStatistics(index, lottoUtils.getPrize(index), amount) + }) + } +} \ No newline at end of file diff --git a/src/utils/InputHandler.js b/src/utils/InputHandler.js deleted file mode 100644 index 06b028899..000000000 --- a/src/utils/InputHandler.js +++ /dev/null @@ -1,8 +0,0 @@ -import {Console} from '@woowacourse/mission-utils' - -export const InputHandler = { - async getInput(instruction, validator = (x) => x, process = (x) => x) { - const input = await Console.readLineAsync(instruction) - return validator(process(input)) - }, -} \ No newline at end of file diff --git a/src/utils/lotto.utils.js b/src/utils/lotto.utils.js index 1aaf615b8..09d126fb6 100644 --- a/src/utils/lotto.utils.js +++ b/src/utils/lotto.utils.js @@ -1,6 +1,7 @@ import {INSTRUCTION, LOTTO} from "../constants/constants.js"; import {Random, Console} from '@woowacourse/mission-utils'; import Lotto from "../Lotto.js"; +import {IOHandler} from "./IOHandler.js"; export const lottoUtils = { generateNLottos(n) { @@ -35,18 +36,6 @@ export const lottoUtils = { return 0 } }, - printWinningStatistics(matchNumber, prize, matchAmount) { - if (matchNumber < 3) return - if (matchNumber === 6) { - Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, this.makeMoneyFormat(prize), matchAmount, INSTRUCTION.EXTRA_MESSAGE_SECOND_PRIZE)) - return - } - if (matchNumber === 7) { - Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, this.makeMoneyFormat(prize), matchAmount)) - return - } - Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber, this.makeMoneyFormat(prize), matchAmount)) - }, makeMoneyFormat(money, separator = ",") { return money.toString().split("").reverse().join("") .replace(/(.{3})(?=.)/g, `$1${separator}`) From c4737a2ecd2258af899dcfaf54ca4631737fe816 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 01:16:09 +0900 Subject: [PATCH 25/31] =?UTF-8?q?refactor=20:=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EA=B3=84=EC=82=B0=20=EB=B0=8F=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=EB=B6=80=EB=B6=84=20=ED=95=A8=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : 로또 결과 계산 및 출력 부분 함수화 - 로또 결과 계산 및 수익률계산 함수 - 수익률 출력시 형식 지정 (소수점 1번째 까지 출력) --- src/App.js | 20 ++------------------ src/constants/constants.js | 2 +- src/utils/lotto.utils.js | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/App.js b/src/App.js index 308fc0996..b8a76bb8e 100644 --- a/src/App.js +++ b/src/App.js @@ -11,30 +11,14 @@ class App { const purchasePrice = await IOHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, validator.purchasePrice.validate); const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); - const lottos = lottoUtils.generateNLottos(lottoAmount); - lottos.map((lotto) => { - lotto.print() - }) + IOHandler.printLottoArray(lottos) const winningNumbers = await IOHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, validator.winningNumbers.validate, (str) => str.split(',')); const bonusNumber = await IOHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, validator.bonusNumbers.validate); - (str) => str.split(',')); - - validator.bonusNumbers.validate); validator.bonusNumbers.validateWithWinningNumbers(bonusNumber, winningNumbers); - const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); - - let totalPrize = 0; - Console.print(INSTRUCTION.PRINT_TOTAL_WINNING_STATISTICS); - lottoResult.forEach((amount, index) => { - lottoUtils.printWinningStatistics(index, lottoUtils.getPrize(index), amount) - totalPrize += amount * lottoUtils.getPrize(index); - }) - const profitRate = lottoUtils.calculateProfitRate(totalPrize, purchasePrice); - Console.print(INSTRUCTION.PRINT_PROFIT_RATE(profitRate)) - + lottoUtils.checkResult(lottos, winningNumbers, bonusNumber, purchasePrice); } catch (error) { Console.print(error.message) } diff --git a/src/constants/constants.js b/src/constants/constants.js index 2e45480d5..ffbdbe288 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -37,5 +37,5 @@ export const INSTRUCTION = { EXTRA_MESSAGE_SECOND_PRIZE: ", 보너스 볼 일치", PRINT_WINNING_STATISTICS: (matchNumber, prize, matchAmount, extraMessage = "") => `${matchNumber}개 일치${extraMessage} (${prize}원) - ${matchAmount}개`, PRINT_TOTAL_WINNING_STATISTICS: "\n당첨 통계\n---", - PRINT_PROFIT_RATE: (profitRate) => `총 수익률은 ${profitRate}%입니다.` + PRINT_PROFIT_RATE: (profitRate) => `총 수익률은 ${profitRate}입니다.` } \ No newline at end of file diff --git a/src/utils/lotto.utils.js b/src/utils/lotto.utils.js index 09d126fb6..70cdc35b6 100644 --- a/src/utils/lotto.utils.js +++ b/src/utils/lotto.utils.js @@ -44,6 +44,24 @@ export const lottoUtils = { calculateProfitRate(profit, purchasePrice) { const profitRate = profit / purchasePrice * 100; return Math.round(profitRate * 100) / 100; + }, + calculateTotalPrize(lottoResult){ + let totalPrize = 0; + lottoResult.forEach((amount, index) => { + totalPrize += amount * lottoUtils.getPrize(index); + }) + return totalPrize; + }, + checkResult(lottos, winningNumbers, bonusNumber, purchasePrice) { + const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); + + let totalPrize = lottoUtils.calculateTotalPrize(lottoResult); + IOHandler.printWinningStatisticsAll(lottoResult); + + const profitRate = lottoUtils.calculateProfitRate(totalPrize, purchasePrice); + + const formattedRate = `${profitRate.toFixed(1)}%`; + Console.print(INSTRUCTION.PRINT_PROFIT_RATE(formattedRate)) } } \ No newline at end of file From 0699199866677299b8ee721e2f5806493ff2e048 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 21:27:54 +0900 Subject: [PATCH 26/31] =?UTF-8?q?refactor=20:=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=EC=9D=80=20IOHandler=EC=97=90=EC=84=9C=EB=A7=8C=20=EB=8B=B4?= =?UTF-8?q?=EB=8B=B9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : 출력은 IOHandler에서만 담당하도록 변경 - Lotto class의 출력 부분을 변경하고 IOHandler가 담당하도록 변경 --- __tests__/LottoTest.js | 4 ++-- src/Lotto.js | 4 ++-- src/utils/IOHandler.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index f39886342..648a87b1f 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,5 +1,5 @@ import Lotto from "../src/Lotto"; -import {MissionUtils} from "@woowacourse/mission-utils"; +import {MissionUtils, Console} from "@woowacourse/mission-utils"; import {lottoUtils} from "../src/utils/lotto.utils.js"; import {ERROR_CODE, LOTTO} from "../src/constants/constants.js"; @@ -45,7 +45,7 @@ describe("로또 클래스 테스트", () => { const lottos = lottoUtils.generateNLottos(1) lottos.forEach(lotto => { - lotto.print() + Console.print(lotto.toString()) expect(logSpy).toHaveBeenCalledWith(log); }) diff --git a/src/Lotto.js b/src/Lotto.js index 1dc79b665..63102d1ba 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -19,8 +19,8 @@ class Lotto { } } - print() { - Console.print(`[${this.#numbers.join(", ")}]`); + toString() { + return `[${this.#numbers.join(", ")}]`; } countLottoMatches(winningNumbers) { diff --git a/src/utils/IOHandler.js b/src/utils/IOHandler.js index 20c31187d..76030fe34 100644 --- a/src/utils/IOHandler.js +++ b/src/utils/IOHandler.js @@ -7,9 +7,9 @@ export const IOHandler = { const input = await Console.readLineAsync(instruction) return validator(process(input)) }, - printLottoArray(lottos){ + printLottoArray(lottos) { lottos.map(lotto => { - lotto.print() + Console.print(lotto.toString()) }) }, printWinningStatistics(matchNumber, prize, matchAmount) { From 1ea02c37c7b5006dc737b6610417069eb91fedcb Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 21:30:09 +0900 Subject: [PATCH 27/31] =?UTF-8?q?refactor=20:=20default=20=EA=B0=92=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EB=8C=80=EC=8B=A0=20=EA=B0=92=EC=9D=B4=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=EC=A7=80=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : default 값 사용대신 값이 있는지 체크 - default 값을 정의하지 않고, 값이 있을때만 수행하도록 변경 --- src/utils/IOHandler.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/utils/IOHandler.js b/src/utils/IOHandler.js index 76030fe34..8bb02fe84 100644 --- a/src/utils/IOHandler.js +++ b/src/utils/IOHandler.js @@ -3,9 +3,15 @@ import {INSTRUCTION} from "../constants/constants.js"; import {lottoUtils} from "./lotto.utils.js"; export const IOHandler = { - async getInput(instruction, validator = (x) => x, process = (x) => x) { - const input = await Console.readLineAsync(instruction) - return validator(process(input)) + async getInput(instruction, validator, process) { + let input = await Console.readLineAsync(instruction) + + if (process) + input = process(input); + if (validator) + input = validator(input); + + return input }, printLottoArray(lottos) { lottos.map(lotto => { @@ -14,17 +20,17 @@ export const IOHandler = { }, printWinningStatistics(matchNumber, prize, matchAmount) { if (matchNumber < 3) return - if (matchNumber === 6) { + if (matchNumber === 6) { //2등 ( 5개 + 보너스 번호 ) Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, lottoUtils.makeMoneyFormat(prize), matchAmount, INSTRUCTION.EXTRA_MESSAGE_SECOND_PRIZE)) return } - if (matchNumber === 7) { + if (matchNumber === 7) { // 1등 ( 2등과 구분을 위해 맞은번호(6개) + 1개 = 7 ) Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber - 1, lottoUtils.makeMoneyFormat(prize), matchAmount)) return } Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber, lottoUtils.makeMoneyFormat(prize), matchAmount)) }, - printWinningStatisticsAll(lottoResult){ + printWinningStatisticsAll(lottoResult) { Console.print(INSTRUCTION.PRINT_TOTAL_WINNING_STATISTICS); lottoResult.forEach((amount, index) => { this.printWinningStatistics(index, lottoUtils.getPrize(index), amount) From 92f688439fceb8daa36403f22e152866ec365c06 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 21:54:01 +0900 Subject: [PATCH 28/31] =?UTF-8?q?refactor=20:=20depth=202=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor : depth 2 제한 수정 - validator에서 depth 2를 넘어 function으로 수정 --- __tests__/unitTest/validation.test.js | 47 ++++++++------- src/App.js | 15 +++-- src/validation/validator.js | 84 +++++++++++++-------------- 3 files changed, 78 insertions(+), 68 deletions(-) diff --git a/__tests__/unitTest/validation.test.js b/__tests__/unitTest/validation.test.js index ed481aaff..1bbfc205c 100644 --- a/__tests__/unitTest/validation.test.js +++ b/__tests__/unitTest/validation.test.js @@ -1,41 +1,46 @@ import {ERROR_CODE, PURCHASE_PRICE, WINNING_NUMBER} from "../../src/constants/constants.js"; -import {validator} from "../../src/validation/validator.js"; +import { + bonusNumbersValidate, bonusNumbersValidateWithWinningNumber, + purchasePriceValidate, + validator, + winningNumbersValidate +} from "../../src/validation/validator.js"; describe("로또 구입 금액 테스트", () => { test("정상 테스트", () => { const purchasePrice = "1000" - expect(validator.purchasePrice.validate(purchasePrice)).toEqual(1000); + expect(purchasePriceValidate(purchasePrice)).toEqual(1000); }); test("정상 테스트", () => { const purchasePrice = 9007199254740000 - expect(validator.purchasePrice.validate(purchasePrice)).toEqual(9007199254740000); + expect(purchasePriceValidate(purchasePrice)).toEqual(9007199254740000); }); test("예외 테스트 : 숫자가 아닌 값", () => { const purchasePrice = "12ab" - expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => purchasePriceValidate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 0 이하의 값 ", () => { const purchasePrice = "0" - expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => purchasePriceValidate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 음수값 ", () => { const purchasePrice = "-1000" - expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => purchasePriceValidate(purchasePrice)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 1000단위가 아닌 금액", () => { const purchasePrice = "9007199254740991" - expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + expect(() => purchasePriceValidate(purchasePrice)).toThrow(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); }); test("예외 테스트 : 범위를 벗어난 값", () => { const purchasePrice = "9007199254740993" - expect(() => validator.purchasePrice.validate(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + expect(() => purchasePriceValidate(purchasePrice)).toThrow(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); }); }); @@ -44,43 +49,43 @@ describe("당첨 번호 유효성 테스트", () => { test("정상 테스트", () => { const winningNumber = [1, 2, 3, 4, 5, 6] - expect(validator.winningNumbers.validate(winningNumber)).toEqual(winningNumber); + expect(winningNumbersValidate(winningNumber)).toEqual(winningNumber); }); test("정상 테스트", () => { const winningNumber = [3, 12, 14, 35, 41, 45] - expect(validator.winningNumbers.validate(winningNumber)).toEqual(winningNumber); + expect(winningNumbersValidate(winningNumber)).toEqual(winningNumber); }); test("예외 테스트 : 양의 정수가 아닌 값 포함", () => { const winningNumber = [3, -12, 14, 35, 0, 42] - expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => winningNumbersValidate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 양의 정수가 아닌 값 포함", () => { const winningNumber = ["1ab", 12, 14, 35, 41, 42] - expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => winningNumbersValidate(winningNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 유효범위(1~45) 이상의 값이 포함된 경우", () => { const winningNumber = [1, 12, 14, 35, 41, 46] - expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + expect(() => winningNumbersValidate(winningNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); }); test("예외 테스트 : 당첨 번호 값이 6개 미만 경우", () => { const winningNumber = [1, 12, 14, 35, 41] - expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + expect(() => winningNumbersValidate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); }); test("예외 테스트 : 당첨 번호 값이 7개 이상 경우", () => { const winningNumber = [1, 12, 14, 35, 41, 42, 43] - expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + expect(() => winningNumbersValidate(winningNumber)).toThrow(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); }); test("예외 테스트 : 당첨 번호에 중복값이 존재하는 경우", () => { const winningNumber = [1, 12, 12, 35, 41, 42] - expect(() => validator.winningNumbers.validate(winningNumber)).toThrow(ERROR_CODE.NUMBER_DUPLICATE); + expect(() => winningNumbersValidate(winningNumber)).toThrow(ERROR_CODE.NUMBER_DUPLICATE); }); }); @@ -88,28 +93,28 @@ describe("보너스 번호 유효성 테스트", () => { test("정상 테스트", () => { const bonusNumber = 43 - expect(validator.bonusNumbers.validate(bonusNumber)).toEqual(Number(bonusNumber)); + expect(bonusNumbersValidate(bonusNumber)).toEqual(Number(bonusNumber)); }); test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { const bonusNumber = "0" - expect(() => validator.bonusNumbers.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => bonusNumbersValidate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 양의 정수를 입력하지 않은 경우", () => { const bonusNumber = "abc" - expect(() => validator.bonusNumbers.validate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); + expect(() => bonusNumbersValidate(bonusNumber)).toThrow(ERROR_CODE.NOT_POSITIVE_NUMBER); }); test("예외 테스트 : 보너스 번호가 범위를 넘어간 경우(1~45)", () => { const bonusNumber = "46" - expect(() => validator.bonusNumbers.validate(bonusNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + expect(() => bonusNumbersValidate(bonusNumber)).toThrow(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); }); test("예외 테스트 : 당첨 번호에 중복값이 존재하는 경우", () => { const winningNumber = [1, 12, 14, 35, 41, 42] const bonusNumber = 1 - expect(() => validator.bonusNumbers.validateWithWinningNumbers(bonusNumber, winningNumber)).toThrow(ERROR_CODE.BONUS_NUMBER_DUPLICATE); + expect(() => bonusNumbersValidateWithWinningNumber(bonusNumber, winningNumber)).toThrow(ERROR_CODE.BONUS_NUMBER_DUPLICATE); }); }); diff --git a/src/App.js b/src/App.js index b8a76bb8e..9e691fa14 100644 --- a/src/App.js +++ b/src/App.js @@ -3,20 +3,25 @@ import {INSTRUCTION} from "./constants/constants.js"; import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' import {lottoUtils} from "./utils/lotto.utils.js"; -import {validator} from "./validation/validator.js"; +import { + bonusNumbersValidate, bonusNumbersValidateWithWinningNumber, + purchasePriceValidate, + validator, + winningNumbersValidate +} from "./validation/validator.js"; class App { async run() { try { - const purchasePrice = await IOHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, validator.purchasePrice.validate); + const purchasePrice = await IOHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceValidate); const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); const lottos = lottoUtils.generateNLottos(lottoAmount); IOHandler.printLottoArray(lottos) - const winningNumbers = await IOHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, validator.winningNumbers.validate, (str) => str.split(',')); - const bonusNumber = await IOHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, validator.bonusNumbers.validate); - validator.bonusNumbers.validateWithWinningNumbers(bonusNumber, winningNumbers); + const winningNumbers = await IOHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, winningNumbersValidate, (str) => str.split(',')); + const bonusNumber = await IOHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, bonusNumbersValidate); + bonusNumbersValidateWithWinningNumber(bonusNumber, winningNumbers); lottoUtils.checkResult(lottos, winningNumbers, bonusNumber, purchasePrice); } catch (error) { diff --git a/src/validation/validator.js b/src/validation/validator.js index bd5ba8d53..08e8bc546 100644 --- a/src/validation/validator.js +++ b/src/validation/validator.js @@ -15,47 +15,47 @@ export const validator = { }, isCorrectSize(number, size) { return number.length === size; - }, - purchasePrice: { - validate(purchasePrice) { - if (!validator.isPositiveNumber(purchasePrice)) - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); - if (!validator.isInRange(purchasePrice)) { - throw new Error(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); - } - if (!validator.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT)) - throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); - return Number(purchasePrice); - } - }, - winningNumbers: { - validate(winningNumbers) { - winningNumbers.forEach((number) => { - if (!validator.isPositiveNumber(number)) - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) - if (!validator.isInRange(number, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) - throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); - }) - if (!validator.isCorrectSize(winningNumbers, WINNING_NUMBER.SIZE)) - throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); - if (validator.hasDuplicates(winningNumbers)) { - throw new Error(ERROR_CODE.NUMBER_DUPLICATE) - } - return winningNumbers.map(Number); - } - }, - bonusNumbers: { - validate(bonusNumber) { - if (!validator.isPositiveNumber(bonusNumber)) - throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); - if (!validator.isInRange(bonusNumber, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) { - throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); - } - return Number(bonusNumber); - }, - validateWithWinningNumbers(bonusNumber, winningNumber) { - if (validator.hasDuplicates([...winningNumber, bonusNumber])) - throw new Error(ERROR_CODE.BONUS_NUMBER_DUPLICATE) - } } } + +export function purchasePriceValidate(purchasePrice) { + if (!validator.isPositiveNumber(purchasePrice)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); + if (!validator.isInRange(purchasePrice)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(1, Number.MAX_SAFE_INTEGER)); + } + if (!validator.isDividedNumberByValue(purchasePrice, PURCHASE_PRICE.MIN_CURR_UNIT)) + throw new Error(ERROR_CODE.NOT_DIVIDED_BY_VALUE(PURCHASE_PRICE.MIN_CURR_UNIT)); + return Number(purchasePrice); +} + +export function winningNumbersValidate(winningNumbers) { + winningNumbers.forEach((number) => { + if (!validator.isPositiveNumber(number)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER) + if (!validator.isInRange(number, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) + throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + }) + if (!validator.isCorrectSize(winningNumbers, WINNING_NUMBER.SIZE)) + throw new Error(ERROR_CODE.SIZE_OUT_OF_RANGE(WINNING_NUMBER.SIZE)); + if (validator.hasDuplicates(winningNumbers)) { + throw new Error(ERROR_CODE.NUMBER_DUPLICATE) + } + return winningNumbers.map(Number); +} + +export function bonusNumbersValidate(bonusNumber) { + if (!validator.isPositiveNumber(bonusNumber)) + throw new Error(ERROR_CODE.NOT_POSITIVE_NUMBER); + if (!validator.isInRange(bonusNumber, WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)) { + throw new Error(ERROR_CODE.OUT_OF_RANGE(WINNING_NUMBER.MIN_NUMBER, WINNING_NUMBER.MAX_NUMBER)); + } + return Number(bonusNumber); +} + +export function bonusNumbersValidateWithWinningNumber(bonusNumber, winningNumber) { + if (validator.hasDuplicates([...winningNumber, bonusNumber])) + throw new Error(ERROR_CODE.BONUS_NUMBER_DUPLICATE) +} + + From f417c7153a1360e3338ba08160c2ca4d2c41d1c5 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 22:32:45 +0900 Subject: [PATCH 29/31] =?UTF-8?q?refactor(Lotto.js)=20:=20private=20method?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(Lotto.js) : private method로 변경 - 클래스 내부에서만 사용되어야 하는 함수 private으로 변경 --- src/Lotto.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Lotto.js b/src/Lotto.js index 63102d1ba..25be474de 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -23,17 +23,17 @@ class Lotto { return `[${this.#numbers.join(", ")}]`; } - countLottoMatches(winningNumbers) { + #countLottoMatches(winningNumbers) { return this.#numbers.filter(num => winningNumbers.includes(num)).length } - isBonusNumberMatch(bonusNumber) { + #isBonusNumberMatch(bonusNumber) { return this.#numbers.includes(bonusNumber); } getLottoResult(winningNumbers, bonusNumber) { - const matchNumber = this.countLottoMatches(winningNumbers); - if (matchNumber === 5 && this.isBonusNumberMatch(bonusNumber) || matchNumber === 6) { + const matchNumber = this.#countLottoMatches(winningNumbers); + if (matchNumber === 5 && this.#isBonusNumberMatch(bonusNumber) || matchNumber === 6) { return matchNumber + 1; } return matchNumber; From b42fcf96629dd831f04cdcde6c0cf36f2c424590 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 23:43:55 +0900 Subject: [PATCH 30/31] =?UTF-8?q?refactor=20:=20LottoGame=20class=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++++- src/App.js | 20 +++++++++------- src/{ => Models}/Lotto.js | 5 ++-- src/Models/LottoGame.js | 50 +++++++++++++++++++++++++++++++++++++++ src/utils/IOHandler.js | 15 ++++++++---- src/utils/lotto.utils.js | 36 +++------------------------- 6 files changed, 88 insertions(+), 51 deletions(-) rename src/{ => Models}/Lotto.js (85%) create mode 100644 src/Models/LottoGame.js diff --git a/README.md b/README.md index 8e69e42e0..87b58be90 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,15 @@ ### 5. 보너스 번호 입력 - 양의 정수를 입력했는가 - 당첨번호가 유효범위 내에 있지 않은 경우 -- 당첨 번호와 중복되는지 여부 \ No newline at end of file +- 당첨 번호와 중복되는지 여부 + +--- + +## 3. 고민한 부분 +- 지난 회차에서 미흡하다고 생각했던 Unit Test를 상세히 추가했습니다. + 특히, validation Check를 꼼꼼하게 하지 못했다고 생각해서, 검사하는 부분 및 순서에 시간을 많이 사용하였습니다. 또한, 해당 부분을 중점으로 unit test를 좀 더 꼼꼼하게 작성하였습니다. +- 상수화에 대한 고민. 상수화를 최대한 하되, 구분이 되도록 constant.js 파일 내에서도 관련 그룹으로 묶어 용도를 명확히 하고자 노력했습니다. +- 책임의 분리. 어떤 객체, 클래스에 책임을 두어야 할지 고민하면서, 객체가 너무 많아지지 않도록 여러번의 refactoring을 거쳤습니다. + +이번 과제는 구현하면서 세세한 부분이 생각보다 많아, 제가 생각한 것보다 코드를 깔끔하게 작성하기 어려웠습니다.
+또한, 코드와 함수가 많아지니 책임의 분리가 어려웠습니다. 부족한 코드지만 많은 피드백 부탁드립니다. \ No newline at end of file diff --git a/src/App.js b/src/App.js index 9e691fa14..8a5c4e0ba 100644 --- a/src/App.js +++ b/src/App.js @@ -4,26 +4,28 @@ import {purchasePriceUtils} from "./utils/purchasePrice.utils.js"; import {Console} from '@woowacourse/mission-utils' import {lottoUtils} from "./utils/lotto.utils.js"; import { - bonusNumbersValidate, bonusNumbersValidateWithWinningNumber, purchasePriceValidate, - validator, - winningNumbersValidate } from "./validation/validator.js"; +import LottoGame from "./Models/LottoGame.js"; class App { async run() { try { - const purchasePrice = await IOHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE, purchasePriceValidate); + const purchasePrice = await IOHandler.getInput(INSTRUCTION.GET_PURCHASE_PRICE); + purchasePriceValidate(purchasePrice); + const lottoAmount = purchasePriceUtils.getLottoAmount(purchasePrice); - Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); + IOHandler.printLottoAmount(lottoAmount); const lottos = lottoUtils.generateNLottos(lottoAmount); IOHandler.printLottoArray(lottos) - const winningNumbers = await IOHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, winningNumbersValidate, (str) => str.split(',')); - const bonusNumber = await IOHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER, bonusNumbersValidate); - bonusNumbersValidateWithWinningNumber(bonusNumber, winningNumbers); + const winningNumbers = await IOHandler.getInput(INSTRUCTION.GET_WINNING_NUMBERS, (str) => str.split(',')); + const bonusNumber = await IOHandler.getInput(INSTRUCTION.GET_BONUS_NUMBER); + + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + IOHandler.printWinningStatisticsAll(lottoGame) + IOHandler.printProfitRate(lottoGame.calculateProfitRate(purchasePrice)) - lottoUtils.checkResult(lottos, winningNumbers, bonusNumber, purchasePrice); } catch (error) { Console.print(error.message) } diff --git a/src/Lotto.js b/src/Models/Lotto.js similarity index 85% rename from src/Lotto.js rename to src/Models/Lotto.js index 25be474de..dfada5c44 100644 --- a/src/Lotto.js +++ b/src/Models/Lotto.js @@ -1,6 +1,5 @@ -import {Console} from '@woowacourse/mission-utils' -import {ERROR_CODE, LOTTO, WINNING_NUMBER} from "./constants/constants.js"; -import {validator} from "./validation/validator.js"; +import {ERROR_CODE, LOTTO} from "../constants/constants.js"; +import {validator} from "../validation/validator.js"; class Lotto { #numbers; diff --git a/src/Models/LottoGame.js b/src/Models/LottoGame.js new file mode 100644 index 000000000..e3776c6a3 --- /dev/null +++ b/src/Models/LottoGame.js @@ -0,0 +1,50 @@ +import { + bonusNumbersValidate, + bonusNumbersValidateWithWinningNumber, + winningNumbersValidate +} from "../validation/validator.js"; +import {lottoUtils} from "../utils/lotto.utils.js"; + +class LottoGame { + #winningNumber + #bonusNumber + #lottos //Lotto 객체 + + constructor(winningNumber, bonusNumber, lottos) { + this.#validate(winningNumber, bonusNumber) + this.#winningNumber = winningNumber.map(Number); + this.#bonusNumber = Number(bonusNumber); + this.#lottos = lottos; + } + + #validate(winningNumber, bonusNumber) { + winningNumbersValidate(winningNumber); + bonusNumbersValidate(bonusNumber); + bonusNumbersValidateWithWinningNumber(bonusNumber, winningNumber); + } + + getLottoMatchResultArray() { + let lottoResult = Array(8).fill(0); + this.#lottos.forEach((lotto) => { + const matchNumber = lotto.getLottoResult(this.#winningNumber, this.#bonusNumber); + lottoResult[matchNumber]++ + }) + return lottoResult; + } + + calculateProfitRate(purchasePrice) { + const profitRate = this.calculateTotalPrize() / purchasePrice * 100; + return Math.round(profitRate * 100) / 100; + } + + calculateTotalPrize() { + let totalPrize = 0; + this.getLottoMatchResultArray().forEach((amount, index) => { + totalPrize += amount * lottoUtils.getPrize(index); + }) + return totalPrize; + } + +} + +export default LottoGame; \ No newline at end of file diff --git a/src/utils/IOHandler.js b/src/utils/IOHandler.js index 8bb02fe84..94966c698 100644 --- a/src/utils/IOHandler.js +++ b/src/utils/IOHandler.js @@ -3,13 +3,11 @@ import {INSTRUCTION} from "../constants/constants.js"; import {lottoUtils} from "./lotto.utils.js"; export const IOHandler = { - async getInput(instruction, validator, process) { + async getInput(instruction, process) { let input = await Console.readLineAsync(instruction) if (process) input = process(input); - if (validator) - input = validator(input); return input }, @@ -30,10 +28,17 @@ export const IOHandler = { } Console.print(INSTRUCTION.PRINT_WINNING_STATISTICS(matchNumber, lottoUtils.makeMoneyFormat(prize), matchAmount)) }, - printWinningStatisticsAll(lottoResult) { + printWinningStatisticsAll(lottoGame) { Console.print(INSTRUCTION.PRINT_TOTAL_WINNING_STATISTICS); - lottoResult.forEach((amount, index) => { + lottoGame.getLottoMatchResultArray().forEach((amount, index) => { this.printWinningStatistics(index, lottoUtils.getPrize(index), amount) }) + }, + printProfitRate(profitRate) { + const formattedRate = `${profitRate.toFixed(1)}%`; + Console.print(INSTRUCTION.PRINT_PROFIT_RATE(formattedRate)) + }, + printLottoAmount(lottoAmount) { + Console.print(INSTRUCTION.PRINT_LOTTO_AMOUNT(lottoAmount)); } } \ No newline at end of file diff --git a/src/utils/lotto.utils.js b/src/utils/lotto.utils.js index 70cdc35b6..8ce492841 100644 --- a/src/utils/lotto.utils.js +++ b/src/utils/lotto.utils.js @@ -1,7 +1,6 @@ -import {INSTRUCTION, LOTTO} from "../constants/constants.js"; -import {Random, Console} from '@woowacourse/mission-utils'; -import Lotto from "../Lotto.js"; -import {IOHandler} from "./IOHandler.js"; +import {LOTTO} from "../constants/constants.js"; +import {Random} from '@woowacourse/mission-utils'; +import Lotto from "../Models/Lotto.js"; export const lottoUtils = { generateNLottos(n) { @@ -12,14 +11,6 @@ export const lottoUtils = { }) return lottos; }, - getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) { - let lottoResult = Array(8).fill(0); - lottos.forEach((lotto) => { - const matchNumber = lotto.getLottoResult(winningNumbers, bonusNumber); - lottoResult[matchNumber]++ - }) - return lottoResult; - }, getPrize(matchNumber) { switch (matchNumber) { case 7: @@ -41,27 +32,6 @@ export const lottoUtils = { .replace(/(.{3})(?=.)/g, `$1${separator}`) .split("").reverse().join(""); }, - calculateProfitRate(profit, purchasePrice) { - const profitRate = profit / purchasePrice * 100; - return Math.round(profitRate * 100) / 100; - }, - calculateTotalPrize(lottoResult){ - let totalPrize = 0; - lottoResult.forEach((amount, index) => { - totalPrize += amount * lottoUtils.getPrize(index); - }) - return totalPrize; - }, - checkResult(lottos, winningNumbers, bonusNumber, purchasePrice) { - const lottoResult = lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber); - - let totalPrize = lottoUtils.calculateTotalPrize(lottoResult); - IOHandler.printWinningStatisticsAll(lottoResult); - - const profitRate = lottoUtils.calculateProfitRate(totalPrize, purchasePrice); - const formattedRate = `${profitRate.toFixed(1)}%`; - Console.print(INSTRUCTION.PRINT_PROFIT_RATE(formattedRate)) - } } \ No newline at end of file From 17ee04d238f5469a06f698169dc2afd5dcca3ed7 Mon Sep 17 00:00:00 2001 From: YujeongLEE Date: Mon, 4 Nov 2024 23:44:06 +0900 Subject: [PATCH 31/31] =?UTF-8?q?test=20:=20lottoGame=20class=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoTest.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 648a87b1f..1ad91be01 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,7 +1,8 @@ -import Lotto from "../src/Lotto"; +import Lotto from "../src/Models/Lotto.js"; import {MissionUtils, Console} from "@woowacourse/mission-utils"; import {lottoUtils} from "../src/utils/lotto.utils.js"; import {ERROR_CODE, LOTTO} from "../src/constants/constants.js"; +import LottoGame from "../src/Models/LottoGame.js"; const getLogSpy = () => { const logSpy = jest.spyOn(MissionUtils.Console, "print"); @@ -62,10 +63,11 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [1, 3, 5, 8, 11, 38] const bonusNumber = 39 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); const lottoResult = [0, 0, 0, 0, 0, 0, 0, 1] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); }); @@ -76,10 +78,12 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [1, 3, 5, 8, 11, 38] const bonusNumber = 39 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + const lottoResult = [0, 0, 0, 0, 0, 0, 1, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); }); @@ -90,10 +94,12 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [1, 3, 5, 8, 11, 38] const bonusNumber = 40 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + const lottoResult = [0, 0, 0, 0, 0, 1, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); }); @@ -104,10 +110,12 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [1, 3, 5, 8, 14, 38] const bonusNumber = 40 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + const lottoResult = [0, 0, 0, 0, 1, 0, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); }); @@ -118,10 +126,12 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [1, 3, 5, 10, 14, 38] const bonusNumber = 40 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + const lottoResult = [0, 0, 0, 1, 0, 0, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); }); @@ -132,10 +142,12 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [2, 4, 6, 10, 14, 38] const bonusNumber = 40 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + const lottoResult = [1, 0, 0, 0, 0, 0, 0, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); }); @@ -147,10 +159,12 @@ describe("당첨 케이스 테스트", () => { const winningNumbers = [1, 3, 5, 8, 11, 39] const bonusNumber = 40 + const lottoGame = new LottoGame(winningNumbers, bonusNumber, lottos); + const lottoResult = [0, 0, 0, 0, 1, 0, 1, 0] //배열의 index는 일치하는 갯수를 위미( but, 6은 2등(5개 + 보너스)/ 7은 1등) expect( - lottoUtils.getLottoMatchResultArray(lottos, winningNumbers, bonusNumber) + lottoGame.getLottoMatchResultArray() ).toStrictEqual(lottoResult); });