Skip to content
Open

Sju #183

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,263 @@
# javascript-lotto-precourse

# 로또

## 구현할 기능 목록

### 1. 구입 금액 입력

- [ ] `Console.readLineAsync()`를 사용해 로또 구입 금액을 입력받는다.
- [ ] 입력값을 숫자로 변환한다.
- [ ] 구입 가능한 로또 수량을 계산한다.
- [ ] 로또 한 장의 가격은 `1,000원`으로 처리한다.

### 2. 구입 금액 검증

- [ ] 입력값이 비어 있지 않은지 확인한다.
- [ ] 입력값이 숫자로만 이루어져 있는지 확인한다.
- [ ] 입력값이 양의 정수인지 확인한다.
- [ ] 입력값이 `1,000원` 이상인지 확인한다.
- [ ] 입력값이 `1,000원` 단위로 나누어 떨어지는지 확인한다.
- [ ] 잘못된 입력일 경우 `[ERROR]`로 시작하는 `Error`를 발생시킨다.
- [ ] 에러 메시지를 출력한 뒤 구입 금액 입력 단계부터 다시 입력받는다.

### 3. 로또 발행

- [ ] 구입 금액을 기준으로 발행할 로또 수량을 계산한다.
- [ ] `Random.pickUniqueNumbersInRange(1, 45, 6)`을 사용해 로또 번호를 생성한다.
- [ ] 각 로또는 서로 중복되지 않는 6개의 번호를 가진다.
- [ ] 생성된 번호를 `Lotto` 객체로 생성한다.
- [ ] 구입한 수량만큼 Lotto 객체를 생성해 저장한다.

### 4. 발행 결과 출력

- [ ] 구매한 로또 수량을 출력한다.
- [ ] 각 로또의 번호를 오름차순으로 정렬해 출력한다.
- [ ] 출력 형식을 `[1, 2, 3, 4, 5, 6]` 형태로 맞춘다.

### 5. 당첨 번호 입력

- [ ] `Console.readLineAsync()`를 사용해 당첨 번호를 입력받는다.
- [ ] 쉼표(`,`)를 기준으로 당첨 번호를 분리한다.
- [ ] 분리된 값을 숫자로 변환한다.

### 6. 당첨 번호 검증

- [ ] 당첨 번호가 정확히 6개인지 확인한다.
- [ ] 각 번호가 숫자로만 이루어져 있는지 확인한다.
- [ ] 각 번호가 정수인지 확인한다.
- [ ] 각 번호가 `1~45` 범위인지 확인한다.
- [ ] 번호 사이에 중복이 없는지 확인한다.
- [ ] 빈 값이 포함되어 있지 않은지 확인한다.
- [ ] 잘못된 입력일 경우 `[ERROR]`로 시작하는 `Error`를 발생시킨다.
- [ ] 에러 메시지를 출력한 뒤 당첨 번호 입력 단계부터 다시 입력받는다.

### 7. 보너스 번호 입력

- [ ] `Console.readLineAsync()`를 사용해 보너스 번호를 입력받는다.
- [ ] 입력값을 숫자로 변환한다.

### 8. 보너스 번호 검증

- [ ] 보너스 번호가 숫자로만 이루어져 있는지 확인한다.
- [ ] 보너스 번호가 정수인지 확인한다.
- [ ] 보너스 번호가 `1~45` 범위인지 확인한다.
- [ ] 보너스 번호가 당첨 번호 6개와 중복되지 않는지 확인한다.
- [ ] 잘못된 입력일 경우 `[ERROR]`로 시작하는 `Error`를 발생시킨다.
- [ ] 에러 메시지를 출력한 뒤 보너스 번호 입력 단계부터 다시 입력받는다.

### 9. Lotto 객체 생성 및 검증

- [ ] 제공된 `Lotto` 클래스를 사용한다.
- [ ] `#numbers` 외의 인스턴스 필드를 추가하지 않는다.
- [ ] 로또 번호가 정확히 6개인지 검증한다.
- [ ] 로또 번호가 모두 `1~45` 범위인지 검증한다.
- [ ] 로또 번호에 중복이 없는지 검증한다.
- [ ] Lotto 내부 번호를 외부에서 필요한 방식으로 조회할 수 있도록 메서드를 구현한다.

### 10. 당첨 번호 비교

- [ ] 구매한 각 로또와 당첨 번호를 비교한다.
- [ ] 일치하는 번호의 개수를 계산한다.
- [ ] 5개가 일치하는 경우 보너스 번호 일치 여부를 추가로 확인한다.
- [ ] 각 Lotto의 당첨 등수를 판별한다.

### 11. 당첨 등수 판별

- [ ] 6개 일치 시 1등으로 처리한다.
- [ ] 5개 일치 + 보너스 번호 일치 시 2등으로 처리한다.
- [ ] 5개 일치 시 3등으로 처리한다.
- [ ] 4개 일치 시 4등으로 처리한다.
- [ ] 3개 일치 시 5등으로 처리한다.
- [ ] 2개 이하 일치 시 당첨되지 않은 것으로 처리한다.

### 12. 당첨 통계 계산

- [ ] 등수별 당첨 개수를 계산한다.
- [ ] 5등 당첨 개수를 저장한다.
- [ ] 4등 당첨 개수를 저장한다.
- [ ] 3등 당첨 개수를 저장한다.
- [ ] 2등 당첨 개수를 저장한다.
- [ ] 1등 당첨 개수를 저장한다.

### 13. 총 당첨 금액 계산

- [ ] 등수별 당첨 개수와 상금을 이용해 총 당첨 금액을 계산한다.
- [ ] 등수별 상금은 다음과 같이 적용한다.
- 5등: `5,000원`
- 4등: `50,000원`
- 3등: `1,500,000원`
- 2등: `30,000,000원`
- 1등: `2,000,000,000원`

### 14. 수익률 계산

- [ ] 총 당첨 금액과 구입 금액을 이용해 수익률을 계산한다.
- [ ] 수익률을 백분율로 변환한다.
- [ ] 소수점 둘째 자리에서 반올림해 소수점 첫째 자리까지 출력한다.
- [ ] 큰 수의 경우 천 단위 구분 쉼표를 적용한다.

### 15. 당첨 통계 출력

- [ ] `## 당첨 통계` 문구를 출력한다.
- [ ] 5등부터 1등 순서로 당첨 개수를 출력한다.
- [ ] 등수별 당첨 기준과 상금을 지정된 형식으로 출력한다.
- [ ] 총 수익률을 지정된 형식으로 출력한다.

---

## 예외 처리 목록

### 구입 금액 관련

- [ ] 빈 문자열 입력
- 예: `""`
- [ ] 공백만 입력
- 예: `" "`
- [ ] 숫자가 아닌 값 입력
- 예: `"abc"`
- [ ] 숫자와 문자가 섞인 입력
- 예: `"1000원"`, `"1a000"`
- [ ] 음수 입력
- 예: `"-1000"`
- [ ] 소수 입력
- 예: `"1000.5"`
- [ ] `0` 입력
- 예: `"0"`
- [ ] `1,000원`보다 작은 금액
- 예: `"500"`
- [ ] `1,000원` 단위로 나누어 떨어지지 않는 금액
- 예: `"1500"`, `"10500"`

### 당첨 번호 관련

- [ ] 빈 문자열 입력
- [ ] 번호 개수가 6개보다 적은 경우
- 예: `"1,2,3,4,5"`
- [ ] 번호 개수가 6개보다 많은 경우
- 예: `"1,2,3,4,5,6,7"`
- [ ] 쉼표가 연속으로 등장하는 경우
- 예: `"1,2,,4,5,6"`
- [ ] 쉼표로 시작하는 경우
- 예: `",1,2,3,4,5"`
- [ ] 쉼표로 끝나는 경우
- 예: `"1,2,3,4,5,"`
- [ ] 숫자가 아닌 값이 포함된 경우
- 예: `"1,2,3,a,5,6"`
- [ ] 음수가 포함된 경우
- 예: `"1,2,3,-4,5,6"`
- [ ] 소수가 포함된 경우
- 예: `"1,2,3,4.5,5,6"`
- [ ] `0`이 포함된 경우
- 예: `"0,1,2,3,4,5"`
- [ ] `45`를 초과하는 번호가 포함된 경우
- 예: `"1,2,3,4,5,46"`
- [ ] 중복 번호가 포함된 경우
- 예: `"1,2,3,4,5,5"`
- [ ] 번호 사이에 공백이 포함된 경우를 요구사항에 맞게 검증한다.
- 예: `"1, 2,3,4,5,6"`

### 보너스 번호 관련

- [ ] 빈 문자열 입력
- [ ] 숫자가 아닌 값 입력
- [ ] 음수 입력
- [ ] 소수 입력
- [ ] `0` 입력
- [ ] `45`를 초과하는 값 입력
- [ ] 당첨 번호와 중복되는 번호 입력
- 당첨 번호: `1,2,3,4,5,6`
- 보너스 번호: `6`

### Lotto 클래스 관련

- [ ] 번호 개수가 6개가 아닌 경우
- [ ] 번호가 `1~45` 범위를 벗어나는 경우
- [ ] 번호가 중복되는 경우

### 공통 예외 처리

- [ ] 모든 예외 메시지는 `[ERROR]`로 시작하도록 한다.
- [ ] 예외 발생 시 `process.exit()`을 사용하지 않는다.
- [ ] 예외 메시지를 출력한 뒤 해당 입력 단계부터 다시 입력받는다.
- [ ] 구입 금액 오류 시 구입 금액만 다시 입력받는다.
- [ ] 당첨 번호 오류 시 당첨 번호만 다시 입력받는다.
- [ ] 보너스 번호 오류 시 보너스 번호만 다시 입력받는다.

---

## 테스트할 주요 정상 케이스

- [ ] `1,000원` 구입 시 로또 1개 발행
- [ ] `8,000원` 구입 시 로또 8개 발행
- [ ] 발행된 각 로또가 6개의 번호를 가지는지 확인
- [ ] 발행된 각 로또 번호가 중복되지 않는지 확인
- [ ] 발행된 각 로또 번호가 `1~45` 범위인지 확인
- [ ] 출력되는 로또 번호가 오름차순인지 확인
- [ ] 6개 번호 일치 시 1등 판별
- [ ] 5개 번호 + 보너스 번호 일치 시 2등 판별
- [ ] 5개 번호 일치 시 3등 판별
- [ ] 4개 번호 일치 시 4등 판별
- [ ] 3개 번호 일치 시 5등 판별
- [ ] 2개 이하 번호 일치 시 미당첨 처리
- [ ] 여러 장의 로또에서 등수별 당첨 개수가 정확히 집계되는지 확인
- [ ] 수익률 계산이 올바른지 확인
- [ ] 수익률 반올림이 올바른지 확인

## 테스트할 주요 예외 케이스

- [ ] 구입 금액 `0`
- [ ] 구입 금액 `500`
- [ ] 구입 금액 `1500`
- [ ] 구입 금액 `-1000`
- [ ] 구입 금액 `abc`
- [ ] 당첨 번호 `1,2,3,4,5`
- [ ] 당첨 번호 `1,2,3,4,5,5`
- [ ] 당첨 번호 `0,1,2,3,4,5`
- [ ] 당첨 번호 `1,2,3,4,5,46`
- [ ] 당첨 번호 `1,2,,4,5,6`
- [ ] 보너스 번호가 당첨 번호와 중복되는 경우
- [ ] 보너스 번호 `0`
- [ ] 보너스 번호 `46`
- [ ] 보너스 번호 `abc`

---

## 완료 확인

- [ ] README에 작성한 모든 기능 구현
- [ ] README에 작성한 모든 예외 처리 구현
- [ ] 제공된 `Lotto` 클래스 제약사항 준수
- [ ] `#numbers` 외의 인스턴스 필드를 추가하지 않았는지 확인
- [ ] `Random.pickUniqueNumbersInRange()` 사용 확인
- [ ] `Console.readLineAsync()` 및 `Console.print()` 사용 확인
- [ ] 함수 또는 메서드 길이가 15라인을 초과하지 않는지 확인
- [ ] indent depth가 2를 초과하지 않는지 확인
- [ ] 삼항 연산자를 사용하지 않았는지 확인
- [ ] 불필요한 `else` 사용을 줄였는지 확인
- [ ] 각 함수 또는 메서드가 한 가지 책임만 담당하는지 확인
- [ ] UI 로직을 제외한 주요 기능에 단위 테스트 작성
- [ ] 제공된 테스트 케이스 통과
- [ ] 직접 작성한 정상/예외 테스트 케이스 통과
- [ ] `npm run start` 정상 동작 확인
- [ ] `npm test` 정상 동작 확인
57 changes: 57 additions & 0 deletions __tests__/LottoGameTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Lotto from '../src/Lotto.js';
import LottoGame from '../src/LottoGame.js';
import { PRIZE, RANK } from '../src/constants/Enum.js';

const winningLotto = new Lotto([1, 2, 3, 4, 5, 6]);

describe('LottoGame 테스트', () => {
test('두 로또의 일치하는 번호 개수를 계산한다.', () => {
expect(
LottoGame.countMatches(
[1, 2, 10, 11, 12, 13],
winningLotto.getNumbers(),
),
).toBe(2);
});

test('로또별 당첨 등수를 계산한다.', () => {
const myLottos = [
new Lotto([1, 2, 3, 4, 5, 6]), // 1등
new Lotto([1, 2, 3, 4, 5, 7]), // 2등: 보너스 7
new Lotto([1, 2, 3, 4, 5, 8]), // 3등
new Lotto([1, 2, 3, 4, 8, 9]), // 4등
new Lotto([1, 2, 3, 8, 9, 10]), // 5등
new Lotto([1, 2, 8, 9, 10, 11]), // 낙첨
];

const results = LottoGame.getResults(myLottos, winningLotto, 7);

expect(results).toEqual([1, 1, 1, 1, 1]);
});

test('등수별 당첨 금액을 합산한다.', () => {
const results = [1, 1, 1, 1, 1];

expect(LottoGame.getProfit(results)).toBe(
PRIZE.FIRST +
PRIZE.SECOND +
PRIZE.THIRD +
PRIZE.FOURTH +
PRIZE.FIFTH,
);
});

test('구매 금액 대비 수익률을 소수점 첫째 자리까지 계산한다.', () => {
const results = [0, 0, 0, 0, 1];

expect(LottoGame.getRateOfReturn(results, 8000)).toBe(62.5);
});

test('당첨 결과 배열의 등수 인덱스가 RANK 상수와 일치한다.', () => {
expect(RANK.FIRST).toBe(0);
expect(RANK.SECOND).toBe(1);
expect(RANK.THIRD).toBe(2);
expect(RANK.FOURTH).toBe(3);
expect(RANK.FIFTH).toBe(4);
});
});
19 changes: 19 additions & 0 deletions __tests__/LottoGeneratorTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Random } from '@woowacourse/mission-utils';
import LottoGenerator from '../src/LottoGenerator.js';

describe('LottoGenerator 테스트', () => {
afterEach(() => {
jest.restoreAllMocks();
});

test('구매 수량만큼 Lotto 객체를 생성한다.', () => {
jest.spyOn(Random, 'pickUniqueNumbersInRange')
.mockReturnValue([1, 2, 3, 4, 5, 6]);

const lottos = LottoGenerator.generate(3000);

expect(lottos).toHaveLength(3);
expect(lottos.every(lotto => lotto.getNumbers().length === 6)).toBe(true);
expect(Random.pickUniqueNumbersInRange).toHaveBeenCalledTimes(3);
});
});
15 changes: 15 additions & 0 deletions __tests__/LottoNumberParserTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import LottoNumberParser from '../src/parser/LottoNumberParser.js';

describe('LottoNumberParser 테스트', () => {
test('쉼표로 구분된 문자열을 숫자 배열로 변환한다.', () => {
expect(LottoNumberParser.parse('1, 2, 3, 4, 5, 6')).toEqual([1, 2, 3, 4, 5, 6]);
});

test('숫자가 아닌 값이 포함되면 예외가 발생한다.', () => {
expect(() => LottoNumberParser.parse('1,2,a,4,5,6')).toThrow('[ERROR]');
});

test('빈 값이 포함되면 예외가 발생한다.', () => {
expect(() => LottoNumberParser.parse('1,2,,4,5,6')).toThrow('[ERROR]');
});
});
Loading