Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step3 : 자동차 경주 #1512

Open
wants to merge 4 commits into
base: sendkite
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# kotlin-racingcar
# kotlin-racingcar

## 기능 요구사항
- 초간단 자동차 경주 게임을 구현한다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우이다.
- 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.


## 구현

- [ ] 자동차는 게임에 참가할 수 있다.
- [ ] 자동차는 전진 또는 멈출 수 있다.
- [ ] 자동차는 0~9 사이 무작위 값을 방아 4이상이면 1칸 전진
12 changes: 12 additions & 0 deletions src/main/kotlin/racingcar/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package racingcar

class Car(
var position: Int = 0,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금과 같은 구조라면 move를 호출하지 않고도 외부에서 가변인 position의 값을 바꿀 수 있을 것 같네요. 이 부분에 대해 고민해보시면 좋겠습니다.

var moveCondition: RandomNumberHolder
) {
fun move() {
if (moveCondition.getRandomNumber() >= 4) {
Comment on lines +5 to +8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추상화의 범위를 어떻게 잡을 지 정답은 없으나 여기서는 랜덤 넘버를 반환하는 것 보다는 움직일지 말지 여부를 결정하는 부분을 추상화하는게 어땠을까 하는 생각이 드네요. 이 부분에 대해서도 고민해보시면 좋겠습니다.

정답은 없으므로 변경하진 않으셔도 됩니다.

position++
}
}
}
27 changes: 27 additions & 0 deletions src/main/kotlin/racingcar/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar

data class InputView(
var gameCount: Int = 0,
var cars: List<Car> = listOf()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래 프로퍼티로 정의할 필요가 없을 것 같다고 말씀드리긴 했는데요, 가변 프로퍼티라 추가로 코멘트 남깁니다.
가변성과 관련해서는 아래 내용 한번 참고해보시면 좋겠습니다.

이펙티브 코틀린 아이템 1 - 가변성을 제한하라

Comment on lines +4 to +5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

둘 다 프로퍼티로 정의할 필요가 있을까요? 필요한 값을 바로 반환할 순 없을까요?

) {
fun inputGameCount() {
println("시도할 회수는 몇회인가요?")
val input = readln().toInt()
validate(input)
this.gameCount = input
}

fun inputCarNumber() {
println("자동차 대수는 몇 대 인가요?")
val input = readln().toInt()
validate(input)

this.cars = List(input) {
Car(moveCondition = RandomNumberHolderImpl())
}
}

fun validate(input: Int) {
require(input > 0) { throw IllegalArgumentException("음수는 입력할 수 없습니다.") }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require은 조건에 따른 결과가 맞지 않을 경우 IllegalArgumentException을 반환하기 때문에 예외 메시지만 남겨주시면 될 것 같습니다.

}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/racingcar/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar

class OutputView {
fun printResult() {
println("실행 결과")
}

fun printPosition(position: Int): String {
return "-".repeat(position)
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/racingcar/RacingGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingcar

class RacingGame(
val gameCount: Int,
val cars: List<Car> = listOf()
) {
fun start() {
OutputView().printResult()
repeat(gameCount) {
moveCars()
println()
}
}

private fun moveCars() {
cars.forEach { car ->
car.move()
println(OutputView().printPosition(car.position))
}
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/racingcar/RandomNumberHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar

interface RandomNumberHolder {

fun getRandomNumber(): Int
}
9 changes: 9 additions & 0 deletions src/main/kotlin/racingcar/RandomNumberHolderImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar

import java.util.Random

class RandomNumberHolderImpl : RandomNumberHolder {
override fun getRandomNumber(): Int {
return Random().nextInt(0, 9)
}
}
103 changes: 103 additions & 0 deletions src/test/kotlin/racingcar/RacingGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package racingcar

import io.kotest.matchers.shouldBe
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.ValueSource
import racingcar.mock.MoveForwardCondition
import racingcar.mock.StayCondition

class RacingGameTest {

@Test
fun `성공 - 게임횟수 자동차 수를 입력`() {

val inputView = InputView(
gameCount = 5,
cars = listOf(
Car(moveCondition = RandomNumberHolderImpl()),
Car(moveCondition = RandomNumberHolderImpl()),
Car(moveCondition = RandomNumberHolderImpl())

)
)

val racingGame = RacingGame(
inputView.gameCount,
inputView.cars
)

racingGame.gameCount shouldBe 5
racingGame.cars.size shouldBe 3
}

@ParameterizedTest
@ValueSource(ints = [-12, 0, -1])
fun `실패 - 0 이하의 게임횟수, 자동차 수 입력`(input: Int) {

assertThatThrownBy { InputView().validate(input) }
.isInstanceOf(IllegalArgumentException::class.java)
.hasMessage("음수는 입력할 수 없습니다.")
}

@ParameterizedTest
@ValueSource(ints = [4, 5, 6, 7, 8, 9])
fun `무작위 수 4 이상이면 자동차 전진`(num : Int) {

val car = Car(moveCondition = RandomNumberHolderImpl()).apply {
moveCondition = object : RandomNumberHolder {
override fun getRandomNumber(): Int {
return num
}
}
}

car.move()
car.position shouldBe 1
}

@ParameterizedTest
@ValueSource(ints = [1, 2, 3])
fun `무작위 수 4 이하면 정지`(num : Int) {

val car = Car(moveCondition = RandomNumberHolderImpl()).apply {
moveCondition = object : RandomNumberHolder {
override fun getRandomNumber(): Int {
return num
}
}
}

car.move()
car.position shouldBe 0
}

@ParameterizedTest
@CsvSource(value = ["1:-","2:--","3:---","4:----"], delimiter = ':' )
fun `자동차 이동 거리 출력`(input: Int, expected: String) {
OutputView().printPosition(input) shouldBe expected
}

@ParameterizedTest
@ValueSource(ints = [1, 2, 3, 4, 5])
fun `게임 결과 출력`(gameCount: Int) {
val inputView = InputView(
gameCount = gameCount,
cars = listOf(
Car(moveCondition = MoveForwardCondition()),
Car(moveCondition = StayCondition()),
)
)

val racingGame = RacingGame(
inputView.gameCount,
inputView.cars
)
racingGame.start()

racingGame.cars[0].position shouldBe gameCount
racingGame.cars[1].position shouldBe 0
}
}
10 changes: 10 additions & 0 deletions src/test/kotlin/racingcar/mock/MoveForwardCondition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package racingcar.mock

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 더블에서 mock은 호출에 대한 기대를 명세하고 내용에 따라 동작하도록 프로그래밍 된 객체를 의미합니다.

현재는 호출에 대한 기대를 정의하지 않고(어떤 메서드 호출 시 어떤 값이 반환될 지 정의하는 것) 정해진 값을 리턴하고 있으므로 Mock보다는 Stub에 가깝지 않나 하는 생각이 드네요.

자세한 내용은 아래 문서 한번 참고 부탁드립니다.

https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/


import racingcar.RandomNumberHolder

class MoveForwardCondition: RandomNumberHolder {

override fun getRandomNumber(): Int {
return 4
}
}
9 changes: 9 additions & 0 deletions src/test/kotlin/racingcar/mock/StayCondition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package racingcar.mock

import racingcar.RandomNumberHolder

class StayCondition: RandomNumberHolder {
override fun getRandomNumber(): Int {
return 3
}
}
2 changes: 1 addition & 1 deletion src/test/kotlin/study/StringTest.kt
Original file line number Diff line number Diff line change
@@ -18,4 +18,4 @@ class StringTest {
assertThat("\n".isBlank()).isTrue()
assertThat("""\n""".isBlank()).isFalse()
}
}
}