Skip to content
Open
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,37 @@
# javascript-lotto-precourse

## 기능 목록 - turtlehwan

- [x] 로또 구입 금액 입력 기능
- [x] 예외 : 정수가 아닌 숫자입니다.
- [x] 예외 : 정수가 1,000 미만입니다.
- [x] 예외 : 입력 받은 수가 1,000 단위가 아닙니다.
- [x] 로또 번호 발행 기능
- [x] 1~45 정수 숫자 범위 -> MissionUtils
- [x] 중복되지 않는 6개 숫자 발행 -> MissionUtils
- [x] 발행한 로또 수량 및 번호 출력 (오름차순)
- [x] 당첨 번호 입력 기능
- [x] 양의 정수 판별
- [x] 1~45 숫자 범위
- [x] 중복되지 않는 6개의 숫자 입력
- [x] 보너스 번호 입력 기능
- [x] 양의 정수 판별
- [x] 1~45 숫자 범위
- [x] 중복되지 않는 6개의 숫자 입력
- [x] 당첨 확인(등수 판별) 기능
- [x] 당첨 내역 확인 및 출력
- [x] 수익률 계산 및 출력
- [x] 소수점 둘째 자리에서 반올림

## 중점 사항

- 값을 하드코딩하지 않고, 상수로 빼내서 정리하기
- 메서드가 한 가지 기능을 하는지 확인하는 기준 세우기
- 여러 메서드에서 중복되는 코드가 있으면 별도의 메서드로 분리
- 해당 메서드를 변경할 때 서로 다른 이유로 변경하고 commit하는지 살펴보기
- 안내 문구 출력, 사용자 입력 처리, 유효값 검증 등의 작업을 각기 다른 함수로 분리
- class의 private 필드 잘 이용하기 (# 문법)
- JavaScript에서 객체를 만드는 다양한 방법을 이해하고 사용하기
- 기능별 단위 테스트 만들기
- 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리
- 처음부터 큰 단위의 테스트를 만들지 않기
51 changes: 51 additions & 0 deletions __tests__/LottoVendingMachineTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { MissionUtils } from "@woowacourse/mission-utils";
import LottoVendingMachine from "../src/LottoVendingMachine.js";

const mockQuestions = (inputs) => {
MissionUtils.Console.readLineAsync = jest.fn();

MissionUtils.Console.readLineAsync.mockImplementation(() => {
const input = inputs.shift();

return Promise.resolve(input);
});
};

const getLogSpy = () => {
const logSpy = jest.spyOn(MissionUtils.Console, "print");
logSpy.mockClear();
return logSpy;
};

const runException = async (input) => {
// given
const logSpy = getLogSpy();

const INPUT_NUMBERS_TO_END = ["1000", "1,2,3,4,5,6", "7"];
mockQuestions([input, ...INPUT_NUMBERS_TO_END]);

// when
const LottoVM = new LottoVendingMachine();
await LottoVM.purchaseLottoAmountInput();

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

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

test("금액이 정수가 아니면 예외가 발생한다.", async () => {
await runException("1000.5");
});

test("금액이 1000원 미만이면 예외가 발생한다.", async () => {
await runException("500");
});

test("금액이 1000원 단위가 아니면 예외가 발생한다.", async () => {
await runException("1521");
});
});
57 changes: 57 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import babelParser from '@babel/eslint-parser';
import { FlatCompat } from '@eslint/eslintrc';
import pluginJs from '@eslint/js';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
import path from 'path';
import { fileURLToPath } from 'url';

const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);

const compat = new FlatCompat({
baseDirectory: dirname,
});

export default [
{
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: babelParser,
parserOptions: {
requireConfigFile: false,
},
globals: {
...globals.node,
...globals.jest,
},
},
},
...compat.extends('eslint-config-airbnb-base'),
prettier,
{
files: ['/src//.js', '/tests//.js', '/tests//*.js'],
rules: {
'max-depth': ['error', 2],
'max-params': ['error', 3],
'max-lines-per-function': ['error', { max: 15 }],
'import/extensions': ['error', 'ignorePackages'],
'import/prefer-default-export': 'off',
'class-methods-use-this': 'off',
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-console': 'off',
'no-unused-vars': 'error',
'prefer-const': 'error',
'no-var': 'error',
eqeqeq: ['error', 'always'],
},
},
{
files: ['eslint.config.js'],
rules: {
'no-underscore-dangle': 'off',
'import/no-extraneous-dependencies': ['error', { packageDir: __dirname }],
},
},
];
7 changes: 6 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import LottoVendingMachine from "./LottoVendingMachine.js";

class App {
async run() {}
async run() {
const LottoVM = new LottoVendingMachine();
await LottoVM.run();
}
}

export default App;
39 changes: 37 additions & 2 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,53 @@
import { MAGIC_NUMBER } from "./constants/magicNumber.js";
import { MESSAGES } from "./constants/messages.js";

class Lotto {
#numbers;

constructor(numbers) {
this.#validate(numbers);
this.#validateDuplicate(numbers);
Comment on lines 8 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

validate안에 validateIntsRange 는 validate안에 넣었는데 validateDuplicate를 따로 하신 이유가 있을까요?!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

  1. validateIntsRange()는 number 배열이 아니라 파라미터로 하나의 숫자를 받아 검증하도록 해서였습니다
  2. 원랜 같이 넣으려다가 너무 함수가 길어져서 뺐습니다.
  3. 차라리 validate들을 더 분리하고 생성자에서 다 불러오는 것이 가독성 측면에서 더 좋아 보였는데 시간이 없었네요 :)

this.#numbers = numbers;
}

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
throw new Error(
MESSAGES.ERROR.PREFIX +
MESSAGES.ERROR.LOTTO_PICK_NUM(MAGIC_NUMBER.LOTTO_PICK_NUM)
);
}

numbers.forEach((number) => {
if (!Number.isInteger(Number(number))) {
throw new Error(MESSAGES.ERROR.PREFIX + MESSAGES.ERROR.NOT_INT);
}
this.#validateIntsRange(
number,
MAGIC_NUMBER.LOTTO_MIN_NUM,
MAGIC_NUMBER.LOTTO_MAX_NUM
);
});
}

#validateIntsRange(number, min, max) {
if (Number(number) < min || Number(number) > max) {
throw new Error(
MESSAGES.ERROR.PREFIX + MESSAGES.ERROR.RANGE_INT(min, max)
);
}
}

// TODO: 추가 기능 구현
#validateDuplicate(numbers) {
const set = new Set(numbers);
if (numbers.length !== set.size) {
throw new Error(MESSAGES.ERROR.PREFIX + MESSAGES.ERROR.DUPLICATE_INT);
}
}

getLottoNumbers() {
return this.#numbers;
}
}

export default Lotto;
Loading