From 431c9157f54241268e6ae73ac1a7054bd10036c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=EC=B0=BD?= <92219795+this-is-spear@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:29:26 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20InputView=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20(#18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 문자열 캐싱 * test: InputView 테스트 * test: Amount 테스트 수정 * fix: 수동 로또 수가 음수인 경우 검증 * refactor: 상수 관리 방법 변경 * refactor: Amount 위치 변경 * feat: Number 클래스 구현 및 테스트 * test: InputViewTest 테스트 작성 * refactor: 린트 수정 * refactor: 린트 수정 * refactor: 변수 명 수정 --------- Co-authored-by: 이건창 --- src/main/kotlin/model/lotto/LottoCount.kt | 5 +- src/main/kotlin/model/lotto/LottoNumber.kt | 2 +- src/main/kotlin/model/lotto/LottoNumbers.kt | 2 +- src/main/kotlin/model/money/Money.kt | 2 +- .../kotlin/{model/money => view}/Amount.kt | 6 +-- src/main/kotlin/view/InputView.kt | 8 +++- src/main/kotlin/view/Number.kt | 14 ++++++ src/main/kotlin/view/OutputView.kt | 13 +++-- src/test/kotlin/model/lotto/LottoCountTest.kt | 11 ++++- .../kotlin/model/lotto/LottoNumberTest.kt | 2 - .../kotlin/model/lotto/LottoNumbersTest.kt | 1 - src/test/kotlin/model/money/AmountTest.kt | 35 -------------- src/test/kotlin/view/AmountTest.kt | 47 +++++++++++++++++++ src/test/kotlin/view/InputViewTest.kt | 24 ++++++++++ src/test/kotlin/view/NumberTest.kt | 46 ++++++++++++++++++ 15 files changed, 163 insertions(+), 55 deletions(-) rename src/main/kotlin/{model/money => view}/Amount.kt (66%) create mode 100644 src/main/kotlin/view/Number.kt delete mode 100644 src/test/kotlin/model/money/AmountTest.kt create mode 100644 src/test/kotlin/view/AmountTest.kt create mode 100644 src/test/kotlin/view/InputViewTest.kt create mode 100644 src/test/kotlin/view/NumberTest.kt diff --git a/src/main/kotlin/model/lotto/LottoCount.kt b/src/main/kotlin/model/lotto/LottoCount.kt index fb5568a..1303cc6 100644 --- a/src/main/kotlin/model/lotto/LottoCount.kt +++ b/src/main/kotlin/model/lotto/LottoCount.kt @@ -2,7 +2,9 @@ package model.lotto import model.money.Money import model.money.PRICE -import view.NOT_ENOUGH_MONEY_MESSAGE + +internal const val NEGATIVE_NUMBER_ERROR_MESSAGE = "입력한 값은 숫자여야 합니다." +internal const val NOT_ENOUGH_MONEY_MESSAGE = "금액이 부족합니다" class LottoCount( val manualCount: Int, @@ -10,6 +12,7 @@ class LottoCount( ) { companion object { fun of(manualCount: Int, money: Money): LottoCount { + require(manualCount >= 0) { NEGATIVE_NUMBER_ERROR_MESSAGE } val totalCount = money / PRICE val autoCount = totalCount - manualCount check(autoCount >= 0) { NOT_ENOUGH_MONEY_MESSAGE } diff --git a/src/main/kotlin/model/lotto/LottoNumber.kt b/src/main/kotlin/model/lotto/LottoNumber.kt index a219ffe..1523cf3 100644 --- a/src/main/kotlin/model/lotto/LottoNumber.kt +++ b/src/main/kotlin/model/lotto/LottoNumber.kt @@ -1,6 +1,6 @@ package model.lotto -private const val INVALID_LOTTO_NUMBER_MESSAGE = "로또 번호는 1에서 45 사이여야 합니다." +internal const val INVALID_LOTTO_NUMBER_MESSAGE = "로또 번호는 1에서 45 사이여야 합니다." data class LottoNumber(private val value: Int) { init { diff --git a/src/main/kotlin/model/lotto/LottoNumbers.kt b/src/main/kotlin/model/lotto/LottoNumbers.kt index a5a45f2..9990678 100644 --- a/src/main/kotlin/model/lotto/LottoNumbers.kt +++ b/src/main/kotlin/model/lotto/LottoNumbers.kt @@ -1,6 +1,6 @@ package model.lotto -import view.INVALID_LOTTO_NUMBER_COUNT_MESSAGE +internal const val INVALID_LOTTO_NUMBER_COUNT_MESSAGE = "로또 번호는 6개여야 합니다." class LottoNumbers private constructor(private val numbers: List) { companion object { diff --git a/src/main/kotlin/model/money/Money.kt b/src/main/kotlin/model/money/Money.kt index 83c6907..9d60396 100644 --- a/src/main/kotlin/model/money/Money.kt +++ b/src/main/kotlin/model/money/Money.kt @@ -2,7 +2,7 @@ package model.money import java.math.BigDecimal -private const val NON_NEGATIVE_NUMBER_ERROR_MESSAGE = "금액은 0원 이상이어야 합니다." +internal const val NON_NEGATIVE_NUMBER_ERROR_MESSAGE = "금액은 0원 이상이어야 합니다." val PRICE = Money(BigDecimal.valueOf(5000)) class Money(private val amount: BigDecimal) { diff --git a/src/main/kotlin/model/money/Amount.kt b/src/main/kotlin/view/Amount.kt similarity index 66% rename from src/main/kotlin/model/money/Amount.kt rename to src/main/kotlin/view/Amount.kt index ecb8cb7..49a533a 100644 --- a/src/main/kotlin/model/money/Amount.kt +++ b/src/main/kotlin/view/Amount.kt @@ -1,7 +1,7 @@ -package model.money +package view -import view.EMPTY_VALUE_ERROR_MESSAGE -import view.INVALID_NUMBER_FORMAT_MESSAGE +internal const val EMPTY_VALUE_ERROR_MESSAGE = "값이 비어있습니다." +internal const val INVALID_NUMBER_FORMAT_MESSAGE = "입력한 값은 숫자여야 합니다." class Amount(private val stringAmount: String) { private val integers = '0'..'9' diff --git a/src/main/kotlin/view/InputView.kt b/src/main/kotlin/view/InputView.kt index a78c20d..94a03b1 100644 --- a/src/main/kotlin/view/InputView.kt +++ b/src/main/kotlin/view/InputView.kt @@ -1,6 +1,5 @@ package view -import model.money.Amount import java.math.BigDecimal fun validInputView( @@ -15,6 +14,8 @@ fun validInputView( } } +private const val WHITE_SPACE = " " + class InputView { fun requestAmount(): BigDecimal { val amount = Amount(readln()) @@ -22,6 +23,9 @@ class InputView { } fun requestLottoNumber(): List { - return readln().split(" ").map { it.toInt() } + return readln() + .split(WHITE_SPACE) + .map { Number(it) } + .map { it.getInt() } } } diff --git a/src/main/kotlin/view/Number.kt b/src/main/kotlin/view/Number.kt new file mode 100644 index 0000000..2c7406a --- /dev/null +++ b/src/main/kotlin/view/Number.kt @@ -0,0 +1,14 @@ +package view + +class Number(private val stringNumber: String) { + private val integers = '0'..'9' + + init { + require(stringNumber.isNotBlank()) { "번호가 비어있을 수 없습니다." } + require(stringNumber.isInt()) { INVALID_NUMBER_FORMAT_MESSAGE } + } + + fun getInt() = stringNumber.toInt() + + private fun String.isInt() = this.all { it in integers } +} diff --git a/src/main/kotlin/view/OutputView.kt b/src/main/kotlin/view/OutputView.kt index 8f4105f..8d352a2 100644 --- a/src/main/kotlin/view/OutputView.kt +++ b/src/main/kotlin/view/OutputView.kt @@ -1,13 +1,12 @@ package view -const val EMPTY_VALUE_ERROR_MESSAGE = "값이 비어있습니다." -const val INVALID_NUMBER_FORMAT_MESSAGE = "입력한 값은 숫자여야 합니다." -const val NOT_ENOUGH_MONEY_MESSAGE = "금액이 부족합니다" -const val INVALID_LOTTO_NUMBER_COUNT_MESSAGE = "로또 번호는 6개여야 합니다." +internal const val INPUT_MONEY_MESSAGE = "구입금액을 입력해 주세요." +internal const val INPUT_MANUAL_INPUT_MESSAGE = "수동으로 구매할 로또 수를 입력해 주세요." +internal const val INPUT_LOTTO_NUMBER_MESSAGE = "로또 번호를 입력해 주세요. (공백으로 구분)" class OutputView { fun requestCapital() { - println("구입금액을 입력해 주세요.") + println(INPUT_MONEY_MESSAGE) } fun printMessage(message: String) { @@ -15,10 +14,10 @@ class OutputView { } fun requestManualCount() { - println("수동으로 구매할 로또 수를 입력해 주세요.") + println(INPUT_MANUAL_INPUT_MESSAGE) } fun requestManualLottoNumber() { - println("로또 번호를 입력해 주세요. (공백으로 구분)") + println(INPUT_LOTTO_NUMBER_MESSAGE) } } diff --git a/src/test/kotlin/model/lotto/LottoCountTest.kt b/src/test/kotlin/model/lotto/LottoCountTest.kt index 55accdd..e5ad2af 100644 --- a/src/test/kotlin/model/lotto/LottoCountTest.kt +++ b/src/test/kotlin/model/lotto/LottoCountTest.kt @@ -5,7 +5,6 @@ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import model.money.Money import model.money.PRICE -import view.NOT_ENOUGH_MONEY_MESSAGE import java.math.BigDecimal class LottoCountTest : FreeSpec( @@ -46,6 +45,16 @@ class LottoCountTest : FreeSpec( lottoCount.manualCount shouldBe 0 lottoCount.autoCount shouldBe 3 } + + "수동 로또 수가 음수면 에러가 발생한다" { + val money = PRICE * 3 + val manualCount = -1 + val exception = + shouldThrow { + LottoCount.of(manualCount, money) + } + exception.message shouldBe NEGATIVE_NUMBER_ERROR_MESSAGE + } } }, ) diff --git a/src/test/kotlin/model/lotto/LottoNumberTest.kt b/src/test/kotlin/model/lotto/LottoNumberTest.kt index 93a4802..285c6a4 100644 --- a/src/test/kotlin/model/lotto/LottoNumberTest.kt +++ b/src/test/kotlin/model/lotto/LottoNumberTest.kt @@ -4,8 +4,6 @@ import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe -private const val INVALID_LOTTO_NUMBER_MESSAGE = "로또 번호는 1에서 45 사이여야 합니다." - class LottoNumberTest : FreeSpec( { "LottoNumber 생성" - { diff --git a/src/test/kotlin/model/lotto/LottoNumbersTest.kt b/src/test/kotlin/model/lotto/LottoNumbersTest.kt index c5f3b5e..1c0072e 100644 --- a/src/test/kotlin/model/lotto/LottoNumbersTest.kt +++ b/src/test/kotlin/model/lotto/LottoNumbersTest.kt @@ -3,7 +3,6 @@ package model.lotto import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe -import view.INVALID_LOTTO_NUMBER_COUNT_MESSAGE class LottoNumbersTest : DescribeSpec( { diff --git a/src/test/kotlin/model/money/AmountTest.kt b/src/test/kotlin/model/money/AmountTest.kt deleted file mode 100644 index a69bd7c..0000000 --- a/src/test/kotlin/model/money/AmountTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package model.money - -import io.kotest.assertions.throwables.shouldThrowExactly -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import java.math.BigDecimal - -class AmountTest : FunSpec( - { - listOf( - "2400", - "3000", - "4000", - ).forEach { - test("금액은 입력받으면 BigDecimal 자료형을 반환한다. 입력받은 금액 : $it") { - Amount(it).getBigDecimal() shouldBe BigDecimal(it) - } - } - - listOf( - "invalid amount", - "유효하지 않은 숫자", - "3k34j4", - "k3333", - "3333k", - "", - ).forEach { - test("잘못된 문자열을 입력받으면 예외가 발생한다. 입력받은 문자 : $it") { - shouldThrowExactly { - Amount(it) - } - } - } - }, -) diff --git a/src/test/kotlin/view/AmountTest.kt b/src/test/kotlin/view/AmountTest.kt new file mode 100644 index 0000000..0e0725d --- /dev/null +++ b/src/test/kotlin/view/AmountTest.kt @@ -0,0 +1,47 @@ +package view + +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import java.math.BigDecimal + +class AmountTest : BehaviorSpec( + { + + given("금액을 입력받을 때") { + `when`("숫자를 금액으로 입력하면") { + val amount = "2400" + then("금액에 맞는 BigDecimal 자료형이 반환된다.") { + Amount(amount).getBigDecimal() shouldBe BigDecimal(amount) + } + } + + `when`("문자열을 금액으로 입력하면") { + val invalidAmount = "invalid amount" + then("IllegalArgumentException 예외가 발생한다") { + shouldThrowExactly { + Amount(invalidAmount) + } + } + } + + `when`("숫자가 포함된 문자열을 금액으로 입력하면") { + val invalidAmount = "3k34j4" + then("IllegalArgumentException 예외가 발생한다") { + shouldThrowExactly { + Amount(invalidAmount) + } + } + } + + `when`("비어있는 문자열을 금액으로 입력하면") { + val invalidAmount = "" + then("IllegalArgumentException 예외가 발생한다") { + shouldThrowExactly { + Amount(invalidAmount) + } + } + } + } + }, +) diff --git a/src/test/kotlin/view/InputViewTest.kt b/src/test/kotlin/view/InputViewTest.kt new file mode 100644 index 0000000..458d534 --- /dev/null +++ b/src/test/kotlin/view/InputViewTest.kt @@ -0,0 +1,24 @@ +package view + +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.collections.shouldContainAll +import java.io.ByteArrayInputStream + +private const val WHITE_SPACE = " " + +class InputViewTest : BehaviorSpec( + { + given("사용자의 입력을 받을 때") { + val inputView = InputView() + val strings = "12 32 2 1 4 5" + val expected = strings.split(WHITE_SPACE).map { it.toInt() } + `when`("$strings 번호를 입력하면") { + System.setIn(ByteArrayInputStream(strings.toByteArray())) + val actual = inputView.requestLottoNumber() + then("$expected 리스트가 반환된다.") { + actual shouldContainAll expected + } + } + } + }, +) diff --git a/src/test/kotlin/view/NumberTest.kt b/src/test/kotlin/view/NumberTest.kt new file mode 100644 index 0000000..3960860 --- /dev/null +++ b/src/test/kotlin/view/NumberTest.kt @@ -0,0 +1,46 @@ +package view + +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe + +class NumberTest : BehaviorSpec( + { + given("로또 번호를 입력 할 때") { + `when`("숫자를 로또 번호로 입력하면") { + val stringNumber = "23" + val number = Number(stringNumber) + then("올바른 숫자를 반환한다.") { + number.getInt() shouldBe stringNumber.toInt() + } + } + + `when`("비어있는 문자열을 입력하면") { + val invalidNumber = "" + then("예외가 발생한다.") { + shouldThrowExactly { + Number(invalidNumber) + } + } + } + + `when`("숫자가 포함되지 않은 문자열을 입력하면") { + val invalidNumber = "invalid Number" + then("예외가 발생한다.") { + shouldThrowExactly { + Number(invalidNumber) + } + } + } + + `when`("숫자와 문자가 포함된 문자열을 입력하면") { + val invalidNumber = "d3" + then("예외가 발생한다.") { + shouldThrowExactly { + Number(invalidNumber) + } + } + } + } + }, +)