Skip to content

[로또] 김다현 미션 제출합니다. - #153

Open
daahyunk wants to merge 33 commits into
woowacourse-precourse:mainfrom
daahyunk:daahyunk
Open

[로또] 김다현 미션 제출합니다.#153
daahyunk wants to merge 33 commits into
woowacourse-precourse:mainfrom
daahyunk:daahyunk

Conversation

@daahyunk

@daahyunk daahyunk commented Nov 3, 2025

Copy link
Copy Markdown

javascript-lotto-precourse

프로젝트 개요

간단한 로또 발매기를 구현한다. 사용자는 금액을 입력해 로또를 구입하고 당첨 번호 및 보너스 번호를 입력해 당첨 결과와 수익률을 확인할 수 있다.


구현 목표

객체 간 협력을 통해 로또 발매 및 결과 계산을 수행한다.

  • 클래스와 함수를 분리해 하나의 클래스가 한 가지 역할만 수행하도록 설계한다.
  • UI 로직(입출력)을 제외한 모든 로직은 단위 테스트로 검증한다.
  • 하드 코딩을 지양하고 상수 파일을 통해 값을 관리한다.
  • README 기능 목록을 지속적으로 업데이트하며, 정상 케이스와 예외 케이스를 모두 정리한다.

기능 목록

1. 구입 금액 입력

  • 사용자로부터 로또 구입 금액을 입력받는다.

  • 구입 금액은 1,000원 단위로 나누어 떨어져야 한다.

  • 구입 금액은 0보다 커야 하며 숫자여야 한다.

  • 예외 상황 발생 시 [ERROR]로 시작하는 메시지를 출력하고 해당 단계부터 재입력받는다.

    • 입력값이 숫자가 아닌 경우 → [ERROR] 구입 금액은 숫자여야 합니다.
    • 입력값이 0 이하인 경우 → [ERROR] 구입 금액은 0보다 커야 합니다.
    • 1,000원 단위로 나누어 떨어지지 않는 경우 → [ERROR] 구입 금액은 1,000원 단위여야 합니다.

2. 로또 발행

  • 구입 금액에 따라 구매 가능한 로또 개수를 계산한다.
    (ex. 8000원 입력 -> 8개 구매)
  • 각 로또는 1~45 사이의 중복되지 않은 6개의 번호로 구성된다.
  • 발행된 로또 번호는 오름차순으로 정렬한다.
  • 번호가 6개가 아닌 경우 → [ERROR] 로또 번호는 6개여야 합니다.
  • 번호가 중복된 경우 → [ERROR] 로또 번호는 중복될 수 없습니다.

3. 발행 결과 출력

  • 구입한 로또 수량과 각 로또 번호를 출력한다.
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
...

4. 당첨 번호 입력

  • 쉼표(,)로 구분된 6개의 숫자를 입력받는다.
  • 숫자는 1~45 사이의 값이어야 하며, 중복되지 않아야 한다.
  • 예외: 개수가 6개가 아니거나 범위를 벗어나거나 중복된 경우 [ERROR] 당첨 번호 형식이 올바르지 않습니다. 출력 후 재입력.

5. 보너스 번호 입력

  • 1개의 보너스 번호를 입력받는다.
  • 보너스 번호는 당첨 번호와 중복될 수 없다.
  • 예외: 1~45 범위를 벗어나거나 당첨 번호와 중복될 경우 [ERROR] 보너스 번호가 올바르지 않습니다. 출력 후 재입력.

6. 당첨 결과 계산

  • 각 로또의 번호를 당첨 번호와 비교하여 일치 개수를 계산한다.
  • 일치 개수 및 보너스 번호 여부에 따라 등수를 판별한다.
순위 조건 상금
1등 6개 번호 일치 2,000,000,000원
2등 5개 번호 + 보너스 번호 일치 30,000,000원
3등 5개 번호 일치 1,500,000원
4등 4개 번호 일치 50,000원
5등 3개 번호 일치 5,000원
  • 등수 계산은 LottoRank(enum 객체)를 통해 관리한다.

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%입니다.

예외 처리 원칙

  • 모든 예외 메시지는 [ERROR]로 시작한다.
  • 잘못된 입력 발생 시 해당 부분부터 다시 입력받는다.
  • 프로그램 종료 시 process.exit()를 사용하지 않는다.

테스트 시나리오

1. 구입 금액 검증 (ValidationTest.js)

구분 입력 기대 결과
정상 입력 "8000" 숫자 8000 반환
예외: 숫자가 아닌 입력 "abc" [ERROR] 구입 금액은 숫자여야 합니다. 발생
예외: 0 이하 입력 "0", "-1000" [ERROR] 구입 금액은 0보다 커야 합니다. 발생
예외: 1,000원 단위 아님 "1500" [ERROR] 구입 금액은 1,000원 단위여야 합니다. 발생

2. 로또 발행 (LottoMachineTest.js)

구분 입력 기대 결과
발행 개수 계산 8000 8개의 로또 객체 생성
번호 유효성 검증 각 로또의 번호 배열 길이 6개, 1~45 범위 내 중복 없음
정렬 검증 [43, 8, 21, 23, 42, 41] → 발행 시 [8, 21, 23, 41, 42, 43] 으로 정렬
예외: 번호 개수 오류 [1,2,3,4,5] [ERROR] 로또 번호는 6개여야 합니다.
예외: 중복 번호 포함 [1,2,3,3,4,5] [ERROR] 로또 번호는 중복될 수 없습니다.

3. 발행 결과 출력 (OutputViewTest.js)

구분 테스트 내용 기대 결과
출력 개수 확인 8개의 로또를 발행했을 때 "8개를 구매했습니다." 출력
각 로또 번호 출력 [8, 21, 23, 41, 42, 43] 콘솔에 [8, 21, 23, 41, 42, 43] 형태로 출력
출력 형식 상수화 검증 상수 파일의 MESSAGE 사용 하드코딩된 문자열 없이 상수로 출력 관리

4. 당첨 번호 입력 (WinningNumbersTest.js)

구분 입력 기대 결과
정상 입력 "1,2,3,4,5,6" [1,2,3,4,5,6] 배열로 변환
예외: 숫자 개수 부족 "1,2,3,4,5" [ERROR] 당첨 번호 형식이 올바르지 않습니다. 발생
예외: 숫자 개수 초과 "1,2,3,4,5,6,7" [ERROR] 당첨 번호 형식이 올바르지 않습니다. 발생
예외: 중복된 숫자 포함 "1,2,3,3,4,5" [ERROR] 당첨 번호 형식이 올바르지 않습니다. 발생
예외: 범위 벗어난 숫자 포함 "0,2,3,4,5,6", "1,2,3,4,5,46" [ERROR] 당첨 번호 형식이 올바르지 않습니다. 발생

5. 보너스 번호 입력 (BonusNumberTest.js)

구분 입력 기대 결과
정상 입력 "7" 숫자 7 반환
예외: 숫자 아님 "abc" [ERROR] 보너스 번호가 올바르지 않습니다. 발생
예외: 1~45 범위 벗어남 "0", "46" [ERROR] 보너스 번호가 올바르지 않습니다. 발생
예외: 당첨 번호와 중복 "1" (당첨 번호에 1 포함 시) [ERROR] 보너스 번호가 올바르지 않습니다. 발생

6. 당첨 결과 계산 (ResultCalculatorTest.js)

구분 입력 기대 결과
1등 로또 번호: [1,2,3,4,5,6], 당첨 번호: [1,2,3,4,5,6], 보너스: 7 1등 반환 (2,000,000,000원)
2등 로또 번호: [1,2,3,4,5,7], 당첨 번호: [1,2,3,4,5,6], 보너스: 7 2등 반환 (30,000,000원)
3등 로또 번호: [1,2,3,4,5,8], 당첨 번호: [1,2,3,4,5,6], 보너스: 7 3등 반환 (1,500,000원)
4등 로또 번호: [1,2,3,4,8,9], 당첨 번호: [1,2,3,4,5,6], 보너스: 7 4등 반환 (50,000원)
5등 로또 번호: [1,2,3,8,9,10], 당첨 번호: [1,2,3,4,5,6], 보너스: 7 5등 반환 (5,000원)
낙첨 로또 번호: [1,9,10,11,12,13], 당첨 번호: [1,2,3,4,5,6], 보너스: 7 낙첨 (0원)
여러 개 로또 입력 [ [1,2,3,4,5,6], [1,2,3,4,5,7], [1,2,3,4,8,9] ] {1등: 1, 2등: 1, 4등: 1} 형태의 객체 반환
수익률 계산 총 상금 2,030,050,000원, 구입 금액 8,000원 "25375625.00%" (소수 둘째 자리 반올림) 반환

보조 시나리오 (LottoRank enum)

구분 조건 기대 결과
1등 매칭 6개 일치 rank: 1, prize: 2,000,000,000
2등 매칭 5개 + 보너스 일치 rank: 2, prize: 30,000,000
3등 매칭 5개 일치 rank: 3, prize: 1,500,000
4등 매칭 4개 일치 rank: 4, prize: 50,000
5등 매칭 3개 일치 rank: 5, prize: 5,000
낙첨 2개 이하 rank: null, prize: 0

7. 당첨 통계 출력 (ApplicationTest.js)

구분 테스트 내용 기대 결과
통합 흐름 테스트 금액 입력 → 로또 발행 → 당첨 번호 → 보너스 번호 입력 후 실행 전체 당첨 통계 및 수익률이 올바르게 출력된다.
출력 결과 검증 로그 출력 내용 "3개 일치 (5,000원) - 1개", "총 수익률은 62.5%입니다." 등 포함
예외 입력 처리 금액 입력에 숫자가 아닌 값 전달 (예: "1000j") [ERROR] 메시지 출력 및 프로그램 재실행
구매 수량 및 로또 출력 "8개를 구매했습니다." 메시지 출력 확인 발행된 로또 번호 8개가 모두 출력됨
수익률 계산 정확도 총 상금과 구입 금액 기반 수익률 계산 "총 수익률은 62.5%입니다." 정확히 출력

프로젝트 구조

src
├── domain              # 핵심 비즈니스 로직 (도메인 계층)
│   ├── BonusNumber.js          # 보너스 번호 검증 및 관리
│   ├── constants.js            # 상수 정의 (메시지, 제한값 등)
│   ├── LottoGenerator.js       # 로또 번호 생성 로직
│   ├── LottoMachine.js         # 구입 금액에 따른 로또 발행
│   ├── LottoRank.js            # 당첨 등수 및 상금 정의
│   ├── ResultCalculator.js     # 당첨 결과 및 수익률 계산
│   └── WinningNumbers.js       # 당첨 번호 검증 및 관리
│
├── utils                # 공통 유틸리티
│   └── validation.js            # 입력값 검증 함수 모음
│
├── view                 # 입출력(UI) 담당 계층
│   ├── OutputView.js           # 로또 발행 결과 출력
│   ├── ResultView.js           # 당첨 통계 및 수익률 출력
│   ├── App.js                  
│   ├── index.js                
│   └── Lotto.js                # 로또 객체 정의 (번호 검증 포함)
│
├── README.md
└── package.json
  • domain 핵심 로직을 담당하며 UI나 입출력과 완전히 분리. 테스트 가능한 순수 로직만 포함.
  • utils 재사용 가능한 검증 로직을 모아두어 각 도메인에서 공통으로 사용
  • view 사용자와 상호작용하는 콘솔 입출력 로직을 담당

회고

이번 3주차 로또 미션에서는 지난 주차에 하지 못했던 테스트 주도 개발(TDD) 방식을 적용하며 기능 구현보다 설계와 구조에 집중하는 과정을 경험했다. 각 기능을 작은 단위로 나누고 테스트를 먼저 작성한 뒤 통과시키는 과정을 반복하면서 코드 품질을 점진적으로 개선할 수 있었던 것 같다.


새롭게 시도한 것

  • Red → Green → Refactor 사이클 적용

    테스트를 작성하고 최소한의 코드로 통과시킨 후 리팩토링하는 과정을 명확히 구분해 진행했다.
    그러다보니 코드의 의도와 구조를 우선적으로 고민하게 되었다.

  • JavaScript 문법 활용

    Set을 이용한 중복 검증, Array.from으로 배열 생성, every를 통한 범위 검증 등 JavaScript 내장 메서드를 활용하여 검증 로직을 간결하고 읽기 쉽게 구현했다.

  • 도메인 중심 설계

    Lotto, LottoMachine, WinningNumbers, BonusNumber, ResultCalculator 등 각 클래스가 하나의 역할만 담당하도록 단일 책임(SRP)에 근거하여 설계했다. 검증 로직은 utils/validation.js, 상수는 constants.js로 분리해 재사용성과 일관성을 확보했다.

  • 하드코딩 제거 및 상수화

    UI 메시지, 배열 조인 구분자 등 모든 문자열 상수를 constants.js에서 중앙화했다. 이를 통해 메시지 변경 시 단일 지점에서만 수정할 수 있고 확장이 용이해졌다.


어려웠던 부분

  • 테스트를 먼저 작성해야 한다니...

    지금까지는 기능을 먼저 구현한 후 테스트를 작성하는 방식에 익숙했기 때문에 테스트 주도 개발(TDD) 방식으로 테스트를 먼저 작성하는 것이 매우 낯설었다. 어떤 테스트를 먼저 작성해야 하는지, 어느 수준의 테스트부터 시작해야 하는지 판단하기가 어려워 처음에 시간이 많이 소요됐다.


실패한 접근

  • 테스트 스펙을 먼저 읽지 않고 구현한 문제

    ApplicationTest.js에서 기대하는 동작 방식을 미리 파악하지 않고 개별 단위 테스트부터 구현했다. 결과적으로 테스트에서 기대하는 인터페이스와 다른 코드를 구현해놓는 바람에 마지막 단계에서 수정을 했어야 했다.


배운 점 / 느낀 점

이번 미션을 TDD 기반으로 진행하면서, 지난 주차까지만 해도 기능부터 만들고 나중에 테스트하는 줄 알았던 내가 TDD라는 개념을 이해하고 "이게 뭘 해야 하지?"를 먼저 정의한 후에 코드를 작성하는 방식으로 바뀌었다는 점에서 우선 한 단계 성장했다고 느꼈다. 덕분에 코드 짜기 전에 머리로 한 번 더 정리하게 되고 결과적으로 코드도 더 깔끔해졌다.

테스트가 귀찮은 작업이 아니라 리팩토링할 때 안전망 같은 역할을 한다는 것도 느꼈다. 코드 구조를 바꾸거나 LottoMachine, ResultCalculator 같은 클래스들 사이 관계를 손볼 때 테스트가 있으니까 뭐가 깨졌는지 바로 알 수 있어서 좋았다. 그냥 "이렇게 바꿔도 되나?" 고민하는 시간이 확 줄었다.

처음엔 테스트 작성이 느리고 답답했는데 하나씩 통과할 때마다 쾌감이 들기도 하고...ㅋㅋ 내 코드가 더 확신이 생기는 느낌이었다. 앞으로도 이런 식으로 검증하면서 코드 짜는 연습을 더 해봐야겠다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant