Skip to content

Commit

Permalink
refactor: InputView 유효성 검증 추가 (#18)
Browse files Browse the repository at this point in the history
* refactor: 문자열 캐싱

* test: InputView 테스트

* test: Amount 테스트 수정

* fix: 수동 로또 수가 음수인 경우 검증

* refactor: 상수 관리 방법 변경

* refactor: Amount 위치 변경

* feat: Number 클래스 구현 및 테스트

* test: InputViewTest 테스트 작성

* refactor: 린트 수정

* refactor: 린트 수정

* refactor: 변수 명 수정

---------

Co-authored-by: 이건창 <[email protected]>
  • Loading branch information
this-is-spear and geon-chang authored Oct 19, 2023
1 parent 3227453 commit 431c915
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 55 deletions.
5 changes: 4 additions & 1 deletion src/main/kotlin/model/lotto/LottoCount.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ 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,
val autoCount: Int,
) {
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 }
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/model/lotto/LottoNumber.kt
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/model/lotto/LottoNumbers.kt
Original file line number Diff line number Diff line change
@@ -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<LottoNumber>) {
companion object {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/model/money/Money.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package view

import model.money.Amount
import java.math.BigDecimal

fun <T> validInputView(
Expand All @@ -15,13 +14,18 @@ fun <T> validInputView(
}
}

private const val WHITE_SPACE = " "

class InputView {
fun requestAmount(): BigDecimal {
val amount = Amount(readln())
return amount.getBigDecimal()
}

fun requestLottoNumber(): List<Int> {
return readln().split(" ").map { it.toInt() }
return readln()
.split(WHITE_SPACE)
.map { Number(it) }
.map { it.getInt() }
}
}
14 changes: 14 additions & 0 deletions src/main/kotlin/view/Number.kt
Original file line number Diff line number Diff line change
@@ -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 }
}
13 changes: 6 additions & 7 deletions src/main/kotlin/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
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) {
println(message)
}

fun requestManualCount() {
println("수동으로 구매할 로또 수를 입력해 주세요.")
println(INPUT_MANUAL_INPUT_MESSAGE)
}

fun requestManualLottoNumber() {
println("로또 번호를 입력해 주세요. (공백으로 구분)")
println(INPUT_LOTTO_NUMBER_MESSAGE)
}
}
11 changes: 10 additions & 1 deletion src/test/kotlin/model/lotto/LottoCountTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<IllegalArgumentException> {
LottoCount.of(manualCount, money)
}
exception.message shouldBe NEGATIVE_NUMBER_ERROR_MESSAGE
}
}
},
)
2 changes: 0 additions & 2 deletions src/test/kotlin/model/lotto/LottoNumberTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 생성" - {
Expand Down
1 change: 0 additions & 1 deletion src/test/kotlin/model/lotto/LottoNumbersTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down
35 changes: 0 additions & 35 deletions src/test/kotlin/model/money/AmountTest.kt

This file was deleted.

47 changes: 47 additions & 0 deletions src/test/kotlin/view/AmountTest.kt
Original file line number Diff line number Diff line change
@@ -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<IllegalArgumentException> {
Amount(invalidAmount)
}
}
}

`when`("숫자가 포함된 문자열을 금액으로 입력하면") {
val invalidAmount = "3k34j4"
then("IllegalArgumentException 예외가 발생한다") {
shouldThrowExactly<IllegalArgumentException> {
Amount(invalidAmount)
}
}
}

`when`("비어있는 문자열을 금액으로 입력하면") {
val invalidAmount = ""
then("IllegalArgumentException 예외가 발생한다") {
shouldThrowExactly<IllegalArgumentException> {
Amount(invalidAmount)
}
}
}
}
},
)
24 changes: 24 additions & 0 deletions src/test/kotlin/view/InputViewTest.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
}
},
)
46 changes: 46 additions & 0 deletions src/test/kotlin/view/NumberTest.kt
Original file line number Diff line number Diff line change
@@ -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<IllegalArgumentException> {
Number(invalidNumber)
}
}
}

`when`("숫자가 포함되지 않은 문자열을 입력하면") {
val invalidNumber = "invalid Number"
then("예외가 발생한다.") {
shouldThrowExactly<IllegalArgumentException> {
Number(invalidNumber)
}
}
}

`when`("숫자와 문자가 포함된 문자열을 입력하면") {
val invalidNumber = "d3"
then("예외가 발생한다.") {
shouldThrowExactly<IllegalArgumentException> {
Number(invalidNumber)
}
}
}
}
},
)

0 comments on commit 431c915

Please sign in to comment.