이번 주 차 미션은 로또였습니다.
한 번도 사본적 없는 로또를 미션을 하면서 처음 구매해보네요.
기능 요구 사항은 다음과 같습니다.
기능 요구 사항
로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- 로또 1장의 가격은 1,000원이다.
- 당첨 번호와 보너스 번호를 입력받는다.
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.
입력 요구사항
- 로또 구입 금액을 입력받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
- 보너스 번호를 입력 받는다.
출력 요구사항
- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
- 당첨 내역을 출력한다.
- 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.
이번 미션에서 추가된 것으로 'Enum 클래스를 적용해 프로그래밍을 구현한다'라는 요구사항이 있었습니다.
enum 클래스는 유니티 게임을 만들 때 상품 목록 관리, 도로 유형 관리에 사용해보았는데요, 그 외에는 자주 사용한 적이 없었습니다. 로또에 enum 클래스를 적용하라니 어디에 적용할지 가장 고민을 많이 했던 것 같습니다.
저는 다음과 같이 로또의 상금을 enum 클래스로 관리하고, 상금과 일치하는 번호 개수를 인자로 주었습니다. 따로 RewardInfo 클래스를 만들어 해당 상금이 몇 번 당첨되었는지 관리하도록 했습니다.
package lotto.model
enum class Reward(val rewardMoney: String, var correctNum: String) {
FIFTH("5,000","3"),
Four("50,000","4"),
Third("1,500,000","5"),
Second("30,000,000","5 보너스"),
First("2,000,000,000","6"),
}
object RewardInfo {
var rewardResult = LinkedHashMap<Reward, Int>()
/* ... */
}
그동안 상수 관리를 따로 하지 않았었는데, 다른 분들을 보니 Constant 클래스를 만들어서 관리하시는 것 같더라고요. 그래서 저도 리팩터링 과정에서 util 패키지를 따로 만들어 Constant 클래스를 생성하여 관리하였습니다. 확실히 코드에 줄줄이 글씨가 없으니 깔끔한 것 같습니다.
리팩터링을 무엇을 하면 좋을까 생각하다가 꽤 많은 분들이 MVC 패턴을 적용하길래 저도 시도해보았습니다. 한 번도 디자인 패턴을 적용해본 적이 없어서 MVC의 각 model, view, controller가 무슨 역할을 하는지 부터 찾아보아야 했습니다. 패키지를 만들어 기존에 있던 클래스를 나누고 MVC패턴에 맞게 리팩토링을 하려니 바꿔야 할게 이것저것 많았습니다. 제가 잘 적용한 것인지는 모르겠지만 첫 디자인 패턴 적용이었습니다.
2주 차에는 테스트 케이스 작성을 신경 쓰지 않고 했는데 이번에는 간단한 테스트 케이스를 여러 개 작성해보았습니다.
그동안 print문을 찍거나 문제가 있으면 디버깅을 했었는데요, 테스트 케이스가 있으니 지웠던 print문을 다시 써서 작동을 확인하지 않아도 되니 편했습니다. 테스트 케이스 작성하는 순간은 귀찮지만 장기적으로 보았을 때는 개발이 필수적인 것 같아요. JUnit5 테스트를 잠깐 검색해보니 어노테이션도 다양하고 많았습니다. 다음에는 다른 것들도 적용시켜 봐야겠습니다.
class LottoTest {
@Test
fun `당첨 번호가 1~45 사이 숫자가 아니면 예외가 발생한다`() {
assertThrows<IllegalArgumentException> {
var lottoNumber = "1,2,18,23,44,50"
Validator.checkWinningNumber(lottoNumber)
}
}
@Test
fun `중복된 당첨 번호를 입력하면 예외가 발생한다`(){
assertThrows<IllegalArgumentException> {
var lottoNumber = "1,2,18,23,23,40"
Validator.checkWinningNumber(lottoNumber)
}
}
}
이번에 마지막까지 붙잡고 있던 예제 테스트가 있었는데요, 바로 이 테스트입니다.
@Test
fun `예외 테스트`() {
assertSimpleTest {
runException("1000j")
assertThat(output()).contains(ERROR_MESSAGE)
}
}
분명 잘못된 값을 입력했을 때 IllegalArgumentException이 발생하고, [ERROR]로 시작하는 에러 메시지도 띄웠지만 테스트가 통과되지 않았습니다. 이런 문제는 저만 겪는 것이 아니었는지 공통적으로 메일이 하나 도착했습니다.
이게 무슨 말일까 고민을 많이 했습니다. 에러를 먼저 띄우고 프로그램이 종료되어야 하는 것 같은데, 제 경우에는 정상적으로 종료가 되지 않아 발생하는 문제 같았습니다. 어떻게 해결하면 좋을까 하다가 main에서 프로그램을 종료할 수 있도록 try-catch문을 추가했습니다. 이 방법이 맞았는지 다행히 정상적으로 테스트가 수행되었습니다. 덕분에 throw로 발생한 에러는 정상적인 프로그램 종료가 아니라는 것을 알고 갑니다.
fun main() {
try {
var lottoController = LottoController()
lottoController.startLotto()
} catch (e : IllegalArgumentException) {
println(ERROR_EXIT)
}
}
이렇게 3주 차 미션도 끝이 났습니다!
열거형 클래스
enum class
- 연관성이 있는 상수를 모아 열거형으로 관리
- 코드가 단순해지고, 가독성이 좋아짐
예제1
enum class Color {
RED,
BLUE,
GREEN
}
fun main(args: Array<String>) {
for (color in Color.values()) when (color) {
Color.RED -> println("Red")
Color.BLUE -> println("Blue")
Color.GREEN -> println("Green")
}
}
예제2
enum class Reward(val rewardMoney: String, val correctNum: String) {
FIFTH("5,000","3"),
Four("50,000","4"),
Third("1,500,000","5"),
Second("30,000,000","5 보너스"),
First("2,000,000,000","6"),
}
fun main(args: Array<String>) {
val first = Reward.First
println("숫자 ${first.correctNum}개 일치 | 상금 : ${first.rewardMoney}원")
}
프로퍼티를 선언하여 접근할 수도 있습니다.
예제3
enum class Reward(val rewardMoney: String, val correctNum: String) {
FIFTH("5,000","3"),
Four("50,000","4"),
Third("1,500,000","5"),
Second("30,000,000","5 보너스"),
First("2,000,000,000","6");
fun printReward() {
println("숫자 ${correctNum}개 일치 | 상금 : ${rewardMoney}원")
}
}
fun main(args: Array<String>) {
for (reward in Reward.values()) {
reward.printReward()
}
}
enum 클래스에 함수를 선언하여 사용할 수도 있습니다.
'Android > 우아한테크코스 5기' 카테고리의 다른 글
[우아한테크코스 5기 프리코스] 안드로이드 4주 차 회고록 (0) | 2022.11.23 |
---|---|
[우아한테크코스 5기 프리코스] 안드로이드 2주차 회고록 (0) | 2022.11.16 |
[우아한테크코스 5기 프리코스] 안드로이드 1주차 회고록 (0) | 2022.11.08 |