Android/우아한테크코스 5기

[우아한테크코스 5기 프리코스] 안드로이드 3주차 회고록

Chef.Yeon 2022. 11. 23. 19:19

이번 주 차 미션은 로또였습니다.

한 번도 사본적 없는 로또를 미션을 하면서 처음 구매해보네요.

 

기능 요구 사항은 다음과 같습니다.

 


 

기능 요구 사항

로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.

- 로또 번호의 숫자 범위는 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]"로 시작해야 한다.
728x90

 

이번 미션에서 추가된 것으로  '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 클래스에 함수를 선언하여 사용할 수도 있습니다.

728x90