3주차 미션은 Java 로또 시뮬레이터를 구현하는 것이었다. 구입 금액을 입력받아 로또를 발급하고, 당첨 번호와 보너스 번호를 입력받아 당첨 통계를 낸 뒤, 최종 수익률을 출력하는 프로그램이다.
2주차 자동차 경주 미션이 객체의 분리에 집중했다면, 이번 3주차 로또 미션은 2주차보다 구현해야 할 기능의 비중이 컸다고 느꼈다. MVC 패턴을 기본으로, Enum을 활용한 상태 관리, 방어적 복사의 필요성, 그리고 테스트 코드의 중요성을 깊이 있게 배울 수 있었다.
지난 주차와 마찬가지로 README.md에 기능 목록과 체크리스트를 작성하며 시작했다.
# java-lotto-precourse
## 구현 기능 목록
### 1️⃣ 로또 구입
- [x] 입력한 금액이 1,000원 단위인지 검증한다.
- [x] 구입 금액이 숫자가 아닌 경우 예외를 발생시킨다.
- [x] 구입 금액만큼 로또를 발행한다. (로또 1장 = 1,000원)
- [x] 발행된 로또의 수량을 출력한다.
- [x] 각 로또는 **1~45 범위의 중복되지 않은 6개 숫자**를 가진다.
- [x] 발행된 로또 번호를 **오름차순으로 출력한다.**
### 2️⃣ 당첨 번호 입력
- [x] 쉼표(,)로 구분된 6개의 숫자를 입력받는다.
- [x] 입력 번호 중 **중복이 있을 경우 예외 발생.**
- [x] 번호가 1~45 범위를 벗어나면 예외 발생.
- [x] 숫자가 아닌 값 입력 시 예외 발생.
- [x] 입력 개수가 정확히 6개가 아닐 경우 예외 발생.
### 3️⃣ 보너스 번호 입력
- [x] 보너스 번호가 **당첨 번호에 포함되어 있으면 예외 발생.**
- [x] 보너스 번호가 **1~45 범위 내**인지 검증한다.
### 4️⃣ 당첨 결과 계산
- [x] 각 로또 번호를 당첨 번호와 비교하여 **일치 개수 계산.**
- [x] 5개 일치 시, **보너스 번호 일치 여부**를 별도 판별.
- [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원 |
### 5️⃣ 수익률 계산
- [x] 당첨 금액의 총합을 계산한다.
- [x] 수익률을 **소수점 둘째 자리에서 반올림**한다.
- [x] 형식: `총 수익률은 XX.X%입니다.`
### 6️⃣ 출력 형식 및 예외 처리
- [x] 잘못된 입력 시 `IllegalArgumentException` 발생.
- [x] 에러 메시지는 반드시 `[ERROR]` 로 시작.
- [x] 예외 발생 시 해당 입력부터 **재입력**을 요청한다.
---
## 단위 테스트 항목
- [x] `Lotto` 생성 시 번호 유효성 검증 (6개, 범위, 중복 확인)
- [x] 금액 입력 검증 (문자, 음수, 1000원 단위 불일치)
- [x] 당첨 번호 / 보너스 번호 예외 검증
- [x] 일치 개수 및 등수 판정 테스트
- [x] 수익률 계산 정확도 테스트
- [x] `[ERROR]` 메시지 출력 검증
설계 방향 및 클래스 역할
| Model | Lotto | - 로또 1장을 표현하는 도메인 객체 - 생성 시 6개 번호의 유효성(범위, 중복)을 스스로 검증 |
| LottoGenerator | - Randoms 라이브러리를 활용해 로또 번호를 생성 - 중복 없는 1~45 범위의 6개 번호 반환 |
|
| LottoType (Enum) | - 1등~5등 및 꽝(MISS)의 상태를 Enum으로 관리 - 각 등수의 일치 개수, 보너스 여부, 상금, 출력 메시지 등을 캡슐화 |
|
| LottoResult | - 구매한 로또 목록(List<Lotto>)과 구입 금액을 저장 - 당첨 통계 계산 및 총 수익률 계산 로직 담당 |
|
| View | InputView | - 사용자 입력 담당 - 구입 금액, 당첨 번호, 보너스 번호 입력 - 입력값의 형식 및 유효성 검증 |
| OutputView | - 모든 출력 담당 - 구매 내역, 당첨 통계, 수익률, 에러 메시지, 안내 문구 출력 |
|
| Controller | LottoController | - 전체 게임 흐름 제어 (입력 → 로또 발급 → 당첨/보너스 입력 → 결과 계산 → 출력) - Model과 View를 중개 - 입력값 검증 및 예외 발생 시 재입력 처리 |
| Application (Entry Point) | Application | - main() 메서드에서 LottoController를 생성하고 run() 실행 - 프로그램의 시작점 역할 |
이번 주차는 생각보다 다양한 Java의 오류와 구현 방법을 배울 수 있었다.
1. 정수 나눗셈 문제
첫째로 가장 오랜 시간 괴롭혔던 버그이다. 기능 구현을 마치고 테스트를 실행했는데, 수익률(Rate of Return) 테스트에서 계속 AssertionFailedError가 발생했다.
로직인 (총 당첨금 / 구매 금액) * 100 은 분명히 맞았다고 생각했는데. 하지만 원인은 정수 나눗셈 때문이었다. 실수로 double 캐스팅을 빼먹어서 결과가 정확하지 않았었는데, 이를 놓쳤었다.
Java는 C나 C++처럼 타입에 민감하기에 자료형 변환을 잘 처리해줬어야 했다.
// long winningMoney, long purchaseMoney
double rateOfReturn = (double)winningMoney / purchaseMoney * 100;
// 이전에는 아래와 같이 double 캐스팅이 없었음
// rateOfReturn = winningMoney / purchaseMoney * 100;
2. 얕은 복사
두 번째 버그는 NsTest의 특정 테스트 케이스에서만 발생했다. 로또를 여러 장 발급할 때, 로또의 리스트가 Null이라고 나오는 오류였다. 이 버그가 까다로웠던 이유는, 실제로 application을 실행해서 게임을 진행할 땐 문제가 없었다는 것이다.
문제가 발생했던 코드
Lotto 생성자에 lottoNumber 리스트의 참조를 그대로 넘겼다. Lotto 내부에서 sortingNumbers()를 호출하자, lottoNumber가 가리키는 원본 리스트 자체가 정렬되었다. 테스트 환경(NsTest)은 예측된 난수 리스트를 반환할 때 이 리스트 참조를 재활용하는 것으로 보였고, 결국 모든 Lotto 객체가 동일한 리스트 참조를 공유하게 되었다.
List<Integer> lottoNumber = lottoGenerator.generate();
Lotto lotto = new Lotto(lottoNumber); // 얕은 복사 (참조 전달)
lotto.sortingNumbers(); // <- 이 부분이 원본 리스트(lottoNumber)를 정렬시킴
lottoResult.addMyLotto(lotto);
해결 코드 (방어적 복사)
List<Integer> lottoNumber = lottoGenerator.generate();
// 생성자로 넘길 때 새로운 ArrayList로 감싸서 '깊은 복사' 수행
Lotto lotto = new Lotto(new ArrayList<>(lottoNumber));
lotto.sortingNumbers(); // 이제 내부 리스트만 정렬됨
lottoResult.addMyLotto(lotto);
3. Enum을 활용한 메서드 15줄 제한 극복
프로그램을 완성하고 검토를 하는 도중, 메서드의 길이가 15줄 이상이 넘지 않도록 구현해야하는 것과, Enum을 활용해볼 것이라는 피드백을 확인할 수 있었다. 또한 값을 하드코딩하지 않도록 구현해야 했는데 완성된 코드는 모두 위 조건들을 위배하고 있었다.
따라서 LottoType 이라는 Enum을 도입하여 해결하였다.
Enum을 도입하여 구현하였을때 장점은 다음과 같다.
- 장점 1 (책임 분리): 등수별 당첨 조건(matchCount, bonus), 상금(prizeMoney), 출력 메시지를 LottoType이 스스로 관리하게 했다.
- 장점 2 (로직 단순화): LottoResult에서는 LottoType.valueOf(일치개수, 보너스여부)를 호출하기만 하면 해당 등수를 바로 얻을 수 있었다.
4. TestCode 작성
이번 미션에서는 2주차보다 테스트 코드의 중요성을 훨씬 더 크게 느꼈다. 단순히 기능을 검증하는 것을 넘어, 구현하면서 놓친 치명적인 버그들을 테스트 과정에서 발견할 수 있었다.
제공된 단위 테스트(JUnit)와 통합 테스트(NsTest)에 새롭게 테스트 코드를 추가하여 다양한 오류를 검증할 수 있었다. 아래에 테스트에 활용했던 코드를 첨부하였다.
3주차는 2주차보다 난이도가 있었지만, 그만큼 테스트의 중요성과 객체지향 설계의 디테일(방어적 복사, Enum 활용)을 배울 수 있었다. 특히 공통 피드백에서 강조한 '메서드 15줄 제한', '값을 하드코딩하지 않기' 등을 의식적으로 지키려 노력했다.
다음주는 오픈미션인데 어떤 과제가 나올지 기대된다!

https://github.com/woowacourse-precourse/java-lotto-8/pull/71
[로또] 이동건 미션 제출합니다. by mvg01 · Pull Request #71 · woowacourse-precourse/java-lotto-8
주요 구현 기능 구입 금액 입력 및 검증 구입 금액을 입력받아 1,000원 단위인지 검증합니다. 숫자가 아니거나 1,000원 단위가 아닐 경우 IllegalArgumentException을 발생시키고, [ERROR] 메시지 출력 후 해
github.com
'우아한테크코스 8기 백엔드' 카테고리의 다른 글
| [우테코 8기 BE] 최종시험 후기 및 회고 (0) | 2026.01.24 |
|---|---|
| [우테코 8기 BE] 4 & 5 주차 오픈미션 (0) | 2025.11.24 |
| [우테코 8기 BE] 2주차 자동차 경주 리뷰 (0) | 2025.10.27 |
| [우테코 8기 BE] 1주차 문자열 덧셈 계산기 리뷰 (0) | 2025.10.18 |