Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2d57066
docs(README.md): 기능 명세서 작성
Oct 29, 2025
00ca7bf
feat(App): 로또 구매할 금액 입력받기 구현
Nov 2, 2025
17da55d
feat(App): 로또 구입 개수 계산하는 함수 구현
Nov 2, 2025
de84078
feat(App): 로또 구입 내역 관련 기능 구현
Nov 2, 2025
9778058
feat(App): 로또 당첨번호 및 보너스번호 입력받기 구현
Nov 2, 2025
b899d5a
docs(README.md): 기능 명세서 수정
Nov 3, 2025
df434b2
feat(App): 당첨 번호 문자열 파싱 기능 구현 (쉼표 구분 및 숫자 변환)
Nov 3, 2025
8e3a45d
feat(App): 로또 번호와 당첨/보너스 번호를 비교해 일치 개수 및 보너스 여부 계산하는 메서드 구현
Nov 3, 2025
1c43834
feat(App): 전체 로또 결과를 계산해서 match3~match6 통계 카운트 기능 구현
Nov 3, 2025
64b9796
feat(App): 총 당첨금 계산 기능 구현
Nov 3, 2025
7202db2
feat(App): 수익률 계산 기능 구현
Nov 3, 2025
f4f3e83
feat(App): 당첨 통계 출력 기능 구현
Nov 3, 2025
b918ce9
docs(README.md): 기능 명세서 수정
Nov 3, 2025
2bfcbb6
feat(App): run 메서드에서 로또 게임 전체 흐름 구현
Nov 3, 2025
b092256
feat(Lotto): Lotto 클래스 이동 및 기능 구현
Nov 3, 2025
14f99d1
refactor(App): Lotto 클래스 사용하도록 리팩토링
Nov 3, 2025
0be7693
docs(README.md): 기능 명세서 수정
Nov 3, 2025
233440d
feat(App): 구매금액 입력 검증 기능 구현
Nov 3, 2025
3b13928
feat(App): 당첨번호 검증 기능 구현
Nov 3, 2025
777c003
docs(README.md): 기능 명세서 수정
Nov 3, 2025
80711b3
feat(App): 보너스 번호 검증 기능 구현
Nov 3, 2025
06675a6
feat(LottoResult): LottoResult 클래스 기본 구조 정의
Nov 3, 2025
f34ecf0
feat(LottoResult): 로또 통계 계산 기능 구현
Nov 3, 2025
274e147
feat(LottoResult): 총 상금계산 기능 구현
Nov 3, 2025
b88264c
feat(LottoResult): 수익률 계산 기능 구현
Nov 3, 2025
cd0e5ba
feat(LottoResult): 당첨 통계 조회 기능 구현
Nov 3, 2025
abf609c
fix(LottoResult): 클래스 내보내기 및 메서드 수정
Nov 3, 2025
634d81c
refactor(App): LottoResult 클래스 연동 및 App 클래스 로직 정리
Nov 3, 2025
8acb38f
docs(README.md): 기능 명세서 수정
Nov 3, 2025
3b1e214
feat(InputHandler): App에서 처리하던 입력 기능을 InputHandler 클래스로 이동
Nov 3, 2025
3d17645
refactor(App): InputHandler 클래스 연동 및 App 클래스 로직 정리
Nov 3, 2025
eb9f0c6
feat(OutputHandler): App에서 처리하던 출력 관련 기능을 OutputHandler 클래스로 이동
Nov 3, 2025
80859dd
refactor(App): OutputHandler 클래스 연동 및 App 클 래스 로직 정리
Nov 3, 2025
43a6c13
feat(LottoUtils): 로또 구매 갯수 계산을 위한 LottoUtils 클래스 구현
Nov 3, 2025
115eb36
refactor(App): LottoUtils 클래스 연동 및 App 클래스 내부 정리
Nov 3, 2025
23218c2
docs(README.md): 기능 명세서 수정
Nov 3, 2025
d67c27d
fix(InputHandler): 사용자가 입력값을 잘못 입력하였을 때, 프로그램 종료하지 않고 해당 지점부터 다시 입력받도…
Nov 3, 2025
f5cce0d
fix(OutputHandler): 주어진 요구사항 출력 형식에 맞게 수정
Nov 3, 2025
0776931
feat(Lotto): 로또 번호 중복 및 범위 검증 구현
Nov 3, 2025
0224251
fix(App): OutputHandler 형식에 맞게 코드 수정
Nov 3, 2025
d4a9b86
fix(LottoTest): 파일 경로 수정
Nov 3, 2025
a68565b
test(Lotto): 로또 번호 1~45 범위 벗어날 때 예외 발생 테스트 추가
Nov 3, 2025
f559029
test(Lotto): 로또 번호 정상 생성 및 오름차순 정렬 테스트 추가
Nov 3, 2025
70032b8
test(LottoUtils): 구매 금액에 따른 로또 개수 계산 테스트 추가
Nov 3, 2025
f05b571
test(LottoResult): LottoResult 단위 테스트 작성 및 당첨 통계/총 상금/수익률 검증
Nov 3, 2025
15604cc
refactor(App): App.js를 controller 폴더 내로 이동
Nov 3, 2025
d4568c5
refactor(ApplicationTest): 경로 수정
Nov 3, 2025
c1eb8c3
refactor(index.js): App.js 경로 수정
Nov 3, 2025
fd3af91
feat(constant): 상수 변수 선언
Nov 3, 2025
a922fa8
refactor(Lotto): 상수 적용
Nov 3, 2025
486d259
refactor(LottoResult): 상수 적용
Nov 3, 2025
26c1503
refactor(InputHandler): 상수 적용
Nov 3, 2025
79ff6f6
refactor(OutputHandler): 상수 적용
Nov 3, 2025
6b74efb
refactor: App.js 파일 이동 및 관련 파일 경로 수정
Nov 3, 2025
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
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# javascript-lotto-precourse

## 구현할 기능 목록

- 로또 구입 금액 입력받기

- 로또 구입 갯수 출력하기

- 로또 구입 내역 출력하기
- 발행한 로또 번호는 오름차순으로 정렬
- 줄바꿈을 통해 로또 1개씩 출력

- 당첨 번호 입력 받기
- 쉼표(,)를 기준으로 1~45 범위의 6개의 중복되지 않은 숫자를 입력받기

- 보너스 번호 입력 받기

- 당첨 통계 출력하기
- 당첨 번호를 구분자 쉼표(,)를 기준으로 구분
- 로또, 당첨 번호, 보너스 번호를 가지고 해당 로또가 맞힌 갯수와 보너스 번호를 맞힌 여부를 반환하는 메서드 (개별 로또 결과 계산)
- 모든 로또에 대해 위의 결과를 계산, 맞힌 갯수가 3,4,5,5+보너스,6일 경우 각각 카운트 저장
- 수익률 계산
- 당첨내역 출력하기
- 수익률 출력하기 (소수점 둘째자리에서 반올림)

- 리팩토링
- App에 있는 로또 관련 로직 로또 클래스에 옮기기
- App에 있는 입출력 관련 로직 view로 분리하기

- 에러 처리
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

애러처리 너무 상세한데요???

이거 복사해서 제 리드미에 몰래 넣고 싶은걸요

- 로또 클래스 관련
- 로또 번호 6개가 아닌 경우(이미 구현되어 있음)
- 로또 번호가 중복되는 경우
- 로또 번호가 1~45 범위를 벗어나는 경우
- 로또 구매 가격 입력 관련
- 입력받은 금액이 숫자가 아닌 경우
- 입력받은 금액이 1,000원 미만일 경우
- 입력받은 금액이 1,000원 단위가 아닌 경우
- 당첨 번호 관련
- 당첨 번호가 숫자가 아닌 경우
- 당첨 번호가 1~45 범위를 벗어나는 경우
- 당첨 번호에 중복된 숫자가 있는 경우
- 당첨 번호 개수가 6개가 아닌 경우
- 보너스 번호 관련
- 보너스 번호가 숫자가 아닌 경우
- 보너스 번호를 입력받을 때 1~45 범위를 벗어나는 경우 -> 에러 처리
- 보너스 번호를 입력받을 때 당첨 번호와 중복되는 경우 -> 에러 처리
50 changes: 25 additions & 25 deletions __tests__/ApplicationTest.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import App from "../src/App.js";
import { MissionUtils } from "@woowacourse/mission-utils";
import App from '../src/App.js';
import { MissionUtils } from '@woowacourse/mission-utils';

const mockQuestions = (inputs) => {
MissionUtils.Console.readLineAsync = jest.fn();
Expand All @@ -19,7 +19,7 @@ const mockRandoms = (numbers) => {
};

const getLogSpy = () => {
const logSpy = jest.spyOn(MissionUtils.Console, "print");
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
logSpy.mockClear();
return logSpy;
};
Expand All @@ -29,7 +29,7 @@ const runException = async (input) => {
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"];
const INPUT_NUMBERS_TO_END = ['1000', '1,2,3,4,5,6', '7'];

mockRandoms([RANDOM_NUMBERS_TO_END]);
mockQuestions([input, ...INPUT_NUMBERS_TO_END]);
Expand All @@ -39,15 +39,15 @@ const runException = async (input) => {
await app.run();

// then
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("[ERROR]"));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[ERROR]'));
};

describe("로또 테스트", () => {
describe('로또 테스트', () => {
beforeEach(() => {
jest.restoreAllMocks();
});

test("기능 테스트", async () => {
test('기능 테스트', async () => {
// given
const logSpy = getLogSpy();

Expand All @@ -61,37 +61,37 @@ describe("로또 테스트", () => {
[2, 13, 22, 32, 38, 45],
[1, 3, 5, 14, 22, 45],
]);
mockQuestions(["8000", "1,2,3,4,5,6", "7"]);
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%입니다.",
'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");
test('예외 테스트', async () => {
await runException('1000j');
});
});
42 changes: 42 additions & 0 deletions __tests__/LottoResultTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Lotto from '../src/lotto/Lotto.js';
import LottoResult from '../src/lotto/LottoResult.js';

describe('LottoResult 단위 테스트', () => {
let lottoResult;
beforeEach(() => {
lottoResult = new LottoResult();
});

test('당첨 통계 업데이트 및 총 상금 계산', () => {
const lottos = [
new Lotto([1, 2, 3, 4, 5, 6]), // match6
new Lotto([1, 2, 3, 4, 5, 7]), // match5 + bonus
new Lotto([1, 2, 3, 4, 8, 9]), // match4
new Lotto([1, 2, 3, 10, 11, 12]), // match3
new Lotto([13, 14, 15, 16, 17, 18]), // 0개
];

const winningNumbers = [1, 2, 3, 4, 5, 6];
const bonusNumber = 7;

lottoResult.updateWinningStatistics(lottos, winningNumbers, bonusNumber);

const winningRank = lottoResult.getWinningRank();
const totalPrize = lottoResult.getTotalPrize();
const profitRate = lottoResult.calculateProfitRate(5000);

// 맞은 개수 검증
expect(winningRank.match3).toBe(1);
expect(winningRank.match4).toBe(1);
expect(winningRank.match5).toBe(0);
expect(winningRank.match5AndBonus).toBe(1);
expect(winningRank.match6).toBe(1);

// 총 상금 검증
const expectedTotal = 5000 + 50000 + 30000000 + 2000000000;
expect(totalPrize).toBe(expectedTotal);

// 수익률 검증
expect(profitRate).toBe(((expectedTotal / 5000) * 100).toFixed(1));
});
});
25 changes: 19 additions & 6 deletions __tests__/LottoTest.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import Lotto from "../src/Lotto";
import Lotto from '../src/lotto/Lotto';

describe("로또 클래스 테스트", () => {
test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
describe('로또 클래스 테스트', () => {
test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 6, 7]);
}).toThrow("[ERROR]");
}).toThrow('[ERROR]');
});

// TODO: 테스트가 통과하도록 프로덕션 코드 구현
test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => {
test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 5]);
}).toThrow("[ERROR]");
}).toThrow('[ERROR]');
});

// TODO: 추가 기능 구현에 따른 테스트 코드 작성
test('로또 번호가 1~45 범위를 벗어나면 예외가 발생한다.', () => {
expect(() => {
new Lotto([0, 2, 3, 4, 5, 6]);
}).toThrow('[ERROR]');
expect(() => {
new Lotto([1, 2, 3, 4, 5, 46]);
}).toThrow('[ERROR]');
});

test('로또 번호가 정상적으로 생성되고 오름차순 정렬된다', () => {
const lotto = new Lotto([19, 3, 8, 21, 16, 31]);
expect(lotto.getNumbers()).toEqual([3, 8, 16, 19, 21, 31]);
});
});
8 changes: 8 additions & 0 deletions __tests__/LottoUtilsTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import LottoUtils from '../src/lotto/LottoUtils.js';

describe('LottoUtils 단위 테스트', () => {
test('구매 금액에 따라 로또 개수 계산', () => {
expect(LottoUtils.getLottoCount(1000)).toBe(1);
expect(LottoUtils.getLottoCount(8000)).toBe(8);
});
});
39 changes: 38 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
import Lotto from './lotto/Lotto.js';
import LottoResult from './lotto/LottoResult.js';
import LottoUtils from './lotto/LottoUtils.js';
import InputHandler from './view/InputHandler.js';
import OutputHandler from './view/OutputHandler.js';

class App {
async run() {}
async run() {
// 1. 구매 금액 입력
const purchaseInput = await InputHandler.readPurchaseAmount();

// 2. 로또 생성
const lottoCount = LottoUtils.getLottoCount(purchaseInput);
const lottos = Lotto.generateLottos(lottoCount);

// 3. 로또 결과 출력
OutputHandler.printLottoCount(lottoCount);
OutputHandler.printLotto(lottos);

// 4. 당첨 번호 입력
const winnerLottoNumbers = await InputHandler.readWinningNumbers();
const bonusLottoNumber =
await InputHandler.readBonusNumber(winnerLottoNumbers);

// 5. 당첨 통계 계산
const lottoResult = new LottoResult();
lottoResult.updateWinningStatistics(
lottos,
winnerLottoNumbers,
bonusLottoNumber
);

// 6. 통계 출력
const totalPrize = lottoResult.getTotalPrize();
const profitRate = lottoResult.calculateProfitRate(purchaseInput);
const winningRank = lottoResult.getWinningRank();

OutputHandler.printStatistics(profitRate, winningRank);
}
}

export default App;
18 changes: 0 additions & 18 deletions src/Lotto.js

This file was deleted.

51 changes: 51 additions & 0 deletions src/constants/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export const LOTTO = {
NUMBER_COUNT: 6,
MIN_NUMBER: 1,
MAX_NUMBER: 45,
MIN_PURCHASE_AMOUNT: 1000,
UNIT: 1000,
};

export const PRIZE = {
MATCH3: 5000,
MATCH4: 50000,
MATCH5: 1500000,
MATCH5_BONUS: 30000000,
MATCH6: 2000000000,
};

export const MESSAGES = {
PURCHASE_AMOUNT_PROMPT: '구매 금액을 입력해 주세요.\n',
WINNING_NUMBERS_PROMPT: '\n당첨 번호를 입력해 주세요.\n',
BONUS_NUMBER_PROMPT: '\n보너스 번호를 입력해 주세요.\n',
};

export const ERROR_MESSAGES = {
PURCHASE_NUMBER: '[ERROR] 구매 금액은 숫자로 입력해 주세요.',
MIN_PURCHASE: '[ERROR] 구매 금액은 1,000원 이상이어야 합니다.',
UNIT_PURCHASE: '[ERROR] 구매 금액은 1,000원 단위로 입력해 주세요.',
LOTTO_NUMBER_COUNT: '[ERROR] 로또 번호는 6개여야 합니다.',
LOTTO_NUMBER_DUPLICATE: '[ERROR] 로또 번호는 중복될 수 없습니다.',
LOTTO_NUMBER_RANGE: '[ERROR] 로또 번호는 1~45 범위여야 합니다.',
WINNING_NUMBER_FORMAT: '[ERROR] 당첨 번호는 숫자로 입력해 주세요.',
WINNING_NUMBER_RANGE: '[ERROR] 당첨 번호는 1 ~ 45 범위로 입력해 주세요.',
WINNING_NUMBER_COUNT: '[ERROR] 당첨 번호는 6개여야 합니다.',
WINNING_NUMBER_DUPLICATE: '[ERROR] 당첨 번호는 중복될 수 없습니다.',
BONUS_NUMBER_FORMAT: '[ERROR] 보너스 번호는 숫자로 입력해 주세요.',
BONUS_NUMBER_RANGE: '[ERROR] 보너스 번호는 1 ~ 45 범위로 입력해 주세요.',
BONUS_NUMBER_DUPLICATE:
'[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.',
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좀 과하긴 하지만 object.freez 사용과
[ERROR] -> ERROR_PREFIX :"[ERROR]" 또는
export default class CustomError extends Error{}
같은 방법모두 고려해보시면 좋을것 같아요!


export const OUTPUT_MESSAGES = {
PURCHASED_COUNT: (count) => `\n${count}개를 구매했습니다.`,
STATISTICS_HEADER: '\n당첨 통계',
STATISTICS_SEPARATOR: '---',
STATISTICS_MATCH3: (count) => `3개 일치 (5,000원) - ${count}개`,
STATISTICS_MATCH4: (count) => `4개 일치 (50,000원) - ${count}개`,
STATISTICS_MATCH5: (count) => `5개 일치 (1,500,000원) - ${count}개`,
STATISTICS_MATCH5_BONUS: (count) =>
`5개 일치, 보너스 볼 일치 (30,000,000원) - ${count}개`,
STATISTICS_MATCH6: (count) => `6개 일치 (2,000,000,000원) - ${count}개`,
PROFIT_RATE: (rate) => `총 수익률은 ${rate}%입니다.`,
};
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import App from "./App.js";
import App from './controller/App.js';

const app = new App();
await app.run();
Loading