-
Notifications
You must be signed in to change notification settings - Fork 168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1단계 - 콘솔 기반 로또 게임] 초코(강다빈) 미션 제출합니다. #290
Changes from 35 commits
9bb5a9e
656df6c
85feae0
a1f4355
e8c8231
812e848
8010ff6
725f690
916057e
d90568f
6e11789
f689337
3885553
41e3459
be26777
e7aac4b
f9d2c32
03ee900
993ff65
7cf4395
6804840
ea9485a
a1eb586
21e5888
ff23a3a
e1572cf
de926bc
788cbf0
cb70b91
184aacf
ed22aa4
27c944d
1b86786
b573410
c681e4a
dfe9bb0
e7b65d9
979af80
f501d39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,79 @@ | ||
{ | ||
"rules": {}, | ||
"env": { | ||
"es6": true, | ||
"node": true | ||
"node": true, | ||
"jest": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": "latest" | ||
"ecmaVersion": "latest", | ||
"sourceType": "module" | ||
}, | ||
"extends": ["eslint:recommended", "plugin:prettier/recommended"] | ||
"extends": [ | ||
"airbnb", | ||
"airbnb/hooks", | ||
"prettier", | ||
"plugin:jest/recommended", | ||
"plugin:prettier/recommended", | ||
"plugin:import/errors", | ||
"plugin:import/warnings", | ||
"eslint:recommended" | ||
], | ||
"plugins": ["import", "jest", "prettier"], | ||
"rules": { | ||
"prettier/prettier": [ | ||
"error", | ||
{ | ||
"endOfLine": "auto", | ||
"printWidth": 100 | ||
} | ||
], | ||
"prefer-const": [ | ||
"error", | ||
{ | ||
"destructuring": "any", | ||
"ignoreReadBeforeAssign": false | ||
} | ||
], | ||
"max-depth": ["error", { "max": 2 }], | ||
"max-params": ["error", { "max": 2 }], | ||
"class-methods-use-this": "off", | ||
"max-lines-per-function": ["error", { "max": 10 }], | ||
"import/extensions": ["error", { "js": "ignorePackages" }], | ||
"import/order": [ | ||
"error", | ||
{ | ||
"groups": [ | ||
"builtin", | ||
"external", | ||
"internal", | ||
["parent", "sibling"], | ||
"index", | ||
"object", | ||
"type", | ||
"unknown" | ||
], | ||
"pathGroups": [ | ||
{ | ||
"pattern": "next", | ||
"group": "builtin", | ||
"position": "before" | ||
}, | ||
{ | ||
"pattern": "@/core/**", | ||
"group": "unknown" | ||
}, | ||
{ | ||
"pattern": "**/*.css.ts", | ||
"group": "unknown", | ||
"position": "after" | ||
} | ||
], | ||
"newlines-between": "always", | ||
"alphabetize": { | ||
"order": "asc", | ||
"caseInsensitive": true | ||
} | ||
} | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
{ | ||
"printWidth": 100, | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "none", | ||
"bracketSpacing": true, | ||
"arrowParens": "always", | ||
"proseWrap": "never", | ||
"endOfLine": "auto" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,61 @@ | ||
<h1 align="middle">🎱</h1> | ||
<h2 align="middle">level1 - 행운의 로또</h2> | ||
<p align="middle">자바스크립트로 구현 하는 로또 어플리케이션</p> | ||
|
||
### 실행 | ||
|
||
```bash | ||
npm run start-step1 | ||
``` | ||
|
||
### 테스트 | ||
|
||
```bash | ||
npm run test | ||
``` | ||
|
||
### 실행 화면 | ||
|
||
<img src="./lotto-step1.png" alt="실행화면" width="450"> | ||
|
||
### 테스트 실행 화면 | ||
|
||
<img src="./lotto-step1-test.png" alt="테스트 실행화면" width="300"> | ||
|
||
### 도메인 로직 | ||
|
||
### 파일 구조 | ||
|
||
``` | ||
📦src | ||
┣ 📂constant : 상수 관리 | ||
┃ ┣ 📜Messages.js : 입력, 입력힌트, 출력, 에러 메세지 관리 | ||
┃ ┗ 📜Options.js : 로또 가격, 로또 범위 최소, 최대 숫자, 상금, 게임 결과 등 관리 | ||
┣ 📂controller | ||
┃ ┗ 📜LottoController.js : 로또 게임 진행 | ||
┣ 📂domain | ||
┃ ┣ 📜Lotto.js | ||
┃ ┗ 📜LottoMachine.js | ||
┣ 📂util | ||
┃ ┣ 📂random | ||
┃ ┃ ┗ 📜Random.js | ||
┃ ┣ 📂readLine | ||
┃ ┃ ┗ 📜readLineAsync.js | ||
┃ ┗ 📂validation | ||
┃ ┃ ┣ 📜LottoNumbersValidator.js | ||
┃ ┃ ┣ 📜PurchaseAmountValidator.js | ||
┃ ┃ ┗ 📜Validation.js | ||
┣ 📂view | ||
┃ ┣ 📜InputView.js | ||
┃ ┗ 📜OutputView.js | ||
┣ 📜App.js | ||
┣ 📜step1-index.js | ||
┗ 📜step2-index.js | ||
|
||
📦__tests__ | ||
┣ 📜.gitkeep | ||
┣ 📜Lotto.test.js | ||
┣ 📜LottoMachine.test.js | ||
┗ 📜Random.test.js | ||
|
||
``` |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자세한 테스트 케이스 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 유일하게 느낀..ㅎ TDD의 장점은 바로 테스트 케이스 작성인데요, 기능 요구사항을 기반으로 테스트 케이스를 먼저 작성하다 보니 기존의 방법(production code 작성 후 test code 작성)보다는 자세하게 작성할 수 있었던 것 같습니다.! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TDD가 주는 또 하나의 장점은 빠른 피드백이에요~ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
import { VARIABLE_ALIAS, ERROR_MESSAGES } from '../src/constant/Messages.js'; | ||
import OPTIONS from '../src/constant/Options.js'; | ||
import Lotto from '../src/domain/Lotto.js'; | ||
|
||
describe('Lotto 단위테스트', () => { | ||
describe('로또 발행 테스트', () => { | ||
test.each([[[1, 2, 3, 4, 5]], [[1, 2, 3, 4, 5, 6, 7]]])( | ||
'로또 번호(%s)가 6개가 아니면 에러를 발생한다.', | ||
(numbers) => { | ||
expect(() => new Lotto(numbers)).toThrow( | ||
`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.hasNotLength(VARIABLE_ALIAS.lottoNumbers, OPTIONS.LOTTO.combination)}` | ||
); | ||
} | ||
); | ||
|
||
test.each([[['a', 2, 3, 4, 5, 6]], [[1.1, 2, 3, 4, 5, 6]]])( | ||
'로또 번호(%s) 중 정수 이외의 값이 있다면 에러를 발생한다.', | ||
(numbers) => { | ||
expect(() => new Lotto(numbers)).toThrow(); | ||
} | ||
); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 46]], [[0, 1, 2, 3, 4, 5]]])( | ||
'로또 번호(%s)의 범위가 1부터 45 사이가 아니라면 에러를 발생한다.', | ||
(numbers) => { | ||
expect(() => new Lotto(numbers)).toThrow(); | ||
} | ||
); | ||
|
||
test('로또 번호에 중복되는 수가 있다면 에러를 발생한다.', () => { | ||
const numbers = [1, 1, 2, 3, 4, 5]; | ||
|
||
expect(() => new Lotto(numbers)).toThrow(); | ||
}); | ||
|
||
test.each([[[3, 2, 4, 5, 6, 1]], [[13, 12, 14, 15, 16, 11]]])( | ||
'로또 번호는 오름차순으로 정렬된다.', | ||
(numbers) => { | ||
const lotto = new Lotto(numbers); | ||
|
||
expect(lotto.getNumbers()).toStrictEqual(numbers.sort()); | ||
} | ||
); | ||
}); | ||
|
||
describe('로또 당첨 여부 판단 테스트', () => { | ||
let lottoNumbers; | ||
let lotto; | ||
|
||
beforeAll(() => { | ||
lottoNumbers = [1, 2, 3, 4, 5, 6]; | ||
lotto = new Lotto(lottoNumbers); | ||
}); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 6], 7, 1]])( | ||
'6개의 번호가 일치하면 1등을 반환한다.', | ||
(winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
} | ||
); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 45], 6, 2]])( | ||
'5개의 번호 + 보너스 번호가 일치하면 2등을 반환한다.', | ||
(winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
} | ||
); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 45], 7, 3]])( | ||
'5개의 번호가 일치하고 보너스 번호가 다르면 3등을 반환한다.', | ||
(winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
} | ||
); | ||
|
||
test.each([ | ||
[[1, 2, 3, 4, 44, 45], 6, 4], | ||
[[1, 2, 3, 4, 44, 45], 7, 4] | ||
])('4개의 번호가 일치하면 4등을 반환한다.', (winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
}); | ||
|
||
test.each([ | ||
[[1, 2, 3, 43, 44, 45], 6, 5], | ||
[[1, 2, 3, 43, 44, 45], 7, 5] | ||
])('3개의 번호가 일치하면 5등을 반환한다.', (winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
}); | ||
|
||
test.each([ | ||
[[1, 2, 42, 43, 44, 45], 6, 6], | ||
[[1, 2, 42, 43, 44, 45], 7, 6], | ||
[[1, 41, 42, 43, 44, 45], 6, 6], | ||
[[1, 41, 42, 43, 44, 45], 7, 6], | ||
[[40, 41, 42, 43, 44, 45], 6, 6], | ||
[[40, 41, 42, 43, 44, 45], 7, 6] | ||
])('3개 미만의 번호가 일치하면 다음기회에^^.', (winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
import { ERROR_MESSAGES, VARIABLE_ALIAS } from '../src/constant/Messages.js'; | ||
import OPTIONS from '../src/constant/Options.js'; | ||
import Lotto from '../src/domain/Lotto.js'; | ||
import LottoMachine from '../src/domain/LottoMachine'; | ||
|
||
// TODO: calculateIssueQuantity 함수를 private 함수로 변경 | ||
describe('LottoMachine 단위테스트', () => { | ||
let lottoMachine; | ||
|
||
beforeEach(() => { | ||
lottoMachine = new LottoMachine(); | ||
}); | ||
|
||
describe('구입 금액에 따른 구입 가능 수량 계산 테스트', () => { | ||
test.each(['a', 1.1])( | ||
'구입 금액(%s)이 정수가 아니라면 에러를 발생시킨다.', | ||
(purchaseAmount) => { | ||
expect(() => lottoMachine.calculateIssueQuantity(purchaseAmount)).toThrow( | ||
`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.isNotInteger(VARIABLE_ALIAS.purchaseAmount)}` | ||
); | ||
} | ||
); | ||
|
||
test('구입 금액이 1000 미만이라면 에러를 발생시킨다.', () => { | ||
const purchaseAmount = 999; | ||
|
||
expect(() => lottoMachine.calculateIssueQuantity(purchaseAmount)).toThrow( | ||
`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.isNotAtLeast(VARIABLE_ALIAS.purchaseAmount, OPTIONS.LOTTO.price)}` | ||
); | ||
}); | ||
|
||
test.each([ | ||
[1000, 1], | ||
[1999, 1], | ||
[2000, 2] | ||
])('구입 금액(%s)에 따른 발행 수량(%s)을 계산하여 반환한다.', (purchaseAmount, quantity) => { | ||
expect(lottoMachine.calculateIssueQuantity(purchaseAmount)).toBe(quantity); | ||
}); | ||
}); | ||
|
||
describe('지정 수량 만큼 로또 발행 테스트', () => { | ||
test('지정 수량만큼 로또를 발행한다.', () => { | ||
const issueQuantity = 1; | ||
const lottos = lottoMachine.issueLottos(issueQuantity); | ||
|
||
expect(Array.isArray(lottos) && lottos.length === issueQuantity).toBe(true); | ||
}); | ||
|
||
test('로또 객체의 배열을 반환한다.', () => { | ||
const issueQuantity = 1; | ||
const lottos = lottoMachine.issueLottos(issueQuantity); | ||
|
||
expect(lottos.every((lotto) => lotto instanceof Lotto)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('로또 결과 분석 테스트', () => { | ||
// TEST: 1. 등수 별 당첨 개수 | ||
test('구입한 로또들의 등수를 분석하여 반환한다.', () => { | ||
const numbersList = [ | ||
[1, 2, 3, 4, 5, 6], | ||
[1, 2, 3, 4, 5, 7], | ||
[1, 2, 3, 4, 5, 45], | ||
[1, 2, 3, 4, 44, 45], | ||
[1, 2, 3, 43, 44, 45], | ||
[1, 2, 42, 43, 44, 45] | ||
]; | ||
const lottos = numbersList.map((numbers) => new Lotto(numbers)); | ||
const winningNumbers = [1, 2, 3, 4, 5, 6]; | ||
const bonusNumber = 7; | ||
|
||
const winningResult = { 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1 }; | ||
|
||
expect(lottoMachine.determineLottoRanks(lottos, winningNumbers, bonusNumber)).toStrictEqual( | ||
winningResult | ||
); | ||
}); | ||
|
||
// TEST: 2. 수익률 계산 | ||
test.each([[{ 1: 0, 2: 0, 3: 0, 4: 0, 5: 1, 6: 7 }, 62.5]])( | ||
'로또 결과에 따른 수익률을 반환한다.', | ||
(winningResult, profitRate) => { | ||
expect(lottoMachine.calculateProfitRate(winningResult)).toBe(profitRate); | ||
} | ||
); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
import Random from '../src/util/random/Random.js'; | ||
|
||
describe('랜덤 테스트', () => { | ||
test('특정 범위 내에서 랜덤 값 1개를 반환한다.', () => { | ||
const min = 1; | ||
const max = 10; | ||
const testCount = 10; | ||
|
||
const testSuites = Array(testCount) | ||
.fill() | ||
.map(() => Random.randomPickNumber(min, max)); | ||
|
||
expect(testSuites.every((testSuite) => min <= testSuite && testSuite <= max)).toBe(true); | ||
}); | ||
|
||
test('특정 범위 내에서 중복되지 않은 조합을 반환한다.', () => { | ||
const min = 1; | ||
const max = 45; | ||
const count = 6; | ||
const testCount = 5; | ||
|
||
const testSuites = Array(testCount) | ||
.fill() | ||
.map(() => Random.pickCombination(min, max, count)); | ||
|
||
expect( | ||
testSuites.every((testSuite) => { | ||
return testSuite.length === new Set(testSuite).size && testSuite.length === count; | ||
}) | ||
).toBe(true); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 import order까지 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위의 두 가지 이유로 eslint 설정을 다시 해보고자 합니다!