Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e29e619
docs(readme): 기능 목록 작성
nunomi0 Nov 3, 2025
4638a59
feat(inputView): 로또 구입 금액 입력
nunomi0 Nov 3, 2025
f1ce297
feat(validator): 로또 구입 금액 검증
nunomi0 Nov 3, 2025
db4b97b
test(validator): 로또 구입 금액 검증
nunomi0 Nov 3, 2025
bd7b97f
feat(lottoController): 로또 구입 금액 입력 및 검증 기능 추가
nunomi0 Nov 3, 2025
0a77694
feat(inputView): 당첨 번호 입력
nunomi0 Nov 3, 2025
44238f9
feat(validator): 당첨 번호 검증
nunomi0 Nov 3, 2025
a0f5318
test(validator): 당첨 번호 검증
nunomi0 Nov 3, 2025
a31916e
feat(lottoController): 당첨 번호 입력 및 검증 기능 추가
nunomi0 Nov 3, 2025
5fe2bc1
refactor(validator): 당첨 번호 검증 순서 조정으로 조기 종료 최적화
nunomi0 Nov 3, 2025
c307044
feat(inputView): 보너스 번호 입력
nunomi0 Nov 3, 2025
f9cc4a3
feat(validator): 보너스 번호 검증
nunomi0 Nov 3, 2025
93aec44
test(validator): 보너스 번호 검증
nunomi0 Nov 3, 2025
0a5009c
feat(lottoController): 보너스 번호 입력 및 검증 기능 추가
nunomi0 Nov 3, 2025
a8ffde0
feat(lottogenerator): 로또 발행 기능 추가
nunomi0 Nov 3, 2025
a52a125
test(lottogenerator): 로또 발행 기능
nunomi0 Nov 3, 2025
3ed8288
feat(calculateresult): 당첨 결과 계산 기능 추가
nunomi0 Nov 3, 2025
fdc331c
test(calculateresult): 당첨 결과 계산 기능
nunomi0 Nov 3, 2025
4360733
refactor(calculateresult): 로또 결과 계산 로직 함수 분리
nunomi0 Nov 3, 2025
e1c70f2
feat(calculateprofit): 수익률 계산 기능 추가
nunomi0 Nov 3, 2025
dfaaf2c
test(calculateprofit): 수익률 계산 기능
nunomi0 Nov 3, 2025
f3e0dba
feat(lotto): getNumbers 메서드 추가
nunomi0 Nov 3, 2025
4950744
feat(outputView): 구매한 로또 번호 출력
nunomi0 Nov 3, 2025
d242a24
feat(outputView): 당첨 통계, 수익률 출력
nunomi0 Nov 3, 2025
1f1ff85
refactor(outputview): printResult 함수 분리
nunomi0 Nov 3, 2025
528000f
fix(lotto): 테스트가 통과하도록 조건 추가
nunomi0 Nov 3, 2025
ef270ff
fix(lottocontroller): 예외 처리가 제대로 되도록 try, catch 추가
nunomi0 Nov 3, 2025
e985655
docs(readme): 기능 소개, 실행 예시, 프로젝트 구조, 예외 처리, 고려한 부분 추가
nunomi0 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
137 changes: 137 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,138 @@
# javascript-lotto-precourse
로또 구매부터 당첨 결과 확인까지 전 과정을 시뮬레이션하는 JavaScript 애플리케이션입니다.

## 기능 소개

1. **로또 구매**: 1,000원 단위로 로또를 구매합니다.
2. **자동 번호 생성**: 1~45 사이의 중복되지 않는 6개의 번호를 자동으로 생성합니다.
3. **당첨 확인**: 구매한 로또와 당첨 번호를 비교하여 당첨 결과를 확인합니다.
4. **수익률 계산**: 총 수익률을 계산하여 표시합니다.

## 실행 예시

```
구입금액을 입력해 주세요.
8000

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]

당첨 번호를 입력해 주세요.
1,2,3,4,5,6

보너스 번호를 입력해 주세요.
7

당첨 통계
----------
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%입니다.
```

## 프로젝트 구조

```
src/
├── App.js # 애플리케이션 진입점
├── index.js # 프로그램 실행
├── controller/
│ └── LottoController.js # 로또 게임 로직 제어
├── model/
│ ├── Lotto.js # 로또 클래스
│ ├── lottoGenerator.js # 로또 생성 로직
│ ├── calculateResult.js # 당첨 결과 계산
│ ├── calculateProfit.js # 수익률 계산
│ ├── validator.js # 입력 검증
│ └── constants.js # 상수 정의
└── view/
├── inputView.js # 사용자 입력 처리
└── outputView.js # 결과 출력 처리

__tests__/
├── ApplicationTest.js # 통합 테스트
├── LottoTest.js # 로또 클래스 테스트
├── validator.test.js # 유효성 검증 테스트
├── LottoGenerator.test.js # 로또 생성 테스트
├── calculateResult.test.js # 결과 계산 테스트
└── calculateProfit.test.js # 수익률 계산 테스트
```

## 구현 기능 목록

### 입력
- [x] 로또 구입 금액 입력
- [x] 숫자가 아닌 경우 예외
- [x] 정수가 아닌 경우 예외
- [x] 양수가 아닌 경우 예외
- [x] `1000`으로 나누어 떨어지지 않는 경우 예외
- [x] 당첨 번호 입력
- [x] 쉼표로 구분된 6개의 숫자 입력
- [x] `1`~`45` 범위를 벗어나는 경우 예외
- [x] 중복된 번호가 있는 경우 예외
- [x] 보너스 번호 입력
- [x] `1`~`45` 범위를 벗어나는 경우 예외
- [x] 당첨 번호와 중복인 경우 예외

### 로또 발행
- [x] `구입 금액 / 1000`개의 로또 발행
- [x] 각 로또는 `1`~`45` 범위의 중복되지 않은 6개의 랜덤 숫자로 구성
- [x] 번호 오름차순 정렬

### 당첨 결과 계산
- [x] 로또 번호에 따른 당첨 결과 계산
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원
- [x] 수익률 계산
- [x] 소수점 둘째 자리 반올림

### 출력
- [x] 구매한 로또 번호 출력
- [x] 당첨 통계 출력
- [x] 수익률 출력
- [x] 예외 발생 시 `[ERROR]`로 시작하는 에러 메시지 출력

## 예외 처리

모든 예외 상황에서 `[ERROR]`로 시작하는 에러 메시지를 출력하고, 올바른 값을 재입력받습니다.

### 구입 금액 예외
- 숫자가 아닌 값 입력 시: `[ERROR] 구입 금액은 숫자여야 합니다.`
- 정수가 아닌 값 입력 시: `[ERROR] 구입 금액은 정수여야 합니다.`
- 0 이하의 값 입력 시: `[ERROR] 구입 금액은 0보다 커야 합니다.`
- 1,000원 단위가 아닌 경우: `[ERROR] 구입 금액은 1000원 단위여야 합니다.`

### 당첨 번호 예외
- 6개가 아닌 경우: `[ERROR] 당첨 번호는 6개여야 합니다.`
- 숫자가 아닌 값 포함 시: `[ERROR] 당첨 번호는 숫자여야 합니다.`
- 1~45 범위를 벗어난 경우: `[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.`
- 중복된 번호가 있는 경우: `[ERROR] 중복된 당첨 번호가 있습니다.`

### 보너스 번호 예외
- 1~45 범위를 벗어난 경우: `[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.`
- 당첨 번호와 중복인 경우: `[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.`

### 로또 클래스 예외
- 번호가 6개가 아닌 경우: `[ERROR] 로또 번호는 6개여야 합니다.`
- 중복된 번호가 있는 경우: `[ERROR] 로또 번호에 중복된 숫자가 있습니다.`

## 설계 시 고려한 부분

1. **MVC 패턴**: Controller, Model, View를 분리하여 관심사의 분리 원칙을 준수합니다.
2. **단일 책임 원칙**: 각 모듈은 하나의 책임만 가집니다.
3. **입력 검증**: 모든 사용자 입력은 validator를 통해 검증됩니다.
4. **에러 처리**: 예외 발생 시 적절한 에러 메시지를 표시합니다.
5. **테스트**: 단위 테스트와 통합 테스트를 통해 코드의 안정성을 보장합니다.
38 changes: 38 additions & 0 deletions __tests__/LottoGenerator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Random } from "@woowacourse/mission-utils";
import { generateLottos } from "../src/model/lottoGenerator.js";
import Lotto from "../src/model/Lotto.js";

jest.mock("@woowacourse/mission-utils", () => ({
Random: {
pickUniqueNumbersInRange: jest.fn(),
},
}));

describe("generateLottos()", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("구입 금액에 따라 Lotto 인스턴스를 생성한다.", () => {
Random.pickUniqueNumbersInRange.mockReturnValue([1, 2, 3, 4, 5, 6]);
const purchaseAmount = 3000;

const lottos = generateLottos(purchaseAmount);

expect(lottos).toHaveLength(3);
lottos.forEach((lotto) => {
expect(lotto).toBeInstanceOf(Lotto);
});
});

test("각 로또는 1~45 범위의 중복되지 않은 6개의 오름차순 숫자로 구성된다.", () => {
Random.pickUniqueNumbersInRange.mockReturnValue([45, 1, 12, 8, 30, 5]);

const lottos = generateLottos(1000);
const numbers = lottos[0].getNumbers ? lottos[0].getNumbers() : undefined;

if (numbers) {
expect(numbers).toEqual([1, 5, 8, 12, 30, 45]);
}
});
});
5 changes: 1 addition & 4 deletions __tests__/LottoTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Lotto from "../src/Lotto";
import Lotto from "../src/model/Lotto";

describe("로또 클래스 테스트", () => {
test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
Expand All @@ -7,12 +7,9 @@ describe("로또 클래스 테스트", () => {
}).toThrow("[ERROR]");
});

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

// TODO: 추가 기능 구현에 따른 테스트 코드 작성
});
54 changes: 54 additions & 0 deletions __tests__/calculateProfit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import calculateProfit from "../src/model/calculateProfit.js";

describe("calculateProfit", () => {
test("당첨 내역이 없으면 수익률은 0%", () => {
const result = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
const purchaseAmount = 10000;
const rate = calculateProfit(result, purchaseAmount);

expect(rate).toBe(0);
});

test("1등 1개면 200,000,000%", () => {
const result = { 1: 1, 2: 0, 3: 0, 4: 0, 5: 0 };
const purchaseAmount = 1000;
const rate = calculateProfit(result, purchaseAmount);

const expected = Number(((2000000000 / 1000) * 100).toFixed(1));
expect(rate).toBe(expected);
});

test("3등 2개면 (1,500,000 × 2) / 10,000 * 100 = 30,000%", () => {
const result = { 1: 0, 2: 0, 3: 2, 4: 0, 5: 0 };
const purchaseAmount = 10000;
const rate = calculateProfit(result, purchaseAmount);

expect(rate).toBe(30000);
});

test("5등 5개면 (5,000 × 5) / 5,000 * 100 = 500%", () => {
const result = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 5 };
const purchaseAmount = 5000;
const rate = calculateProfit(result, purchaseAmount);

expect(rate).toBe(500);
});

test("1등 1명, 3등 2명, 5등 3명일 때 총 상금은 2,003,015,000원이다.", () => {
const result = { 1: 1, 2: 0, 3: 2, 4: 0, 5: 3 };
const purchaseAmount = 10000;
const expected = Number(((2003015000 / purchaseAmount) * 100).toFixed(1));
const rate = calculateProfit(result, purchaseAmount);

expect(rate).toBe(expected);
});

test("결과값은 소수점 한 자리까지 반올림된다.", () => {
const result = { 1: 0, 2: 1, 3: 0, 4: 0, 5: 0 };
const purchaseAmount = 3333;
const rate = calculateProfit(result, purchaseAmount);

const expected = Number(((30000000 / 3333) * 100).toFixed(1));
expect(rate).toBe(expected);
});
});
63 changes: 63 additions & 0 deletions __tests__/calculateResult.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import calculateResult from "../src/model/calculateResult.js";

const createLotto = (numbers) => ({
getNumbers: () => numbers,
});

describe("calculateResult", () => {
test("6개 번호가 모두 일치하면 1등 1개를 반환한다.", () => {
const lottos = [createLotto([1, 2, 3, 4, 5, 6])];
const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 1, 2: 0, 3: 0, 4: 0, 5: 0 });
});

test("5개 번호 + 보너스 번호가 일치하면 2등 1개를 반환한다.", () => {
const lottos = [createLotto([1, 2, 3, 4, 5, 7])];
const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 0, 2: 1, 3: 0, 4: 0, 5: 0 });
});

test("5개 번호가 일치하면 3등 1개를 반환한다.", () => {
const lottos = [createLotto([1, 2, 3, 4, 5, 9])];
const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 0, 2: 0, 3: 1, 4: 0, 5: 0 });
});

test("4개 번호가 일치하면 4등 1개를 반환한다.", () => {
const lottos = [createLotto([1, 2, 3, 4, 10, 11])];
const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 0, 2: 0, 3: 0, 4: 1, 5: 0 });
});

test("3개 번호가 일치하면 5등 1개를 반환한다.", () => {
const lottos = [createLotto([1, 2, 3, 10, 11, 12])];
const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 1 });
});

test("2개 이하로 일치하면 등수에 포함되지 않는다.", () => {
const lottos = [createLotto([1, 2, 10, 11, 12, 13])];
const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 });
});

test("여러 장의 로또 결과가 등수별로 누적된다.", () => {
const lottos = [
createLotto([1, 2, 3, 4, 5, 6]), // 1등
createLotto([1, 2, 3, 4, 5, 7]), // 2등
createLotto([1, 2, 3, 4, 5, 9]), // 3등
createLotto([1, 2, 3, 4, 10, 11]), // 4등
createLotto([1, 2, 3, 10, 11, 12]), // 5등
];

const result = calculateResult(lottos, [1, 2, 3, 4, 5, 6], 7);

expect(result).toEqual({ 1: 1, 2: 1, 3: 1, 4: 1, 5: 1 });
});
});
Loading