Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e646949
docs(REAME.md): 요구사항 명세서 작성
NaturalSoda4552 Jan 10, 2026
929007a
feat(Lotto.js): Lotto 클래스 생성 및 구현
NaturalSoda4552 Jan 10, 2026
560e649
feat(LottoGameController.js): LottoGameController 클래스 생성 및 당첨번호 반복 입력…
NaturalSoda4552 Jan 10, 2026
acd427f
feat(LottoGame.js): LottoGame 클래스 생성 및 랜덤 번호의 로또 생성 후 출력 기능 구현
NaturalSoda4552 Jan 10, 2026
a1de711
feat(WinningLotto.js): WinningLotto 클래스 생성 및 당첨 번호 & 보너스 번호 반복 입력 기능 구현
NaturalSoda4552 Jan 10, 2026
ed54603
feat(LottoGame.js): 통계 생성 기능 및 출력 구현
NaturalSoda4552 Jan 10, 2026
b894b46
feat(validate.js): 유효성 검사 객체 구현 및 적용
NaturalSoda4552 Jan 10, 2026
56ab2bc
fix(App.js): 비동기로 컨트롤러를 실행하지 않아 test를 통과하지 못하는 버그 수정
NaturalSoda4552 Jan 10, 2026
dfa8276
feat(LottoTest.js): Lotto 클래스에 대한 테스트코드 작성
NaturalSoda4552 Jan 10, 2026
431943d
feat(LottoGameTest.js): LottoGame 클래스에 대한 테스트코드 작성
NaturalSoda4552 Jan 10, 2026
2fcb21f
feat(WinningLottoTest.js): WinningLotto 클래스에 대한 테스트코드 작성
NaturalSoda4552 Jan 10, 2026
06faf6a
feat(validateTest.js): Validate 객체의 함수들에 대한 테스트코드 작성 및 에러 문자열 동일하게 수정
NaturalSoda4552 Jan 10, 2026
ee95a1e
docs(README.md): 요구사항 명세서 불필요 내용 삭제 & 필요 기능에 따라 일부 수정 및 추가
NaturalSoda4552 Jan 10, 2026
2b94088
feat(validate.js): 당첨 번호에 대해 유효성 검사하는 기능 추가
NaturalSoda4552 Jan 10, 2026
8004493
refactor(errorMessages.js): 에러 메세지를 상수로 분리, 문자열 오타 수정 및 로또 규칙인 숫자들에 대…
NaturalSoda4552 Jan 10, 2026
b7a8b7c
refactor(Lotto.js, LottoTest.js): 로또 클래스와 로또 클래스 테스트코드 에러 메세지를 상수로 변경
NaturalSoda4552 Jan 10, 2026
d108ef4
refactor(validate.js, validateTest.js): Validate 객체 메서드들과 Validate 테스…
NaturalSoda4552 Jan 10, 2026
b598eaa
docs(README.md): 요구사항 명세서 업데이트 및 문자열 오타 수정
NaturalSoda4552 Jan 10, 2026
295ec4a
refactor(Lotto.js): Lotto 클래스 상수 리팩토링
NaturalSoda4552 Jan 10, 2026
0c181a3
refactor(LottoGame.js): LottoGame 클래스 상수 리팩토링
NaturalSoda4552 Jan 10, 2026
3474198
refactor(WinningLotto.js): WinningLotto 클래스 상수 리팩토링
NaturalSoda4552 Jan 10, 2026
e19383e
fix(LottoGame.js): LottoGame 클래스 내부 불필요 주석 제거
NaturalSoda4552 Jan 10, 2026
a683860
refactor(LottoGame.js): 컨트롤러에서 수행하던 Map 로직을 LottoGame 클래스 내부 메서드로 분리하…
NaturalSoda4552 Jan 10, 2026
c9b02db
fix: 불필요한 import문 제거 및 import 경로 버그 수정
NaturalSoda4552 Jan 10, 2026
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
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,86 @@
# javascript-planetlotto-precourse

## 📋 요구사항 명세서

- 프로그램 실행 시 `"구입금액을 입력해 주세요."` 메세지를 출력한다.
- 사용자로부터 `로또 구입 금액`을 입력받는다. `단위는 1,000원`이며 나누어 떨어지지 않는 경우 예외처리한다.
- 500원 단위로 자른 개수인 n에 대해 `"n개를 구매했습니다."` 메세지를 출력한다.
- 이후 `"당첨 번호를 입력해 주세요."` 메세지를 출력한 후, 사용자로부터 `당첨 번호`를 입력받는다. 당첨 번호는 `쉼표(,)`를 기준으로 구분한다.
- 이후 `"보너스 번호를 입력해 주세요."` 메세지를 출력한 후, 사용자로부터 `보너스 번호`를 입력받는다.
- 입력이 완료되면 `"당첨통계"` 메세지와 `"---"`를 출력한 후, `당첨 내역`을 출력한다.
- 당첨 내역은 2개~5개 일치 항목을 순서대로 다음 형식에 맞춰 출력한다. (n은 실제 당첨된 개수)
- 5개 번호 일치 (100,000,000원) - n개
- 4개 번호, 보너스 번호 일치 (10,000,000원) - n개
- 4개 번호 일치 (1,500,000원) - n개
- 3개 번호 일치, 보너스 번호 일치 (500,000원) - n개
- 2개 번호 일치, 보너스 번호 일치 (5,000원) - n개
- 0개 번호 일치 (0원) - n개
- 예외 상황 시 `[ERROR]`로 시작하는 에러 문구를 출력한 후, 해당 지점부터 다시 입력을 받는다.

## ⚙️ 구현할 기능 목록

### LottoGameController.js

#### 1️⃣ 행성로또 게임 진행 관리 기능

- [x] 사용자가 입력하는 값들은 에러 발생 시 다시 반복해서 입력받는다.

### Lotto.js

#### 1️⃣ 로또 번호 유효성 검사 기능

- [x] 로또 번호 개수가 5개가 아니면 오류를 발생시킨다.
- [x] 로또 번호가 정수가 아닐 때 오류를 발생시킨다.
- [x] 로또 번호가 중복될 시 오류를 발생시킨다.
- [x] 로또 번호는 1~30 사이의 숫자가 아닐 시 오류를 발생시킨다.

#### 2️⃣ 로또 번호 및 문자열 반환 기능

- [x] 로또 번호 배열을 반환한다.
- [x] 로또 번호를 정해진 형태의 문자열로 반환한다.

### LottoGame.js

#### 1️⃣ 로또 배열 생성 기능

- [x] 로또 구입 금액에 따른 로또 개수만큼 로또 객체을 만들어 배열로 반환한다.

#### 2️⃣ 로또 객체 생성 기능

- [x] 하나의 로또 객체를 만들어 반환한다.

#### 3️⃣ 통계 생성 기능

- [x] 로또 배열의 모든 로또에 대해 등수를 계산해서 객체로 반환한다.

#### 4️⃣ 로또 배열 반환 기능

- [x] 로또 배열을 반환한다.

### WinningLotto.js

#### 1️⃣ 등수 확인 기능

- [x] 당첨 번호와 비교하여 등수를 계산해서 반환한다.

#### 2️⃣ 등수 추출 기능

- [x] 당첨 번호와 겹치는 번호 수와 보너스 여부를 받아 등수를 반환한다.

### 🚫 예외 처리 기능

- [x] 당첨 번호를 입력할 때 콤마(,) 사이의 공백은 무시한다.
- [x] 예외 상황 시 `[ERROR]`로 시작하는 에러 문구를 출력한 후, 해당 지점부터 다시 입력을 받는다.

#### ❌ [ERROR] 처리

- [x] 구입 금액은 500원 단위의 숫자여야 한다.

- [x] 당첨 번호는 5개여야 한다.
- [x] 당첨 번호는 모두 정수여야 한다.
- [x] 당첨 번호는 중복될 수 없다.
- [x] 당첨 번호는 모두 1~30 사이의 숫자여야 한다.

- [x] 보너스 번호는 정수여야 한다.
- [x] 보너스 번호는 1~30 사이의 숫자여야 한다.
- [x] 보너스 번호는 당첨 번호와 중복될 수 없다.
57 changes: 57 additions & 0 deletions __tests__/model/LottoGameTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { MissionUtils } from '@woowacourse/mission-utils';

import LottoGame from '../../src/model/LottoGame.js';
import WinningLotto from '../../src/model/WinningLotto.js';
import Lotto from '../../src/model/Lotto.js';
import { RANK } from '../../src/constants/lottoConstants.js';

const mockRandoms = (numbers) => {
MissionUtils.Random.pickUniqueNumbersInRange = jest.fn();
numbers.reduce((acc, number) => {
return acc.mockReturnValueOnce(number);
}, MissionUtils.Random.pickUniqueNumbersInRange);
};

describe('LottoGame 클래스 테스트', () => {
beforeEach(() => {
jest.restoreAllMocks();
});

test('구매 금액에 해당하는 개수만큼 로또를 생성해야 한다.', () => {
const purchaseAmount = 3000;
const game = new LottoGame(purchaseAmount);
const lottos = game.getLottos();

expect(lottos.length).toBe(6);
});

test('createWinningStatistics는 올바른 당첨 통계를 생성해야 한다.', () => {
// given
const mockNumbers = [
[1, 2, 3, 4, 5], // 1등 (5개 일치)
[1, 2, 3, 4, 6], // 2등 (4개 일치 + 보너스 번호 일치 O)
[1, 2, 3, 4, 7], // 3등 (4개 일치 + 보너스 번호 일치 X)
];
mockRandoms(mockNumbers);

const purchaseAmount = 1500;
const lottoGame = new LottoGame(purchaseAmount);

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

// when
const winningStatistics = lottoGame.createWinningStatistics(winningLotto);

// then
expect(winningStatistics[RANK.FIRST]).toBe(1);
expect(winningStatistics[RANK.SECOND]).toBe(1);
expect(winningStatistics[RANK.THIRD]).toBe(1);
expect(winningStatistics[RANK.FOURTH]).toBe(0);
expect(winningStatistics[RANK.FIFTH]).toBe(0);
});
});
61 changes: 61 additions & 0 deletions __tests__/model/LottoTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import ERROR_MESSAGES from '../../src/constants/errorMessages.js';
import Lotto from '../../src/model/Lotto.js';

describe('Lotto 클래스 테스트', () => {
describe('로또 번호 유효성 검사', () => {
test.each([
{
description: '번호가 5개가 아닐 때 (6개)',
input: [1, 2, 3, 4, 5, 6],
expectedError: ERROR_MESSAGES.INVALID_NUMBERS_COUNT,
},
{
description: '번호가 5개가 아닐 때 (4개)',
input: [1, 2, 3, 4],
expectedError: ERROR_MESSAGES.INVALID_NUMBERS_COUNT,
},
{
description: '번호에 중복된 숫자가 있을 때',
input: [1, 2, 3, 4, 4],
expectedError: ERROR_MESSAGES.DUPLICATE_NUMBERS,
},
{
description: '번호에 문자가 있을 때',
input: [1, 2, 3, 4, 'a'],
expectedError: ERROR_MESSAGES.NUMBERS_MUST_BE_INTEGER,
},
{
description: '번호에 실수가 있을 때',
input: [1, 2, 3, 4, 5.5],
expectedError: ERROR_MESSAGES.NUMBERS_MUST_BE_INTEGER,
},
{
description: '번호가 1~30 범위를 벗어날 때 (0)',
input: [0, 1, 2, 3, 4],
expectedError: ERROR_MESSAGES.OUT_OF_RANGE_NUMBERS,
},

[
'번호가 1~45 범위를 벗어날 때 (31)',
[1, 2, 3, 4, 31],
ERROR_MESSAGES.OUT_OF_RANGE_NUMBERS,
],
])('%s, 예외가 발생해야 한다.', ({ input, expectedError }) => {
expect(() => new Lotto(input)).toThrow(expectedError);
});
});

describe('기능 테스트', () => {
test('Lotto 객체는 번호를 오름차순으로 정렬하여 저장한다.', () => {
const numbers = [5, 4, 3, 2, 1];
const lotto = new Lotto(numbers);
expect(lotto.getNumbers()).toEqual([1, 2, 3, 4, 5]);
});

test("toString 메서드는 '[1, 2, 3, 4, 5]' 형식의 문자열을 반환한다.", () => {
const numbers = [1, 2, 3, 4, 5];
const lotto = new Lotto(numbers);
expect(lotto.toString()).toEqual('[1, 2, 3, 4, 5]');
});
});
});
24 changes: 24 additions & 0 deletions __tests__/model/WinningLottoTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Lotto from '../../src/model/Lotto.js';
import WinningLotto from '../../src/model/WinningLotto.js';
import { RANK } from '../../src/constants/lottoConstants.js';

describe('WinningLotto 클래스 테스트', () => {
const winningNumbers = [1, 2, 3, 4, 5];
const bonusNumber = 6;
const winningLotto = new WinningLotto(new Lotto(winningNumbers), bonusNumber);

test.each([
['1등(5개 일치)', new Lotto([1, 2, 3, 4, 5]), RANK.FIRST],
['2등(4개 + 보너스 일치)', new Lotto([1, 2, 3, 4, 6]), RANK.SECOND],
['3등(4개 일치)', new Lotto([1, 2, 3, 4, 7]), RANK.THIRD],
['4등(3개 + 보너스 일치)', new Lotto([1, 2, 3, 6, 7]), RANK.FOURTH],
['5등(2개 + 보너스 일치)', new Lotto([1, 2, 6, 7, 8]), RANK.FIFTH],
['꽝(0개 일치)', new Lotto([7, 8, 9, 10, 11]), RANK.ZERO],
])(
'%s일때, 올바른 등수를 반환해야 한다.',
(description, userLotto, expectedRank) => {
const rank = winningLotto.match(userLotto);
expect(rank).toBe(expectedRank);
},
);
});
59 changes: 59 additions & 0 deletions __tests__/utils/validateTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Validate from '../../src/utils/validate.js';
import Lotto from '../../src/model/Lotto.js';
import ERROR_MESSAGES from '../../src/constants/errorMessages.js';

describe('Validate 테스트', () => {
describe('validatePurchaseAmount 테스트', () => {
test.each([
['숫자가 아닌 경우(abc)', 'abc'],
['500원 단위가 아닌 경우(750원)', 750],
['숫자와 문자가 섞여있는 경우(500j)', '500j'],
])('%s, 예외가 발생해야 한다.', (description, amount) => {
expect(() => Validate.validatePurchaseAmount(amount)).toThrow(
ERROR_MESSAGES.INVALID_PURCHASE_AMOUNT,
);
});

test('유효한 구매 금액인 경우, 예외가 발생하지 않는다.', () => {
expect(() => Validate.validatePurchaseAmount(3000)).not.toThrow();
});
});

describe('validateBonusNumber 테스트', () => {
test.each([
['숫자가 아닌 경우', 'a', ERROR_MESSAGES.BONUS_NUMBER_MUST_BE_INTEGER],
['범위를 벗어난 경우 (0)', 0, ERROR_MESSAGES.OUT_OF_RANGE_BONUS_NUMBER],
['범위를 벗어난 경우 (31)', 31, ERROR_MESSAGES.OUT_OF_RANGE_BONUS_NUMBER],
])('%s, 예외가 발생해야 한다.', (description, number, expectedError) => {
expect(() => Validate.validateBonusNumber(number)).toThrow(expectedError);
});

test('유효한 보너스 번호인 경우, 예외가 발생하지 않는다.', () => {
expect(() => Validate.validateBonusNumber(6)).not.toThrow();
});
});

describe('validateWinningNumbersAndBonusNumber 테스트', () => {
test('보너스 번호가 당첨 번호와 중복될 경우, 예외가 발생해야 한다.', () => {
const winningNumberLotto = new Lotto([1, 2, 3, 4, 5]);
const bonusNumber = 5;
expect(() =>
Validate.validateWinningNumbersAndBonusNumber(
winningNumberLotto,
bonusNumber,
),
).toThrow(ERROR_MESSAGES.DUPLICATE_WINNING_NUMBERS_AND_BONUS_NUMBER);
});

test('보너스 번호가 당첨 번호와 중복되지 않는 경우, 예외가 발생하지 않는다.', () => {
const winningNumberLotto = new Lotto([1, 2, 3, 4, 5]);
const bonusNumber = 6;
expect(() =>
Validate.validateWinningNumbersAndBonusNumber(
winningNumberLotto,
bonusNumber,
),
).not.toThrow();
});
});
});
7 changes: 6 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import LottoGameController from './controller/LottoGameController.js';

class App {
async run() {}
async run() {
const lottoGameController = new LottoGameController();
await lottoGameController.play();
}
}

export default App;
23 changes: 23 additions & 0 deletions src/constants/errorMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { LOTTO_RULES } from './lottoConstants.js';

const ERROR_MESSAGES = Object.freeze({
DUPLICATE_WINNING_NUMBERS_AND_BONUS_NUMBER:
'보너스 번호는 당첨 번호와 중복될 수 없습니다.',

INVALID_PURCHASE_AMOUNT: `구입 금액은 ${LOTTO_RULES.PRICE}원 단위의 숫자여야 합니다.`,

INVALID_NUMBERS_COUNT: `로또 번호는 ${LOTTO_RULES.NUMBER_COUNT}개여야 합니다.`,
NUMBERS_MUST_BE_INTEGER: '로또 번호는 모두 정수여야 합니다',
DUPLICATE_NUMBERS: '로또 번호는 중복될 수 없습니다.',
OUT_OF_RANGE_NUMBERS: `로또 번호는 ${LOTTO_RULES.MIN_NUMBER}부터 ${LOTTO_RULES.MAX_NUMBER} 사이의 숫자여야 합니다.`,

INVALID_WINNING_NUMBERS_COUNT: `당첨 번호는 ${LOTTO_RULES.NUMBER_COUNT}개여야 합니다.`,
WINNING_NUMBERS_MUST_BE_INTEGER: '당첨 번호는 모두 정수여야 합니다',
DUPLICATE_WINNING_NUMBERS: '당첨 번호는 중복될 수 없습니다.',
OUT_OF_RANGE_WINNING_NUMBERS: `당첨 번호는 ${LOTTO_RULES.MIN_NUMBER}부터 ${LOTTO_RULES.MAX_NUMBER} 사이의 숫자여야 합니다.`,

BONUS_NUMBER_MUST_BE_INTEGER: '보너스 번호는 정수여야 합니다.',
OUT_OF_RANGE_BONUS_NUMBER: `보너스 번호는 ${LOTTO_RULES.MIN_NUMBER}부터 ${LOTTO_RULES.MAX_NUMBER} 사이의 숫자여야 합니다.`,
});

export default ERROR_MESSAGES;
15 changes: 15 additions & 0 deletions src/constants/lottoConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const RANK = Object.freeze({
FIRST: 1,
SECOND: 2,
THIRD: 3,
FOURTH: 4,
FIFTH: 5,
ZERO: 0,
});

export const LOTTO_RULES = Object.freeze({
MIN_NUMBER: 1,
MAX_NUMBER: 30,
NUMBER_COUNT: 5,
PRICE: 500,
});
Loading