From a59f19be10537d1dcb6b8f6d406e47164c8d4c16 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Fri, 25 Nov 2022 19:46:46 +0900 Subject: [PATCH 01/15] =?UTF-8?q?typo:=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AC=B8=EC=84=9C=20=EB=82=B4=EC=9A=A9=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/{SETP3-README.md => STEP3-README.md} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename docs/{SETP3-README.md => STEP3-README.md} (95%) diff --git a/docs/SETP3-README.md b/docs/STEP3-README.md similarity index 95% rename from docs/SETP3-README.md rename to docs/STEP3-README.md index e7b0448cba..02bd3b67d1 100644 --- a/docs/SETP3-README.md +++ b/docs/STEP3-README.md @@ -57,11 +57,11 @@ * [x] Service 계층에 위치한 난수 생성부 분리 * Service 계층에 위치한 난수 생성부로 인해 해당 계층이 테스트 하기 어려운 문제를 개선하기 위해 난수 생성부를 Service 계층 분리 후, 외부에서 생성된 난수를 각 `Car`객체에 주입하여 해당 계층을 테스트 가능하도록 수정 * 난수 생성부 위치 변경 - * AS*IS : `Car` 객체에서 경주 진행 메서드 호출 시, 난수를 생성 - * TO*BE : Car 객체 생성 시, 진행 라운드 만큼 난수 생성 후 Car 객체 필드에 주입 + * AS-IS : `Car` 객체에서 경주 진행 메서드 호출 시, 난수를 생성 + * TO-BE : Car 객체 생성 시, 진행 라운드 만큼 난수 생성 후 Car 객체 필드에 주입 * 각 차량의 자동차 경주 진행 방식 변경 - * AS*IS : 각 차량에서 현재 라운드 진행을 위해 난수 생성 및 생성된 난수에 따른 진행거리 누적 여부 판별 - * TO*BE : 외부에서 각 차량에 진행 라운드 만큼 아미 생성된 난수를 판별하여 진행 거리 누적 여부만 판별 + * AS-IS : 각 차량에서 현재 라운드 진행을 위해 난수 생성 및 생성된 난수에 따른 진행거리 누적 여부 판별 + * TO-BE : 외부에서 각 차량에 진행 라운드 만큼 아미 생성된 난수를 판별하여 진행 거리 누적 여부만 판별 * 로직 변경으로 인해 깨지는 TC 수정 * 생성된 난수 목록 일급 컬렉션 정의 * 진행 라운드 만큼 생성된 난수 목록이 주입되는 일급 컬렉션 From b6ac70f894bab2d0f716aef84b5fad6ce4c68112 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Fri, 25 Nov 2022 19:47:02 +0900 Subject: [PATCH 02/15] =?UTF-8?q?docs:=20step4=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=B0=A8=20=EA=B2=BD=EC=A3=BC(=EC=9A=B0=EC=8A=B9=EC=9E=90)=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=82=B4=EC=9A=A9=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/STEP4-README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 9c6003b265..7a01179ff3 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -32,3 +32,23 @@ * 우승자 도메인 정의 * 경주 완료 후 자동차 객체 목록을 통해 우승자 객체 생성 및 우승자 목록 추출 * 경주를 완료한 자동차 객체 목록을 통해 우승한 차량의 이름 추출 TC 작성 + +## 코드 리뷰 피드백 내용 정리 +* [ ] 각 라운드 진행 방식 변경 + * AS-IS : `Car` 객체에 미리 생성된 난수를 판별하여 주행거리를 누적 + * TO-BE : 매 라운드 마다 난수를 주입받아 판별한 후, 차량에 주행거리를 누적 +* [ ] 별도의 역할 없이 객체를 포장하고 있는 `Cars`일급 컬렉션과 참가한 자동차 목록으로 부터 우승자를 산출하는 `Winners` 일급 컬렉션 역할을 병합 +* 테스트 표현 개선 + * [ ] 도메인 로직을 활용한 테스트 결과 검증 부를 원하는 결과값을 명시적으로 하드코딩 + * 도메인 로직 변경 시 도메인 로직을 이용한 해당 테스트는 통과되므로 오류를 검증하는 테스트로써의 역할을 수행할 수 없는 경우 발생 가능 + * AS-IS : + ```kotlin + + ``` + * TO-BE : + ```kotlin + + ``` + * [ ] (테스트 픽스처 활용으로 인해 테스트 코드가 결합되는 현상 개선)[https://jojoldu.tistory.com/611] +* [ ] `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드를 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체 객체로 분리 + * 외부에서 해당 객체에 값에 접근하는 처리 방식에서 객체에 메시지를 보내 그 결과값을 반환받는 방식으로 수정 From 7a4fee471a330d28132da12e183d77a14c54fa31 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Mon, 28 Nov 2022 15:02:36 +0900 Subject: [PATCH 03/15] =?UTF-8?q?refactor:=20=EC=9A=B0=EC=8A=B9=EC=9E=90?= =?UTF-8?q?=20=EC=82=B0=EC=B6=9C=20=EA=B0=9D=EC=B2=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 별도의 역할 없이 객체를 포장하고 있는 `Cars`일급 컬렉션과 참가한 자동차 목록으로 부터 우승자를 산출하는 `Winners` 일급 컬렉션 역할을 병합 --- docs/STEP4-README.md | 2 +- .../kotlin/step3/racingcar/domain/Cars.kt | 15 +++++++++++++ .../kotlin/step3/racingcar/domain/Winners.kt | 22 ------------------- .../racingcar/service/RacingCarService.kt | 3 +-- .../kotlin/step3/racingcar/view/ResultView.kt | 5 ++--- .../kotlin/step3/racingcar/domain/CarsTest.kt | 21 ++++++++++++++++++ .../step3/racingcar/domain/WinnersTest.kt | 21 ------------------ 7 files changed, 40 insertions(+), 49 deletions(-) delete mode 100644 src/main/kotlin/step3/racingcar/domain/Winners.kt create mode 100644 src/test/kotlin/step3/racingcar/domain/CarsTest.kt delete mode 100644 src/test/kotlin/step3/racingcar/domain/WinnersTest.kt diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 7a01179ff3..93db4d0059 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -34,10 +34,10 @@ * 경주를 완료한 자동차 객체 목록을 통해 우승한 차량의 이름 추출 TC 작성 ## 코드 리뷰 피드백 내용 정리 +* [x] 별도의 역할 없이 객체를 포장하고 있는 `Cars`일급 컬렉션과 참가한 자동차 목록으로 부터 우승자를 산출하는 `Winners` 일급 컬렉션 역할을 병합 * [ ] 각 라운드 진행 방식 변경 * AS-IS : `Car` 객체에 미리 생성된 난수를 판별하여 주행거리를 누적 * TO-BE : 매 라운드 마다 난수를 주입받아 판별한 후, 차량에 주행거리를 누적 -* [ ] 별도의 역할 없이 객체를 포장하고 있는 `Cars`일급 컬렉션과 참가한 자동차 목록으로 부터 우승자를 산출하는 `Winners` 일급 컬렉션 역할을 병합 * 테스트 표현 개선 * [ ] 도메인 로직을 활용한 테스트 결과 검증 부를 원하는 결과값을 명시적으로 하드코딩 * 도메인 로직 변경 시 도메인 로직을 이용한 해당 테스트는 통과되므로 오류를 검증하는 테스트로써의 역할을 수행할 수 없는 경우 발생 가능 diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index 57ab2f6e2b..45637c6e11 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -1,11 +1,26 @@ package step3.racingcar.domain class Cars private constructor(private val elements: List) { + val winnerNames: String = formatToWinnerNames() + fun elements(): List = elements + fun size(): Int = elements.size + operator fun get(index: Int) = elements[index] + private fun formatToWinnerNames(): String = + findWinnerNames().joinToString(WINNER_NAME_JOINING_SEPARATOR) + + private fun findWinnerNames() = + elements() + .filter { it.distance == findMaxDistance() } + .map { it.name } + + private fun findMaxDistance() = elements().maxOf { it.distance } + companion object { + private const val WINNER_NAME_JOINING_SEPARATOR = ", " fun of(elements: List) = Cars(elements) } } diff --git a/src/main/kotlin/step3/racingcar/domain/Winners.kt b/src/main/kotlin/step3/racingcar/domain/Winners.kt deleted file mode 100644 index 425e624a4a..0000000000 --- a/src/main/kotlin/step3/racingcar/domain/Winners.kt +++ /dev/null @@ -1,22 +0,0 @@ -package step3.racingcar.domain - -class Winners private constructor(cars: Cars) { - val names: String = formatToWinnerNames(cars, findMaxDistance(cars)) - - private fun findMaxDistance(cars: Cars) = cars.elements().maxOf { it.distance } - - private fun formatToWinnerNames(cars: Cars, maxDistance: Int): String { - - return findWinnerNames(cars, maxDistance).joinToString(WINNER_NAME_JOINING_SEPARATOR) - } - - private fun findWinnerNames(cars: Cars, maxDistance: Int) = - cars.elements() - .filter { it.distance == maxDistance } - .map { it.name } - - companion object { - private const val WINNER_NAME_JOINING_SEPARATOR = ", " - fun of(cars: Cars): Winners = Winners(cars) - } -} diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index ed23e09a87..85d9e321fd 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -2,7 +2,6 @@ package step3.racingcar.service import step3.racingcar.domain.Cars import step3.racingcar.domain.PlayInfo -import step3.racingcar.domain.Winners import step3.racingcar.view.ResultView.Companion.printRoundResult import step3.racingcar.view.ResultView.Companion.printWinner @@ -11,7 +10,7 @@ class RacingCarService { repeat(playInfo.totalRound) { playEachRound(it, playInfo.cars) } - printWinner(Winners.of(playInfo.cars)) + printWinner(playInfo.cars) } private fun playEachRound(currentRoundIndex: Int, cars: Cars) { diff --git a/src/main/kotlin/step3/racingcar/view/ResultView.kt b/src/main/kotlin/step3/racingcar/view/ResultView.kt index 42c6b88454..db768d5b08 100644 --- a/src/main/kotlin/step3/racingcar/view/ResultView.kt +++ b/src/main/kotlin/step3/racingcar/view/ResultView.kt @@ -2,7 +2,6 @@ package step3.racingcar.view import step3.racingcar.domain.Car import step3.racingcar.domain.Cars -import step3.racingcar.domain.Winners class ResultView { companion object { @@ -32,9 +31,9 @@ class ResultView { return result } - fun printWinner(winners: Winners) { + fun printWinner(cars: Cars) { println() - println(WINNER_GUIDE_MESSAGE_FORMAT.format(winners.names)) + println(WINNER_GUIDE_MESSAGE_FORMAT.format(cars.winnerNames)) } } } diff --git a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt new file mode 100644 index 0000000000..ae7c3cd7b2 --- /dev/null +++ b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt @@ -0,0 +1,21 @@ +package step3.racingcar.domain + +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import step3.racingcar.fixture.CarFixtureGenerator.Companion.경주를_완료한_차량_생성 + +internal class CarsTest : BehaviorSpec({ + given("경주를 완료한 자동차 객체 목록이 주어지고") { + val 첫_번째_차량 = 경주를_완료한_차량_생성("첫 번째 차량", 1, 1, 1, 4, 4) + val 두_번째_차량 = 경주를_완료한_차량_생성("두 번째 차량", 1, 1, 4, 4, 4) + val 세_번째_차량 = 경주를_완료한_차량_생성("세 번째 차량", 4, 4, 4, 1, 1) + + `when`("우승자 객체를 생성하면") { + val 참가_차량_목록 = listOf(첫_번째_차량, 두_번째_차량, 세_번째_차량) + val given = Cars.of(참가_차량_목록) + then("우승한 차량들의 이름을 반환한다.") { + given.winnerNames shouldBe "${두_번째_차량.name}, ${세_번째_차량.name}" + } + } + } +}) diff --git a/src/test/kotlin/step3/racingcar/domain/WinnersTest.kt b/src/test/kotlin/step3/racingcar/domain/WinnersTest.kt deleted file mode 100644 index 5f840b88fb..0000000000 --- a/src/test/kotlin/step3/racingcar/domain/WinnersTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package step3.racingcar.domain - -import io.kotest.core.spec.style.BehaviorSpec -import io.kotest.matchers.shouldBe -import step3.racingcar.fixture.CarFixtureGenerator - -internal class WinnersTest : BehaviorSpec({ - given("경주를 완료한 자동차 객체 목록이 주어지고") { - val 첫_번째_차량 = CarFixtureGenerator.경주를_완료한_차량_생성("첫 번째 차량", 1, 1, 1, 4, 4) - val 두_번째_차량 = CarFixtureGenerator.경주를_완료한_차량_생성("두 번째 차량", 1, 1, 4, 4, 4) - val 세_번째_차량 = CarFixtureGenerator.경주를_완료한_차량_생성("세 번째 차량", 4, 4, 4, 1, 1) - - `when`("우승자 객체를 생성하면") { - val joinerCars = Cars.of(listOf(첫_번째_차량, 두_번째_차량, 세_번째_차량)) - val given = Winners.of(joinerCars) - then("우승한 차량들의 이름을 반환한다.") { - given.names shouldBe "${두_번째_차량.name}, ${세_번째_차량.name}" - } - } - } -}) From 9f3930b609de2e9ef7fa35ca97c71208f3b4fdd9 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Mon, 28 Nov 2022 16:12:22 +0900 Subject: [PATCH 04/15] =?UTF-8?q?refactor:=20=EC=B6=94=EC=83=81=ED=99=94?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=EB=82=9C=EC=88=98=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=96=B4=EB=A0=A4=EC=9A=B4=20=ED=98=84=EC=83=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 난수 발생 부를 추상화하여 운영 환경에 주입되는 구현체와 테스트 환경에 주입되는 구현체를 별도로 구현한 뒤, 각 실행 환경에 맞는 구현체를 선택하여 주입함으로써 난수로 인해 테스트 하기 어려운 문제 개선 - RandomNumberGenerator : 운영 환경에서 1~9 범위의 난수를 발생하는 구현체 - TestRandomNumberGenerator : 테스트 환경에서 의도한 수를 반환하는 구현체 --- docs/STEP4-README.md | 10 +++++++--- .../racingcar/controller/RacingCarController.kt | 4 ++-- .../kotlin/step3/racingcar/domain/RandomNumber.kt | 10 ++++++++++ .../step3/racingcar/utils/RandomNumberGenerator.kt | 12 +++++++----- .../racingcar/fixture/TestRandomNumberGenerator.kt | 7 +++++++ 5 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/step3/racingcar/domain/RandomNumber.kt create mode 100644 src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 93db4d0059..69bb315f13 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -37,7 +37,13 @@ * [x] 별도의 역할 없이 객체를 포장하고 있는 `Cars`일급 컬렉션과 참가한 자동차 목록으로 부터 우승자를 산출하는 `Winners` 일급 컬렉션 역할을 병합 * [ ] 각 라운드 진행 방식 변경 * AS-IS : `Car` 객체에 미리 생성된 난수를 판별하여 주행거리를 누적 - * TO-BE : 매 라운드 마다 난수를 주입받아 판별한 후, 차량에 주행거리를 누적 + * TO-BE : 매 라운드 마다 발생된 난수를 판별한 후, 차량 주행거리를 누적 + * [x] 추상화를 통해 난수에 대한 의존성으로 테스트 하기 어려운 현상 개선 + * [ ] 난수 발생 부를 추상화하여 운영 환경에 주입되는 구현체와 테스트 환경에 주입되는 구현체를 별도로 구현한 뒤, 각 실행 환경에 맞는 구현체를 선택하여 주입함으로써 난수로 인해 테스트 하기 어려운 문제 개선 + * [ ] 자동차 경주를 진행하는 `RacingService` 객체에 추상화된 난수 생성 부의 구현체를 주입하여 생성한 후, 각 라운드 진행 시 운영 환경에서는 실제 난수 생성 구현체가 동작하고, 테스트 환경에서는 난수 발생 시 의도한 수를 반환하는 구현체가 동작할 수 있도록 수정 + * 난수 생성 부 추상화에 따른 `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드 제거 + * [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 + * 외부에서 해당 객체에 값에 접근하는 처리 방식에서 객체에 메시지를 보내 그 결과값을 반환받는 방식으로 수정 * 테스트 표현 개선 * [ ] 도메인 로직을 활용한 테스트 결과 검증 부를 원하는 결과값을 명시적으로 하드코딩 * 도메인 로직 변경 시 도메인 로직을 이용한 해당 테스트는 통과되므로 오류를 검증하는 테스트로써의 역할을 수행할 수 없는 경우 발생 가능 @@ -50,5 +56,3 @@ ``` * [ ] (테스트 픽스처 활용으로 인해 테스트 코드가 결합되는 현상 개선)[https://jojoldu.tistory.com/611] -* [ ] `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드를 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체 객체로 분리 - * 외부에서 해당 객체에 값에 접근하는 처리 방식에서 객체에 메시지를 보내 그 결과값을 반환받는 방식으로 수정 diff --git a/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt b/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt index 288823242b..3b23655c1c 100644 --- a/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt +++ b/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt @@ -4,7 +4,7 @@ import step3.racingcar.domain.Cars import step3.racingcar.domain.PlayInfo import step3.racingcar.service.RacingCarService import step3.racingcar.utils.CarGenerator -import step3.racingcar.utils.RandomNumberGenerator.generateRandomNumberToCarByRound +import step3.racingcar.utils.RandomNumberGenerator import step3.racingcar.view.InputView.Companion.inputJoinerCarsGuideMessagePrinter import step3.racingcar.view.InputView.Companion.inputRoundCountGuideMessagePrinter @@ -15,7 +15,7 @@ class RacingCarController { val carNames = inputJoinerCarsGuideMessagePrinter() val totalRound = inputRoundCountGuideMessagePrinter() val cars = Cars.of(CarGenerator.generate(carNames)) - generateRandomNumberToCarByRound(cars, totalRound) + RandomNumberGenerator().generateRandomNumberToCarByRound(cars, totalRound) val playInfo = PlayInfo(cars, totalRound) racingCarService.play(playInfo) } diff --git a/src/main/kotlin/step3/racingcar/domain/RandomNumber.kt b/src/main/kotlin/step3/racingcar/domain/RandomNumber.kt new file mode 100644 index 0000000000..d821e9d19e --- /dev/null +++ b/src/main/kotlin/step3/racingcar/domain/RandomNumber.kt @@ -0,0 +1,10 @@ +package step3.racingcar.domain + +interface RandomNumber { + fun value(): Int + + companion object { + const val RANGE_START = 1 + const val RANGE_END = 9 + } +} diff --git a/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt b/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt index ea485c48d6..f238d2948a 100644 --- a/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt +++ b/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt @@ -2,10 +2,14 @@ package step3.racingcar.utils import step3.racingcar.domain.Car import step3.racingcar.domain.Cars +import step3.racingcar.domain.RandomNumber +import step3.racingcar.domain.RandomNumber.Companion.RANGE_END +import step3.racingcar.domain.RandomNumber.Companion.RANGE_START -object RandomNumberGenerator { - private const val RANGE_START = 1 - private const val RANGE_END = 9 +class RandomNumberGenerator : RandomNumber { + override fun value(): Int = generate() + + private fun generate(): Int = (RANGE_START..RANGE_END).random() fun generateRandomNumberToCarByRound(cars: Cars, totalRound: Int) { cars.elements().forEach { @@ -13,8 +17,6 @@ object RandomNumberGenerator { } } - private fun generate(): Int = (RANGE_START..RANGE_END).random() - private fun generateRandomNumberToEachCar(car: Car, totalRound: Int) { repeat(totalRound) { car.addRandomNumber(generate()) diff --git a/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt b/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt new file mode 100644 index 0000000000..12ababd3c6 --- /dev/null +++ b/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt @@ -0,0 +1,7 @@ +package step3.racingcar.fixture + +import step3.racingcar.domain.RandomNumber + +class TestRandomNumberGenerator(private val testValue: Int) : RandomNumber { + override fun value(): Int = testValue +} From 9c824577059e80a0cacee5d2bd616eac96cba03c Mon Sep 17 00:00:00 2001 From: choi-ys Date: Mon, 28 Nov 2022 17:17:05 +0900 Subject: [PATCH 05/15] =?UTF-8?q?refactor:=20=EC=B6=94=EC=83=81=ED=99=94?= =?UTF-8?q?=EB=90=9C=20=EB=82=9C=EC=88=98=20=EC=83=9D=EC=84=B1=EB=B6=80=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=EC=9D=84=20=ED=86=B5=ED=95=9C=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=EC=B0=A8=20=EA=B2=BD=EC=A3=BC=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동차 경주를 진행하는 `RacingService` 객체에 추상화된 난수 생성 부의 구현체를 주입하여 생성한 후, 각 라운드 진행 시 운영 환경에서는 실제 난수 생성 구현체가 동작하고, 테스트 환경에서는 난수 발생 시 의도한 수를 반환하는 구현체가 동작할 수 있도록 수정 - 난수 생성 부 추상화에 따른 `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드 제거 - 난수 발생 부를 추상화하여 운영 환경에 주입되는 구현체와 테스트 환경에 주입되는 구현체를 별도로 구현한 뒤, 각 실행 환경에 맞는 구현체를 선택하여 주입함으로써 난수로 인해 테스트 하기 어려운 문제 개선 --- docs/STEP4-README.md | 7 +- .../controller/RacingCarController.kt | 3 +- src/main/kotlin/step3/racingcar/domain/Car.kt | 8 +- .../kotlin/step3/racingcar/domain/Cars.kt | 4 +- .../racingcar/service/RacingCarService.kt | 14 ++- .../racingcar/utils/RandomNumberGenerator.kt | 14 --- .../kotlin/step3/racingcar/view/ResultView.kt | 2 +- .../kotlin/step3/racingcar/domain/CarTest.kt | 20 ++--- .../kotlin/step3/racingcar/domain/CarsTest.kt | 2 +- .../racingcar/fixture/CarFixtureGenerator.kt | 11 --- .../fixture/TestRandomNumberGenerator.kt | 6 +- .../racingcar/service/RacingCarServiceTest.kt | 87 +++++++++++++------ 12 files changed, 92 insertions(+), 86 deletions(-) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 69bb315f13..b4ca96d2aa 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -39,9 +39,10 @@ * AS-IS : `Car` 객체에 미리 생성된 난수를 판별하여 주행거리를 누적 * TO-BE : 매 라운드 마다 발생된 난수를 판별한 후, 차량 주행거리를 누적 * [x] 추상화를 통해 난수에 대한 의존성으로 테스트 하기 어려운 현상 개선 - * [ ] 난수 발생 부를 추상화하여 운영 환경에 주입되는 구현체와 테스트 환경에 주입되는 구현체를 별도로 구현한 뒤, 각 실행 환경에 맞는 구현체를 선택하여 주입함으로써 난수로 인해 테스트 하기 어려운 문제 개선 - * [ ] 자동차 경주를 진행하는 `RacingService` 객체에 추상화된 난수 생성 부의 구현체를 주입하여 생성한 후, 각 라운드 진행 시 운영 환경에서는 실제 난수 생성 구현체가 동작하고, 테스트 환경에서는 난수 발생 시 의도한 수를 반환하는 구현체가 동작할 수 있도록 수정 - * 난수 생성 부 추상화에 따른 `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드 제거 + * [x] 난수 발생 부를 추상화하여 운영 환경에 주입되는 구현체와 테스트 환경에 주입되는 구현체를 별도로 구현한 뒤, 각 실행 환경에 맞는 구현체를 선택하여 주입함으로써 난수로 인해 테스트 하기 어려운 문제 개선 + * 추상화된 난수 생성부 주입을 통한 자동차 경주 진행 + * [x] 자동차 경주를 진행하는 `RacingService` 객체에 추상화된 난수 생성 부의 구현체를 주입하여 생성한 후, 각 라운드 진행 시 운영 환경에서는 실제 난수 생성 구현체가 동작하고, 테스트 환경에서는 난수 발생 시 의도한 수를 반환하는 구현체가 동작할 수 있도록 수정 + * [x] 난수 생성 부 추상화에 따른 `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드 제거 * [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 * 외부에서 해당 객체에 값에 접근하는 처리 방식에서 객체에 메시지를 보내 그 결과값을 반환받는 방식으로 수정 * 테스트 표현 개선 diff --git a/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt b/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt index 3b23655c1c..66be05936c 100644 --- a/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt +++ b/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt @@ -9,13 +9,12 @@ import step3.racingcar.view.InputView.Companion.inputJoinerCarsGuideMessagePrint import step3.racingcar.view.InputView.Companion.inputRoundCountGuideMessagePrinter class RacingCarController { - private val racingCarService: RacingCarService = RacingCarService() + private val racingCarService: RacingCarService = RacingCarService(RandomNumberGenerator()) fun gameStart() { val carNames = inputJoinerCarsGuideMessagePrinter() val totalRound = inputRoundCountGuideMessagePrinter() val cars = Cars.of(CarGenerator.generate(carNames)) - RandomNumberGenerator().generateRandomNumberToCarByRound(cars, totalRound) val playInfo = PlayInfo(cars, totalRound) racingCarService.play(playInfo) } diff --git a/src/main/kotlin/step3/racingcar/domain/Car.kt b/src/main/kotlin/step3/racingcar/domain/Car.kt index 5cead021c0..413d3b5910 100644 --- a/src/main/kotlin/step3/racingcar/domain/Car.kt +++ b/src/main/kotlin/step3/racingcar/domain/Car.kt @@ -4,14 +4,10 @@ private const val CAR_ID_DELIMITER = "-" private const val MOVE_CRITERIA = 4 class Car(val name: String) { - private var randomNumbers: RandomNumbers = RandomNumbers() var distance = 0 - fun addRandomNumber(randomNumber: Int) = randomNumbers.add(randomNumber) - - fun race(currentRoundIndex: Int) { - val randomNumberByCurrentRound: Int = randomNumbers[currentRoundIndex] - if (isMove(randomNumberByCurrentRound)) { + fun race(randomNumber: Int) { + if (isMove(randomNumber)) { distance++ } } diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index 45637c6e11..41a3d1fffb 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -1,15 +1,13 @@ package step3.racingcar.domain class Cars private constructor(private val elements: List) { - val winnerNames: String = formatToWinnerNames() - fun elements(): List = elements fun size(): Int = elements.size operator fun get(index: Int) = elements[index] - private fun formatToWinnerNames(): String = + fun winnerNames(): String = findWinnerNames().joinToString(WINNER_NAME_JOINING_SEPARATOR) private fun findWinnerNames() = diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index 85d9e321fd..04e4701727 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -1,11 +1,13 @@ package step3.racingcar.service +import step3.racingcar.domain.Car import step3.racingcar.domain.Cars import step3.racingcar.domain.PlayInfo +import step3.racingcar.domain.RandomNumber import step3.racingcar.view.ResultView.Companion.printRoundResult import step3.racingcar.view.ResultView.Companion.printWinner -class RacingCarService { +class RacingCarService(private val randomNumber: RandomNumber) { fun play(playInfo: PlayInfo) { repeat(playInfo.totalRound) { playEachRound(it, playInfo.cars) @@ -13,10 +15,16 @@ class RacingCarService { printWinner(playInfo.cars) } - private fun playEachRound(currentRoundIndex: Int, cars: Cars) { + fun playEachRound(currentRoundIndex: Int, cars: Cars) { cars.elements().forEach { - it.race(currentRoundIndex) + val randomNumber = randomNumber.value() + it.race(randomNumber) } printRoundResult(currentRoundIndex, cars) } + + fun playEachRoundByCar(car: Car){ + val randomNumber = randomNumber.value() + car.race(randomNumber) + } } diff --git a/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt b/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt index f238d2948a..85ef0f635c 100644 --- a/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt +++ b/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt @@ -1,7 +1,5 @@ package step3.racingcar.utils -import step3.racingcar.domain.Car -import step3.racingcar.domain.Cars import step3.racingcar.domain.RandomNumber import step3.racingcar.domain.RandomNumber.Companion.RANGE_END import step3.racingcar.domain.RandomNumber.Companion.RANGE_START @@ -10,16 +8,4 @@ class RandomNumberGenerator : RandomNumber { override fun value(): Int = generate() private fun generate(): Int = (RANGE_START..RANGE_END).random() - - fun generateRandomNumberToCarByRound(cars: Cars, totalRound: Int) { - cars.elements().forEach { - generateRandomNumberToEachCar(it, totalRound) - } - } - - private fun generateRandomNumberToEachCar(car: Car, totalRound: Int) { - repeat(totalRound) { - car.addRandomNumber(generate()) - } - } } diff --git a/src/main/kotlin/step3/racingcar/view/ResultView.kt b/src/main/kotlin/step3/racingcar/view/ResultView.kt index db768d5b08..0c1578c93e 100644 --- a/src/main/kotlin/step3/racingcar/view/ResultView.kt +++ b/src/main/kotlin/step3/racingcar/view/ResultView.kt @@ -33,7 +33,7 @@ class ResultView { fun printWinner(cars: Cars) { println() - println(WINNER_GUIDE_MESSAGE_FORMAT.format(cars.winnerNames)) + println(WINNER_GUIDE_MESSAGE_FORMAT.format(cars.winnerNames())) } } } diff --git a/src/test/kotlin/step3/racingcar/domain/CarTest.kt b/src/test/kotlin/step3/racingcar/domain/CarTest.kt index 42a8e0a470..85a3ad5bf8 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarTest.kt @@ -2,15 +2,13 @@ package step3.racingcar.domain import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe -import step3.racingcar.fixture.CarFixtureGenerator class CarTest : BehaviorSpec({ given("경주에 참가하는 자동차 한대에 4이상의 숫자가 주어지고") { - val currentRoundIndex = 0 - val givenCar = CarFixtureGenerator.난수를_가지는_차량_생성("참가 차량", 4) + val givenCar = Car("참가 차량") `when`("경주를 진행하면") { - givenCar.race(currentRoundIndex) + givenCar.race(4) then("현재 차량의 주행거리를 1만큼 누적한다.") { givenCar.distance shouldBe 1 } @@ -18,11 +16,10 @@ class CarTest : BehaviorSpec({ } given("경주에 참가하는 자동차 한대에 3이하의 숫자가 주어지고") { - val currentRoundIndex = 0 - val givenCar = CarFixtureGenerator.난수를_가지는_차량_생성("참가 차량", 3) + val givenCar = Car("참가 차량") `when`("경주를 진행하면") { - givenCar.race(currentRoundIndex) + givenCar.race(3) then("현재 차량의 주행거리는 누적되지 않는다.") { givenCar.distance shouldBe 0 } @@ -33,22 +30,19 @@ class CarTest : BehaviorSpec({ val car = Car("참가 차량") `when`("첫번째 라운드에 4가 주어지면") { - car.addRandomNumber(4) - car.race(0) + car.race(4) then("차량 전진 횟수가 1 증가한다.") { car.distance shouldBe 1 } } `when`("두번째 라운드에 3이 주어지면") { - car.addRandomNumber(3) - car.race(1) + car.race(3) then("차량의 전진 횟수는 증가하지 않는다.") { car.distance shouldBe 1 } } `when`("세번째 라운드에 6이 주어지면") { - car.addRandomNumber(6) - car.race(2) + car.race(6) then("차량 전진 횟수가 1 증가한다.") { car.distance shouldBe 2 } diff --git a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt index ae7c3cd7b2..6735ef394c 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt @@ -14,7 +14,7 @@ internal class CarsTest : BehaviorSpec({ val 참가_차량_목록 = listOf(첫_번째_차량, 두_번째_차량, 세_번째_차량) val given = Cars.of(참가_차량_목록) then("우승한 차량들의 이름을 반환한다.") { - given.winnerNames shouldBe "${두_번째_차량.name}, ${세_번째_차량.name}" + given.winnerNames() shouldBe "${두_번째_차량.name}, ${세_번째_차량.name}" } } } diff --git a/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt b/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt index 5c8c2dad3d..2510c92b42 100644 --- a/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt +++ b/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt @@ -4,20 +4,9 @@ import step3.racingcar.domain.Car class CarFixtureGenerator { companion object { - fun 난수를_가지는_차량_생성(carName: String, vararg randomNumbers: Int): Car { - val car = Car(carName) - randomNumbers.forEach { - car.addRandomNumber(it) - } - return car - } - fun 경주를_완료한_차량_생성(carName: String, vararg randomNumbers: Int): Car { val car = Car(carName) randomNumbers.forEach { - car.addRandomNumber(it) - } - repeat(randomNumbers.size) { car.race(it) } return car diff --git a/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt b/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt index 12ababd3c6..cdae1a5471 100644 --- a/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt +++ b/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt @@ -2,6 +2,10 @@ package step3.racingcar.fixture import step3.racingcar.domain.RandomNumber -class TestRandomNumberGenerator(private val testValue: Int) : RandomNumber { +class TestRandomNumberGenerator(private val testValue: Int = DEFAULT_TEST_VALUE) : RandomNumber { override fun value(): Int = testValue + + companion object { + private const val DEFAULT_TEST_VALUE = 1 + } } diff --git a/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt b/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt index 03de2c1c6c..5eca506ae6 100644 --- a/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt +++ b/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt @@ -2,66 +2,97 @@ package step3.racingcar.service import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe +import step3.racingcar.domain.Car import step3.racingcar.domain.Cars import step3.racingcar.domain.PlayInfo -import step3.racingcar.fixture.CarFixtureGenerator.Companion.난수를_가지는_차량_생성 +import step3.racingcar.fixture.TestRandomNumberGenerator internal class RacingCarServiceTest : BehaviorSpec({ - val racingCarService = RacingCarService() + lateinit var racingCarService: RacingCarService - given("참가 차량 한대에 숫자 4가 주어지고") { - val 참가_차량 = 난수를_가지는_차량_생성("참가 차량", 4) + given("단일 라운드를 진행할 참가 차량 한대가 주어지고") { + val 참가_차량 = Car("참가 차량") val given = Cars.of(listOf(참가_차량)) val playInfo = PlayInfo(given, 1) - `when`("해당 라운드를 진행하면") { + `when`("4이상의 난수가 발생하면") { + racingCarService = of(4) racingCarService.play(playInfo) - then("차량은 한칸 전진한다.") { + then("차량의 주행거리는 1 누적된다.") { 참가_차량.distance shouldBe 1 } } } - given("참가 차량 한대에 숫자 4와 3이 주어지고") { - val 참가_차량 = 난수를_가지는_차량_생성("참가 차량", 4, 3) + given("두 라운드를 진행할 참가 차량 한대가 주어지고") { + val 참가_차량 = Car("참가 차량") val given = Cars.of(listOf(참가_차량)) - val playInfo = PlayInfo(given, 2) - `when`("각 라운드를 진행하면") { - racingCarService.play(playInfo) - then("차량은 한칸 전진한다.") { + `when`("첫번째 라운드에 4이상의 난수가 발생하면") { + racingCarService = of(4) + racingCarService.playEachRound(0, given) + then("차량의 주행거리는 1 누적된다.") { + 참가_차량.distance shouldBe 1 + } + } + `when`("두번째 라운드에 3이하의 난수가 발생하면") { + racingCarService = of(3) + racingCarService.playEachRound(1, given) + then("차량의 주행거리는 누적되지 않는다.") { 참가_차량.distance shouldBe 1 } } } - given("두 차량에 각각 4와 3이 주어지고") { - val 첫번째_참가_차량 = 난수를_가지는_차량_생성("첫번째 참가 차량", 4) - val 두번째_참가_차량 = 난수를_가지는_차량_생성("두번째 참가 차량", 3) + given("단일 라운드를 진행할 참가 차량 두대가 주어지고") { + val 첫번째_참가_차량 = Car("첫번째_참가_차량") + val 두번째_참가_차량 = Car("두번째_참가_차량") val given = Cars.of(listOf(첫번째_참가_차량, 두번째_참가_차량)) - val playInfo = PlayInfo(given, 1) - `when`("해당 라운드를 진행하면") { - racingCarService.play(playInfo) - then("각 차량의 진행 거리를 확인할 수 있다.") { + `when`("첫번째 차량에는 4의 난수가, 두번째 차량에는 3의 난수가 발생하면") { + racingCarService = of(4).also { + it.playEachRoundByCar(첫번째_참가_차량) + } + racingCarService = of(3).also { + it.playEachRoundByCar(두번째_참가_차량) + } + then("첫번째 차량이 누적거리 1로 우승한다.") { 첫번째_참가_차량.distance shouldBe 1 - 두번째_참가_차량.distance shouldBe 0 + given.winnerNames() shouldBe "첫번째_참가_차량" } } } - given("여러 차량에 숫자를 주입하고") { - val 첫번째_참가_차량 = 난수를_가지는_차량_생성("첫번째 참가 차량", 4, 5) - val 두번째_참가_차량 = 난수를_가지는_차량_생성("두번째 참가 차량", 3, 5) + given("두 라운드를 진행할 참가 차량 두대가 주어지고") { + val 첫번째_참가_차량 = Car("첫번째_참가_차량") + val 두번째_참가_차량 = Car("두번째_참가_차량") val given = Cars.of(listOf(첫번째_참가_차량, 두번째_참가_차량)) - val playInfo = PlayInfo(given, 2) - `when`("각 라운드를 진행하면") { - racingCarService.play(playInfo) - then("각 차량의 진행 거리를 확인할 수 있다.") { + `when`("첫번째 라운드에는 첫번째 차량에는 4의 난수가, 두번째 차량에는 3의 난수가 발생하고") { + racingCarService = of(4).also { + it.playEachRoundByCar(첫번째_참가_차량) + } + racingCarService = of(3).also { + it.playEachRoundByCar(두번째_참가_차량) + } + then("첫번째 차량을 우승 차량으로 반환한다.") { + given.winnerNames() shouldBe "첫번째_참가_차량" + } + } + + `when`("두번째 라운드에는 두 차량 모두 4의 난수가 발생하면") { + racingCarService = of(4).also { + it.playEachRoundByCar(첫번째_참가_차량) + } + racingCarService = of(4).also { + it.playEachRoundByCar(두번째_참가_차량) + } + then("첫번째 차량이 누적거리 2로 우승한다.") { 첫번째_참가_차량.distance shouldBe 2 - 두번째_참가_차량.distance shouldBe 1 + given.winnerNames() shouldBe "첫번째_참가_차량" } } } }) + +fun of(randomNumber: Int): RacingCarService = RacingCarService(TestRandomNumberGenerator(randomNumber)) From 0effa6b26ed3158b8702c23a7defa8186cdd1aa6 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Mon, 28 Nov 2022 17:22:18 +0900 Subject: [PATCH 06/15] =?UTF-8?q?polishing:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자료형과 반환형이 생략된 변수 및 함수에 자료형과 반환형 명시 - 각 차량에 발생한 난수를 저장하는 RandomNumbers 미사용 일급 컬렉션 제거 --- src/main/kotlin/step3/racingcar/domain/Car.kt | 2 +- src/main/kotlin/step3/racingcar/domain/Cars.kt | 8 ++++---- src/main/kotlin/step3/racingcar/domain/RandomNumbers.kt | 7 ------- .../kotlin/step3/racingcar/service/RacingCarService.kt | 2 +- 4 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 src/main/kotlin/step3/racingcar/domain/RandomNumbers.kt diff --git a/src/main/kotlin/step3/racingcar/domain/Car.kt b/src/main/kotlin/step3/racingcar/domain/Car.kt index 413d3b5910..1b6f3f2162 100644 --- a/src/main/kotlin/step3/racingcar/domain/Car.kt +++ b/src/main/kotlin/step3/racingcar/domain/Car.kt @@ -4,7 +4,7 @@ private const val CAR_ID_DELIMITER = "-" private const val MOVE_CRITERIA = 4 class Car(val name: String) { - var distance = 0 + var distance: Int = 0 fun race(randomNumber: Int) { if (isMove(randomNumber)) { diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index 41a3d1fffb..da2236892d 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -5,20 +5,20 @@ class Cars private constructor(private val elements: List) { fun size(): Int = elements.size - operator fun get(index: Int) = elements[index] + operator fun get(index: Int): Car = elements[index] fun winnerNames(): String = findWinnerNames().joinToString(WINNER_NAME_JOINING_SEPARATOR) - private fun findWinnerNames() = + private fun findWinnerNames(): List = elements() .filter { it.distance == findMaxDistance() } .map { it.name } - private fun findMaxDistance() = elements().maxOf { it.distance } + private fun findMaxDistance(): Int = elements().maxOf { it.distance } companion object { private const val WINNER_NAME_JOINING_SEPARATOR = ", " - fun of(elements: List) = Cars(elements) + fun of(elements: List): Cars = Cars(elements) } } diff --git a/src/main/kotlin/step3/racingcar/domain/RandomNumbers.kt b/src/main/kotlin/step3/racingcar/domain/RandomNumbers.kt deleted file mode 100644 index 4dc1428322..0000000000 --- a/src/main/kotlin/step3/racingcar/domain/RandomNumbers.kt +++ /dev/null @@ -1,7 +0,0 @@ -package step3.racingcar.domain - -class RandomNumbers { - private var elements: MutableList = mutableListOf() - fun add(randomNumber: Int) = elements.add(randomNumber) - operator fun get(index: Int): Int = elements[index] -} diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index 04e4701727..38a91479fb 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -23,7 +23,7 @@ class RacingCarService(private val randomNumber: RandomNumber) { printRoundResult(currentRoundIndex, cars) } - fun playEachRoundByCar(car: Car){ + fun playEachRoundByCar(car: Car) { val randomNumber = randomNumber.value() car.race(randomNumber) } From c6fd39d2b6acd0534479e10473866461b322f764 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Mon, 28 Nov 2022 17:25:29 +0900 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=94=BD=EC=8A=A4=EC=B2=98=20=ED=99=9C=EC=9A=A9=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EA=B0=80=20=EA=B2=B0=ED=95=A9=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 픽스처로 인해 테스트 코드가 결합되어 테스트 코드 유지보수가 복잡해지는 현상 개선을 위해, 각 테스트에 필요한 fixture 생성부를 private 메서드로 구성 --- docs/STEP4-README.md | 3 ++- .../kotlin/step3/racingcar/domain/CarsTest.kt | 8 +++++++- .../racingcar/fixture/CarFixtureGenerator.kt | 15 --------------- 3 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index b4ca96d2aa..a91ead3137 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -56,4 +56,5 @@ ```kotlin ``` - * [ ] (테스트 픽스처 활용으로 인해 테스트 코드가 결합되는 현상 개선)[https://jojoldu.tistory.com/611] + * [x] (테스트 픽스처 활용으로 인해 테스트 코드가 결합되는 현상 개선)[https://jojoldu.tistory.com/611] + * 테스트 픽스처로 인해 테스트 코드가 결합되어 테스트 코드 유지보수가 복잡해지는 현상 개선을 위해, 각 테스트에 필요한 fixture 생성부를 private 메서드로 구성 diff --git a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt index 6735ef394c..45092b8792 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt @@ -2,7 +2,6 @@ package step3.racingcar.domain import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe -import step3.racingcar.fixture.CarFixtureGenerator.Companion.경주를_완료한_차량_생성 internal class CarsTest : BehaviorSpec({ given("경주를 완료한 자동차 객체 목록이 주어지고") { @@ -19,3 +18,10 @@ internal class CarsTest : BehaviorSpec({ } } }) +private fun 경주를_완료한_차량_생성(carName: String, vararg randomNumbers: Int): Car { + val car = Car(carName) + randomNumbers.forEach { + car.race(it) + } + return car +} diff --git a/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt b/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt deleted file mode 100644 index 2510c92b42..0000000000 --- a/src/test/kotlin/step3/racingcar/fixture/CarFixtureGenerator.kt +++ /dev/null @@ -1,15 +0,0 @@ -package step3.racingcar.fixture - -import step3.racingcar.domain.Car - -class CarFixtureGenerator { - companion object { - fun 경주를_완료한_차량_생성(carName: String, vararg randomNumbers: Int): Car { - val car = Car(carName) - randomNumbers.forEach { - car.race(it) - } - return car - } - } -} From 10710ada80bec35251171db1e451d3035f035903 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Mon, 28 Nov 2022 17:34:12 +0900 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=ED=99=9C=EC=9A=A9=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B2=80=EC=A6=9D=20=EB=B6=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 검증 부가 도메인 로직을 참조하고 있음에 따라 로직 변경 시 테스트로서의 역할을 제대로 수행할 수 없는 문제점 개선을 위해 테스트 검증 부를 하드코딩된 값으로 변경 --- docs/STEP4-README.md | 8 ++++---- src/test/kotlin/step3/racingcar/domain/CarsTest.kt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index a91ead3137..18d8d11b66 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -46,15 +46,15 @@ * [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 * 외부에서 해당 객체에 값에 접근하는 처리 방식에서 객체에 메시지를 보내 그 결과값을 반환받는 방식으로 수정 * 테스트 표현 개선 - * [ ] 도메인 로직을 활용한 테스트 결과 검증 부를 원하는 결과값을 명시적으로 하드코딩 - * 도메인 로직 변경 시 도메인 로직을 이용한 해당 테스트는 통과되므로 오류를 검증하는 테스트로써의 역할을 수행할 수 없는 경우 발생 가능 + * [x] 도메인 로직을 활용한 테스트 결과 검증 부를 하드코딩된 값으로 변경 + * 테스트 검증 부가 도메인 로직을 참조하고 있음에 따라 로직 변경 시 테스트로서의 역할을 제대로 수행할 수 없는 문제점 개선을 위해 테스트 검증 부를 하드코딩된 값으로 변경 * AS-IS : ```kotlin - + given.winnerNames() shouldBe "${두_번째_차량.name}, ${세_번째_차량.name}" ``` * TO-BE : ```kotlin - + given.winnerNames() shouldBe "두 번째 차량, 세 번째 차량" ``` * [x] (테스트 픽스처 활용으로 인해 테스트 코드가 결합되는 현상 개선)[https://jojoldu.tistory.com/611] * 테스트 픽스처로 인해 테스트 코드가 결합되어 테스트 코드 유지보수가 복잡해지는 현상 개선을 위해, 각 테스트에 필요한 fixture 생성부를 private 메서드로 구성 diff --git a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt index 45092b8792..174e63b6f5 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt @@ -13,11 +13,12 @@ internal class CarsTest : BehaviorSpec({ val 참가_차량_목록 = listOf(첫_번째_차량, 두_번째_차량, 세_번째_차량) val given = Cars.of(참가_차량_목록) then("우승한 차량들의 이름을 반환한다.") { - given.winnerNames() shouldBe "${두_번째_차량.name}, ${세_번째_차량.name}" + given.winnerNames() shouldBe "두 번째 차량, 세 번째 차량" } } } }) + private fun 경주를_완료한_차량_생성(carName: String, vararg randomNumbers: Int): Car { val car = Car(carName) randomNumbers.forEach { From 753cac73bf72975092bd2d69670292cfd826f2d9 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 02:47:36 +0900 Subject: [PATCH 09/15] =?UTF-8?q?docs:=20step4=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=B0=A8=20=EA=B2=BD=EC=A3=BC(=EC=9A=B0=EC=8A=B9=EC=9E=90)=202?= =?UTF-8?q?=EC=B0=A8=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EB=82=B4=EC=9A=A9=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/STEP4-README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 18d8d11b66..4dc7e85fcb 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -58,3 +58,19 @@ ``` * [x] (테스트 픽스처 활용으로 인해 테스트 코드가 결합되는 현상 개선)[https://jojoldu.tistory.com/611] * 테스트 픽스처로 인해 테스트 코드가 결합되어 테스트 코드 유지보수가 복잡해지는 현상 개선을 위해, 각 테스트에 필요한 fixture 생성부를 private 메서드로 구성 + +## 2차 코드 리뷰 피드백 내용 정리 +* Car 도메인 수정 + * [ ] 누락된 접근 제어자 추가(Car.distance) + * [ ] 경주가 완료된 차량의 용이한 테스트를 위해 `Car` 도메인의 `distance` 속성의 초기값 설정이 가능하도록 수정 +* Cars 도메인 수정 + * [ ] private 생성자의 멤버 변수를 반환하는 불필요한 getter 제거 + * [ ] ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 + * [ ] Service 계층에서 Cars 도메인의 elements에 접근하여 경주를 진행하는 방식에서 도메인에 메세지를 보내 직접 처리하도록 수정 + * [ ] 우승자 산출을 위한 Cars 도메인 TC 개선 : + * 우승자 산출 테스트를 위해 Car.race()를 호출하는 부분을 Car 도메인의 distance 속성 초기값 설정을 통해 제거하여 테스트 간소화 +* [ ] 명확하지 않은 난수 생성기 인터페이스 이름 변경 + * AS-IS : RandomNumber + * TO-BE : NumberGenerator +* [ ] Service 계층에 구현된 각 라운드 결과 출력 부를 View 계층으로 이동 +* [ ] 명확하지 않은 Given/When 테스트 표현 개선 From def429b84853dd85e9f142664fb753bd2d686498 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 03:14:45 +0900 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=20Car=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - variable로 선언된 distance(누적 주행 거리) 속성의 외부 변경을 제한하기 위해 누락된 private 접근 제어자 추가 - 미사용 상수 제거 및 distance 속성의 초기값 상수 추가 - 경주가 완료된 차량의 용이한 테스트를 위해 `Car` 도메인의 `distance` 속성의 초기값 설정이 가능하도록 수정 - 우승자 산출 방식 변경 : 해당 차량의 누적 주행거리가 최대값인지를 확인하는 함수 작성 - AS-IS : 우승자 산출을 위해 경주에 기존 `Cars` 객체에서 각 원소(Car)를 순회하며 distance 속성에 접근하여 최대 누적 주행거리를 비교 - TO-BE : `Cars` 객체에서 각 원소(Car)를 순회하며 누적 주행거리가 최대값인 원소를 추출 - 우승자 산출을 위한 Cars 도메인 TC의 Car.race() 호출 부 개선 - 우승자 산출 테스트를 위해 Car.race()를 호출하여 누적 주행거리를 설정하는 테스트 방식을 Car 도메인의 distance 속성 초기값 설정을 통해 제거하여 테스트 간소화 - 경주를 완료한 차량 생성을 위해 더 이상 불필요한 test fixture 생성 함수 제거 --- docs/STEP4-README.md | 15 ++++++++++----- src/main/kotlin/step3/racingcar/domain/Car.kt | 8 ++++---- src/main/kotlin/step3/racingcar/domain/Cars.kt | 8 +++++--- .../kotlin/step3/racingcar/domain/CarsTest.kt | 14 +++----------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 4dc7e85fcb..1cc9053e07 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -61,14 +61,19 @@ ## 2차 코드 리뷰 피드백 내용 정리 * Car 도메인 수정 - * [ ] 누락된 접근 제어자 추가(Car.distance) - * [ ] 경주가 완료된 차량의 용이한 테스트를 위해 `Car` 도메인의 `distance` 속성의 초기값 설정이 가능하도록 수정 + * [x] variable로 선언된 distance(누적 주행 거리) 속성의 외부 변경을 제한하기 위해 누락된 private 접근 제어자 추가 + * [x] 미사용 상수 제거 및 distance 속성의 초기값 상수 추가 + * [x] 경주가 완료된 차량의 용이한 테스트를 위해 `Car` 도메인의 `distance` 속성의 초기값 설정이 가능하도록 수정 + * [x] 우승자 산출 방식 변경 : 해당 차량의 누적 주행거리가 최대값인지를 확인하는 함수 작성 + * AS-IS : 우승자 산출을 위해 경주에 기존 `Cars` 객체에서 각 원소(Car)를 순회하며 distance 속성에 접근하여 최대 누적 주행거리를 비교 + * TO-BE : `Cars` 객체에서 각 원소(Car)를 순회하며 누적 주행거리가 최대값인 원소를 추출 + * [x] 우승자 산출을 위한 Cars 도메인 TC의 Car.race() 호출 부 개선 + * 우승자 산출 테스트를 위해 Car.race()를 호출하여 누적 주행거리를 설정하는 테스트 방식을 Car 도메인의 distance 속성 초기값 설정을 통해 제거하여 테스트 간소화 + * 경주를 완료한 차량 생성을 위해 더 이상 불필요한 test fixture 생성 함수 제거 * Cars 도메인 수정 - * [ ] private 생성자의 멤버 변수를 반환하는 불필요한 getter 제거 + * [ ] private 생성자의 property를 동일하게 반환하는 불필요 getter 제거 * [ ] ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 * [ ] Service 계층에서 Cars 도메인의 elements에 접근하여 경주를 진행하는 방식에서 도메인에 메세지를 보내 직접 처리하도록 수정 - * [ ] 우승자 산출을 위한 Cars 도메인 TC 개선 : - * 우승자 산출 테스트를 위해 Car.race()를 호출하는 부분을 Car 도메인의 distance 속성 초기값 설정을 통해 제거하여 테스트 간소화 * [ ] 명확하지 않은 난수 생성기 인터페이스 이름 변경 * AS-IS : RandomNumber * TO-BE : NumberGenerator diff --git a/src/main/kotlin/step3/racingcar/domain/Car.kt b/src/main/kotlin/step3/racingcar/domain/Car.kt index 1b6f3f2162..edc95041c3 100644 --- a/src/main/kotlin/step3/racingcar/domain/Car.kt +++ b/src/main/kotlin/step3/racingcar/domain/Car.kt @@ -1,16 +1,16 @@ package step3.racingcar.domain -private const val CAR_ID_DELIMITER = "-" private const val MOVE_CRITERIA = 4 +private const val INITIAL_DISTANCE = 0 -class Car(val name: String) { - var distance: Int = 0 - +class Car(val name: String, var distance: Int = INITIAL_DISTANCE) { fun race(randomNumber: Int) { if (isMove(randomNumber)) { distance++ } } + fun isMaximumDistance(maxDistance: Int): Boolean = distance == maxDistance + private fun isMove(randomNumber: Int): Boolean = randomNumber >= MOVE_CRITERIA } diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index da2236892d..69e44f84ba 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -10,10 +10,12 @@ class Cars private constructor(private val elements: List) { fun winnerNames(): String = findWinnerNames().joinToString(WINNER_NAME_JOINING_SEPARATOR) - private fun findWinnerNames(): List = - elements() - .filter { it.distance == findMaxDistance() } + private fun findWinnerNames(): List { + val maxDistance = findMaxDistance() + return elements() + .filter { it.isMaximumDistance(maxDistance) } .map { it.name } + } private fun findMaxDistance(): Int = elements().maxOf { it.distance } diff --git a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt index 174e63b6f5..d3658b7c20 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt @@ -5,9 +5,9 @@ import io.kotest.matchers.shouldBe internal class CarsTest : BehaviorSpec({ given("경주를 완료한 자동차 객체 목록이 주어지고") { - val 첫_번째_차량 = 경주를_완료한_차량_생성("첫 번째 차량", 1, 1, 1, 4, 4) - val 두_번째_차량 = 경주를_완료한_차량_생성("두 번째 차량", 1, 1, 4, 4, 4) - val 세_번째_차량 = 경주를_완료한_차량_생성("세 번째 차량", 4, 4, 4, 1, 1) + val 첫_번째_차량 = Car("첫 번째 차량", 2) + val 두_번째_차량 = Car("두 번째 차량", 3) + val 세_번째_차량 = Car("세 번째 차량", 3) `when`("우승자 객체를 생성하면") { val 참가_차량_목록 = listOf(첫_번째_차량, 두_번째_차량, 세_번째_차량) @@ -18,11 +18,3 @@ internal class CarsTest : BehaviorSpec({ } } }) - -private fun 경주를_완료한_차량_생성(carName: String, vararg randomNumbers: Int): Car { - val car = Car(carName) - randomNumbers.forEach { - car.race(it) - } - return car -} From ebca7414be7b191386513517f1de9fe5d309d400 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 03:40:33 +0900 Subject: [PATCH 11/15] =?UTF-8?q?refactor:=20Cars=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 생성자의 `elements` 프로퍼티를 반환하는 elements() 함수를 제거하고, 해당 프로퍼티를 public 으로 변경 - ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 - Service 계층에서 Cars 도메인의 각 원소(Car)에 접근하여 경주를 진행하는 방식에서 Cars 도메인에 메세지를 보내어 각 원소(Car)의 race() 함수를 호출함으로써 도메인이 메세지를 받아 직접 로직을 처리 하도록 수정 --- docs/STEP4-README.md | 10 +++++++--- .../kotlin/step3/racingcar/domain/Cars.kt | 19 ++++++++++++------- .../racingcar/service/RacingCarService.kt | 10 ++-------- .../kotlin/step3/racingcar/view/ResultView.kt | 10 +++++++--- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 1cc9053e07..8704f5eb94 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -71,11 +71,15 @@ * 우승자 산출 테스트를 위해 Car.race()를 호출하여 누적 주행거리를 설정하는 테스트 방식을 Car 도메인의 distance 속성 초기값 설정을 통해 제거하여 테스트 간소화 * 경주를 완료한 차량 생성을 위해 더 이상 불필요한 test fixture 생성 함수 제거 * Cars 도메인 수정 - * [ ] private 생성자의 property를 동일하게 반환하는 불필요 getter 제거 - * [ ] ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 - * [ ] Service 계층에서 Cars 도메인의 elements에 접근하여 경주를 진행하는 방식에서 도메인에 메세지를 보내 직접 처리하도록 수정 + * [x] private 생성자의 `elements` 프로퍼티를 반환하는 getter 제거하고, 해당 프로퍼티를 public 으로 변경 + * [x] ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 + * [x] Service 계층에서 Cars 도메인의 각 원소(Car)에 접근하여 경주를 진행하는 방식에서 Cars 도메인에 메세지를 보내어 각 원소(Car)의 race() 함수를 호출함으로써 도메인이 메세지를 받아 직접 로직을 처리 하도록 수정 * [ ] 명확하지 않은 난수 생성기 인터페이스 이름 변경 * AS-IS : RandomNumber * TO-BE : NumberGenerator * [ ] Service 계층에 구현된 각 라운드 결과 출력 부를 View 계층으로 이동 * [ ] 명확하지 않은 Given/When 테스트 표현 개선 +* [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 + * Cars 도메인에서 직접 각 라운드 결과를 출력하는 방식을 Cars의 각 원소(Car)가 경주를 완료한 기록을 별도의 객체로 반환하여 출력 부에 전달 + * 출력부(외부)에서 Cars 도메인의 값에 접근하는 처리 방식에서 Cars 도메인에 메시지를 보내 그 결과값을 반환 받는 방식으로 수정 + * Cars 도메인 계층에서 출력부를 분리함에 따라, 출력부에서 접근하고 있던 Cars 도메인의 elements 프로퍼티의 접근 제한자를 private에서 public으로 변경 \ No newline at end of file diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index 69e44f84ba..e0a93bc3fc 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -1,26 +1,31 @@ package step3.racingcar.domain -class Cars private constructor(private val elements: List) { +import step3.racingcar.view.ResultView + +class Cars private constructor(val elements: List) { fun elements(): List = elements fun size(): Int = elements.size operator fun get(index: Int): Car = elements[index] - fun winnerNames(): String = - findWinnerNames().joinToString(WINNER_NAME_JOINING_SEPARATOR) + fun race(currentRoundIndex: Int, randomNumber: RandomNumber) { + elements.forEach { + it.race(randomNumber.value()) + } + ResultView.printRoundResult(currentRoundIndex, this) + } - private fun findWinnerNames(): List { + fun winnerNames(): List { val maxDistance = findMaxDistance() - return elements() + return elements .filter { it.isMaximumDistance(maxDistance) } .map { it.name } } - private fun findMaxDistance(): Int = elements().maxOf { it.distance } + private fun findMaxDistance(): Int = elements.maxOf { it.distance } companion object { - private const val WINNER_NAME_JOINING_SEPARATOR = ", " fun of(elements: List): Cars = Cars(elements) } } diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index 38a91479fb..940d32b9e7 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -4,7 +4,6 @@ import step3.racingcar.domain.Car import step3.racingcar.domain.Cars import step3.racingcar.domain.PlayInfo import step3.racingcar.domain.RandomNumber -import step3.racingcar.view.ResultView.Companion.printRoundResult import step3.racingcar.view.ResultView.Companion.printWinner class RacingCarService(private val randomNumber: RandomNumber) { @@ -15,13 +14,8 @@ class RacingCarService(private val randomNumber: RandomNumber) { printWinner(playInfo.cars) } - fun playEachRound(currentRoundIndex: Int, cars: Cars) { - cars.elements().forEach { - val randomNumber = randomNumber.value() - it.race(randomNumber) - } - printRoundResult(currentRoundIndex, cars) - } + fun playEachRound(currentRoundIndex: Int, cars: Cars) = + cars.race(currentRoundIndex, randomNumber) fun playEachRoundByCar(car: Car) { val randomNumber = randomNumber.value() diff --git a/src/main/kotlin/step3/racingcar/view/ResultView.kt b/src/main/kotlin/step3/racingcar/view/ResultView.kt index 0c1578c93e..d7c8ffa6e3 100644 --- a/src/main/kotlin/step3/racingcar/view/ResultView.kt +++ b/src/main/kotlin/step3/racingcar/view/ResultView.kt @@ -8,6 +8,7 @@ class ResultView { private const val SCORE_BAR = "-" private const val ROUND_COMPLETE_GUIDE_MESSAGE_FORMAT = "%d 라운드가 종료되었습니다." private const val WINNER_GUIDE_MESSAGE_FORMAT = "%s 가 최종 우승했습니다." + private const val WINNER_NAME_JOINING_SEPARATOR = ", " fun printRoundResult(currentRoundIndex: Int, cars: Cars) { roundCompleteGuideMessage(currentRoundIndex) @@ -19,9 +20,8 @@ class ResultView { println(ROUND_COMPLETE_GUIDE_MESSAGE_FORMAT.format(currentRoundIndex.plus(1))) } - private fun printEachCarRoundResult(car: Car) { + private fun printEachCarRoundResult(car: Car) = println("${car.name} : ${distanceToScore(car.distance)}") - } private fun distanceToScore(distance: Int): StringBuilder { val result: StringBuilder = StringBuilder() @@ -33,7 +33,11 @@ class ResultView { fun printWinner(cars: Cars) { println() - println(WINNER_GUIDE_MESSAGE_FORMAT.format(cars.winnerNames())) + val formattedWinnerNames = formatToWinnerNames(cars.winnerNames()) + println(WINNER_GUIDE_MESSAGE_FORMAT.format(formattedWinnerNames)) } + + private fun formatToWinnerNames(winnerNames: List): String = + winnerNames.joinToString(WINNER_NAME_JOINING_SEPARATOR) } } From 107cc529de612f68dd1d5aa5d7c7c68843c888b0 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 04:01:53 +0900 Subject: [PATCH 12/15] =?UTF-8?q?refactor:=20=EB=AA=85=ED=99=95=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=82=9C=EC=88=98=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EA=B8=B0=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AS-IS : RandomNumber - TO-BE : NumberGenerator --- docs/STEP4-README.md | 2 +- src/main/kotlin/step3/racingcar/domain/Cars.kt | 4 ++-- .../domain/{RandomNumber.kt => NumberGenerator.kt} | 2 +- .../kotlin/step3/racingcar/service/RacingCarService.kt | 8 ++++---- .../kotlin/step3/racingcar/utils/RandomNumberGenerator.kt | 8 ++++---- .../step3/racingcar/fixture/TestRandomNumberGenerator.kt | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) rename src/main/kotlin/step3/racingcar/domain/{RandomNumber.kt => NumberGenerator.kt} (84%) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 8704f5eb94..1ef13f8f05 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -74,7 +74,7 @@ * [x] private 생성자의 `elements` 프로퍼티를 반환하는 getter 제거하고, 해당 프로퍼티를 public 으로 변경 * [x] ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 * [x] Service 계층에서 Cars 도메인의 각 원소(Car)에 접근하여 경주를 진행하는 방식에서 Cars 도메인에 메세지를 보내어 각 원소(Car)의 race() 함수를 호출함으로써 도메인이 메세지를 받아 직접 로직을 처리 하도록 수정 -* [ ] 명확하지 않은 난수 생성기 인터페이스 이름 변경 +* [x] 명확하지 않은 난수 생성기 인터페이스 이름 변경 * AS-IS : RandomNumber * TO-BE : NumberGenerator * [ ] Service 계층에 구현된 각 라운드 결과 출력 부를 View 계층으로 이동 diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index e0a93bc3fc..eaaa10a46b 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -9,9 +9,9 @@ class Cars private constructor(val elements: List) { operator fun get(index: Int): Car = elements[index] - fun race(currentRoundIndex: Int, randomNumber: RandomNumber) { + fun race(currentRoundIndex: Int, numberGenerator: NumberGenerator) { elements.forEach { - it.race(randomNumber.value()) + it.race(numberGenerator.value()) } ResultView.printRoundResult(currentRoundIndex, this) } diff --git a/src/main/kotlin/step3/racingcar/domain/RandomNumber.kt b/src/main/kotlin/step3/racingcar/domain/NumberGenerator.kt similarity index 84% rename from src/main/kotlin/step3/racingcar/domain/RandomNumber.kt rename to src/main/kotlin/step3/racingcar/domain/NumberGenerator.kt index d821e9d19e..6a692a2e6f 100644 --- a/src/main/kotlin/step3/racingcar/domain/RandomNumber.kt +++ b/src/main/kotlin/step3/racingcar/domain/NumberGenerator.kt @@ -1,6 +1,6 @@ package step3.racingcar.domain -interface RandomNumber { +interface NumberGenerator { fun value(): Int companion object { diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index 940d32b9e7..c219b2a0f0 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -3,10 +3,10 @@ package step3.racingcar.service import step3.racingcar.domain.Car import step3.racingcar.domain.Cars import step3.racingcar.domain.PlayInfo -import step3.racingcar.domain.RandomNumber +import step3.racingcar.domain.NumberGenerator import step3.racingcar.view.ResultView.Companion.printWinner -class RacingCarService(private val randomNumber: RandomNumber) { +class RacingCarService(private val numberGenerator: NumberGenerator) { fun play(playInfo: PlayInfo) { repeat(playInfo.totalRound) { playEachRound(it, playInfo.cars) @@ -15,10 +15,10 @@ class RacingCarService(private val randomNumber: RandomNumber) { } fun playEachRound(currentRoundIndex: Int, cars: Cars) = - cars.race(currentRoundIndex, randomNumber) + cars.race(currentRoundIndex, numberGenerator) fun playEachRoundByCar(car: Car) { - val randomNumber = randomNumber.value() + val randomNumber = numberGenerator.value() car.race(randomNumber) } } diff --git a/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt b/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt index 85ef0f635c..88d951ef47 100644 --- a/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt +++ b/src/main/kotlin/step3/racingcar/utils/RandomNumberGenerator.kt @@ -1,10 +1,10 @@ package step3.racingcar.utils -import step3.racingcar.domain.RandomNumber -import step3.racingcar.domain.RandomNumber.Companion.RANGE_END -import step3.racingcar.domain.RandomNumber.Companion.RANGE_START +import step3.racingcar.domain.NumberGenerator +import step3.racingcar.domain.NumberGenerator.Companion.RANGE_END +import step3.racingcar.domain.NumberGenerator.Companion.RANGE_START -class RandomNumberGenerator : RandomNumber { +class RandomNumberGenerator : NumberGenerator { override fun value(): Int = generate() private fun generate(): Int = (RANGE_START..RANGE_END).random() diff --git a/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt b/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt index cdae1a5471..e261771a1c 100644 --- a/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt +++ b/src/test/kotlin/step3/racingcar/fixture/TestRandomNumberGenerator.kt @@ -1,8 +1,8 @@ package step3.racingcar.fixture -import step3.racingcar.domain.RandomNumber +import step3.racingcar.domain.NumberGenerator -class TestRandomNumberGenerator(private val testValue: Int = DEFAULT_TEST_VALUE) : RandomNumber { +class TestRandomNumberGenerator(private val testValue: Int = DEFAULT_TEST_VALUE) : NumberGenerator { override fun value(): Int = testValue companion object { From 352368f0e7bb1f4f80de92f48c368867dfbe6f3a Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 04:02:10 +0900 Subject: [PATCH 13/15] =?UTF-8?q?refactor:=20=EB=AA=85=ED=99=95=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20Given/When=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=91=9C=ED=98=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/STEP4-README.md | 2 +- .../kotlin/step3/racingcar/domain/CarTest.kt | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 1ef13f8f05..445fbae675 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -77,8 +77,8 @@ * [x] 명확하지 않은 난수 생성기 인터페이스 이름 변경 * AS-IS : RandomNumber * TO-BE : NumberGenerator +* [x] 명확하지 않은 Given/When 테스트 표현 개선 * [ ] Service 계층에 구현된 각 라운드 결과 출력 부를 View 계층으로 이동 -* [ ] 명확하지 않은 Given/When 테스트 표현 개선 * [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 * Cars 도메인에서 직접 각 라운드 결과를 출력하는 방식을 Cars의 각 원소(Car)가 경주를 완료한 기록을 별도의 객체로 반환하여 출력 부에 전달 * 출력부(외부)에서 Cars 도메인의 값에 접근하는 처리 방식에서 Cars 도메인에 메시지를 보내 그 결과값을 반환 받는 방식으로 수정 diff --git a/src/test/kotlin/step3/racingcar/domain/CarTest.kt b/src/test/kotlin/step3/racingcar/domain/CarTest.kt index 85a3ad5bf8..adce90874b 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarTest.kt @@ -4,21 +4,21 @@ import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe class CarTest : BehaviorSpec({ - given("경주에 참가하는 자동차 한대에 4이상의 숫자가 주어지고") { + given("차량 전진 : 경주에 참가하는 자동차 한대에") { val givenCar = Car("참가 차량") - `when`("경주를 진행하면") { + `when`("4이상의 숫자로 경주를 진행하면") { givenCar.race(4) - then("현재 차량의 주행거리를 1만큼 누적한다.") { + then("현재 차량의 주행거리는 1만큼 누적된다.") { givenCar.distance shouldBe 1 } } } - given("경주에 참가하는 자동차 한대에 3이하의 숫자가 주어지고") { + given("차량 멈춤 : 경주에 참가하는 자동차 한대에") { val givenCar = Car("참가 차량") - `when`("경주를 진행하면") { + `when`("3이하의 숫자로 경주를 진행하면") { givenCar.race(3) then("현재 차량의 주행거리는 누적되지 않는다.") { givenCar.distance shouldBe 0 @@ -26,26 +26,26 @@ class CarTest : BehaviorSpec({ } } - given("경주에 참가하는 자동차 한대가 주어지고") { + given("경주에 참가하는 자동차 한대에") { val car = Car("참가 차량") - `when`("첫번째 라운드에 4가 주어지면") { + `when`("첫번째 라운드에 4이상의 숫자로 경주를 진행하면") { car.race(4) then("차량 전진 횟수가 1 증가한다.") { car.distance shouldBe 1 } } - `when`("두번째 라운드에 3이 주어지면") { + `when`("두번째 라운드에 3이하의 숫자로 경주를 진행하면") { car.race(3) then("차량의 전진 횟수는 증가하지 않는다.") { car.distance shouldBe 1 } } - `when`("세번째 라운드에 6이 주어지면") { + `when`("세번째 라운드에 4이상의 숫자로 경주를 진행하면") { car.race(6) then("차량 전진 횟수가 1 증가한다.") { car.distance shouldBe 2 } } } -}) +}) \ No newline at end of file From 754af097b27aa42f616f88f3aea9001d31505fe1 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 05:35:15 +0900 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EA=B3=84=EC=B8=B5=EA=B3=BC=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EA=B3=84=EC=B8=B5=EC=97=90=20=EA=B5=AC=ED=98=84=EB=90=9C=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EB=B6=80=EB=A5=BC=20View=20=EA=B3=84?= =?UTF-8?q?=EC=B8=B5=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AS-IS : `Cars` 도메인에서 직접 각 라운드 결과를 출력하고, `RacingService` 계층에서 우승자를 출력 - TO-BE : - 각 `Car` 객체가 해당 라운드의 경주를 완료 하면 각 라운드 결과를 기록한 객체(RoundResult)를 반환 - `Cars` 객체는 `Car` 객체로부터 각 라운드 결과 객체를 반환받아 합산한 후, 전체 라운드 결과 객체(RoundResults)를 반환 - [x] `RacingService` 계층은 반환받은 전체 라운드 결과 객체를 출력부에 전달하여 로직과 출력부를 분리 - 각 라운드 결과 객체에 현재 `Car` 객체의 상태값을 저장하기 위한 DeepCopy 함수 구현 - Cars 도메인 계층에서 출력부를 분리함에 따라, 출력부에서 접근하고 있던 `Cars` 도메인의 `elements` 프로퍼티의 접근 제한자를 `private`에서 `public`으로 변경 - 개선 사항 : - 출력을 위해 public 으로 열려있는 도메인 계층의 프로퍼티의 접근 제한자를 private 으로 변경 - 서비스 계층과 도메인 계층은 자동차 경주만을 위한 코드가 작성되고, 출력 부는 로직이 완료 된 후 반환된 객체를 넘겨 받아 결과를 출력함으로써 출력부에 대한 로직 실행부의 의존성 제거 --- docs/STEP4-README.md | 40 ++++++++++++------- .../controller/RacingCarController.kt | 4 +- src/main/kotlin/step3/racingcar/domain/Car.kt | 2 +- .../kotlin/step3/racingcar/domain/Cars.kt | 12 +++--- .../step3/racingcar/domain/RoundResult.kt | 9 +++++ .../step3/racingcar/domain/RoundResults.kt | 12 ++++++ .../racingcar/service/RacingCarService.kt | 17 ++++---- .../kotlin/step3/racingcar/view/ResultView.kt | 19 +++++++-- .../kotlin/step3/racingcar/domain/CarTest.kt | 2 +- .../kotlin/step3/racingcar/domain/CarsTest.kt | 4 +- .../step3/racingcar/domain/RoundResultTest.kt | 17 ++++++++ .../racingcar/service/RacingCarServiceTest.kt | 11 ++--- 12 files changed, 106 insertions(+), 43 deletions(-) create mode 100644 src/main/kotlin/step3/racingcar/domain/RoundResult.kt create mode 100644 src/main/kotlin/step3/racingcar/domain/RoundResults.kt create mode 100644 src/test/kotlin/step3/racingcar/domain/RoundResultTest.kt diff --git a/docs/STEP4-README.md b/docs/STEP4-README.md index 445fbae675..ea638adaf6 100644 --- a/docs/STEP4-README.md +++ b/docs/STEP4-README.md @@ -35,16 +35,16 @@ ## 코드 리뷰 피드백 내용 정리 * [x] 별도의 역할 없이 객체를 포장하고 있는 `Cars`일급 컬렉션과 참가한 자동차 목록으로 부터 우승자를 산출하는 `Winners` 일급 컬렉션 역할을 병합 -* [ ] 각 라운드 진행 방식 변경 + +* [x] 각 라운드 진행 방식 변경 * AS-IS : `Car` 객체에 미리 생성된 난수를 판별하여 주행거리를 누적 * TO-BE : 매 라운드 마다 발생된 난수를 판별한 후, 차량 주행거리를 누적 * [x] 추상화를 통해 난수에 대한 의존성으로 테스트 하기 어려운 현상 개선 * [x] 난수 발생 부를 추상화하여 운영 환경에 주입되는 구현체와 테스트 환경에 주입되는 구현체를 별도로 구현한 뒤, 각 실행 환경에 맞는 구현체를 선택하여 주입함으로써 난수로 인해 테스트 하기 어려운 문제 개선 * 추상화된 난수 생성부 주입을 통한 자동차 경주 진행 * [x] 자동차 경주를 진행하는 `RacingService` 객체에 추상화된 난수 생성 부의 구현체를 주입하여 생성한 후, 각 라운드 진행 시 운영 환경에서는 실제 난수 생성 구현체가 동작하고, 테스트 환경에서는 난수 발생 시 의도한 수를 반환하는 구현체가 동작할 수 있도록 수정 - * [x] 난수 생성 부 추상화에 따른 `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드 제거 - * [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 - * 외부에서 해당 객체에 값에 접근하는 처리 방식에서 객체에 메시지를 보내 그 결과값을 반환받는 방식으로 수정 + * [x] 난수 생성 부 추상화에 따른 `Car`객체에 자동차를 표현하기 어색한 `RandomNumbers` 필드 제거 + * 테스트 표현 개선 * [x] 도메인 로직을 활용한 테스트 결과 검증 부를 하드코딩된 값으로 변경 * 테스트 검증 부가 도메인 로직을 참조하고 있음에 따라 로직 변경 시 테스트로서의 역할을 제대로 수행할 수 없는 문제점 개선을 위해 테스트 검증 부를 하드코딩된 값으로 변경 @@ -61,25 +61,35 @@ ## 2차 코드 리뷰 피드백 내용 정리 * Car 도메인 수정 - * [x] variable로 선언된 distance(누적 주행 거리) 속성의 외부 변경을 제한하기 위해 누락된 private 접근 제어자 추가 - * [x] 미사용 상수 제거 및 distance 속성의 초기값 상수 추가 + * [x] `variable`로 선언된 `distance`(누적 주행 거리) 속성의 외부 변경을 제한하기 위해 누락된 `private` 접근 제어자 추가 + * [x] 미사용 상수 제거 및 `distance` 속성의 초기값 상수 추가 * [x] 경주가 완료된 차량의 용이한 테스트를 위해 `Car` 도메인의 `distance` 속성의 초기값 설정이 가능하도록 수정 * [x] 우승자 산출 방식 변경 : 해당 차량의 누적 주행거리가 최대값인지를 확인하는 함수 작성 * AS-IS : 우승자 산출을 위해 경주에 기존 `Cars` 객체에서 각 원소(Car)를 순회하며 distance 속성에 접근하여 최대 누적 주행거리를 비교 * TO-BE : `Cars` 객체에서 각 원소(Car)를 순회하며 누적 주행거리가 최대값인 원소를 추출 - * [x] 우승자 산출을 위한 Cars 도메인 TC의 Car.race() 호출 부 개선 - * 우승자 산출 테스트를 위해 Car.race()를 호출하여 누적 주행거리를 설정하는 테스트 방식을 Car 도메인의 distance 속성 초기값 설정을 통해 제거하여 테스트 간소화 - * 경주를 완료한 차량 생성을 위해 더 이상 불필요한 test fixture 생성 함수 제거 + * [x] 우승자 산출을 위한 `Cars` 도메인 TC의 Car.race() 호출 부 개선 + * 우승자 산출 테스트를 위해 Car.race()를 호출하여 누적 주행거리를 설정하는 테스트 방식을 `Car` 도메인의 `distance` 속성 초기값 설정을 통해 제거하여 테스트 간소화 + * 경주를 완료한 차량 생성을 위해 더 이상 불필요한 `test fixture` 생성 함수 제거 + * Cars 도메인 수정 * [x] private 생성자의 `elements` 프로퍼티를 반환하는 getter 제거하고, 해당 프로퍼티를 public 으로 변경 * [x] ','를 기준으로 우승자 이름을 묶어내는 `Cars` 도메인의 함수를 View 계층으로 이동 - * [x] Service 계층에서 Cars 도메인의 각 원소(Car)에 접근하여 경주를 진행하는 방식에서 Cars 도메인에 메세지를 보내어 각 원소(Car)의 race() 함수를 호출함으로써 도메인이 메세지를 받아 직접 로직을 처리 하도록 수정 + * [x] Service 계층에서 `Cars` 도메인의 각 원소(Car)에 접근하여 경주를 진행하는 방식에서 Cars 도메인에 메세지를 보내어 각 원소(Car)의 race() 함수를 호출함으로써 도메인이 메세지를 받아 직접 로직을 처리 하도록 수정 + * [x] 명확하지 않은 난수 생성기 인터페이스 이름 변경 * AS-IS : RandomNumber * TO-BE : NumberGenerator + * [x] 명확하지 않은 Given/When 테스트 표현 개선 -* [ ] Service 계층에 구현된 각 라운드 결과 출력 부를 View 계층으로 이동 -* [ ] 결과 출력 시 특정 라운드에 차량에 발생한 난수와, 그 결과를 기록하는 별도의 객체를 이용하도록 수정 - * Cars 도메인에서 직접 각 라운드 결과를 출력하는 방식을 Cars의 각 원소(Car)가 경주를 완료한 기록을 별도의 객체로 반환하여 출력 부에 전달 - * 출력부(외부)에서 Cars 도메인의 값에 접근하는 처리 방식에서 Cars 도메인에 메시지를 보내 그 결과값을 반환 받는 방식으로 수정 - * Cars 도메인 계층에서 출력부를 분리함에 따라, 출력부에서 접근하고 있던 Cars 도메인의 elements 프로퍼티의 접근 제한자를 private에서 public으로 변경 \ No newline at end of file + +* 도메인 계층과 서비스 계층에 구현된 출력 부를 View 계층으로 이동 + * AS-IS : `Cars` 도메인에서 직접 각 라운드 결과를 출력하고, `RacingService` 계층에서 우승자를 출력 + * TO-BE : + * [x] 각 `Car` 객체가 해당 라운드의 경주를 완료 하면 각 라운드 결과를 기록한 객체(RoundResult)를 반환 + * [x] `Cars` 객체는 `Car` 객체로부터 각 라운드 결과 객체를 반환받아 합산한 후, 전체 라운드 결과 객체(RoundResults)를 반환 + * [x] `RacingService` 계층은 반환받은 전체 라운드 결과 객체를 출력부에 전달하여 로직과 출력부를 분리 + * [x] 각 라운드 결과 객체에 현재 `Car` 객체의 상태값을 저장하기 위한 DeepCopy 함수 구현 + * [x] Cars 도메인 계층에서 출력부를 분리함에 따라, 출력부에서 접근하고 있던 `Cars` 도메인의 `elements` 프로퍼티의 접근 제한자를 `private`에서 `public`으로 변경 + * 개선 사항 : + * 출력을 위해 public 으로 열려있는 도메인 계층의 프로퍼티의 접근 제한자를 private 으로 변경 + * 서비스 계층과 도메인 계층은 자동차 경주만을 위한 코드가 작성되고, 출력 부는 로직이 완료 된 후 반환된 객체를 넘겨 받아 결과를 출력함으로써 출력부에 대한 로직 실행부의 의존성 제거 diff --git a/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt b/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt index 66be05936c..806187f7c6 100644 --- a/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt +++ b/src/main/kotlin/step3/racingcar/controller/RacingCarController.kt @@ -7,6 +7,7 @@ import step3.racingcar.utils.CarGenerator import step3.racingcar.utils.RandomNumberGenerator import step3.racingcar.view.InputView.Companion.inputJoinerCarsGuideMessagePrinter import step3.racingcar.view.InputView.Companion.inputRoundCountGuideMessagePrinter +import step3.racingcar.view.ResultView class RacingCarController { private val racingCarService: RacingCarService = RacingCarService(RandomNumberGenerator()) @@ -16,6 +17,7 @@ class RacingCarController { val totalRound = inputRoundCountGuideMessagePrinter() val cars = Cars.of(CarGenerator.generate(carNames)) val playInfo = PlayInfo(cars, totalRound) - racingCarService.play(playInfo) + val playResult = racingCarService.play(playInfo) + ResultView.printResult(playResult) } } diff --git a/src/main/kotlin/step3/racingcar/domain/Car.kt b/src/main/kotlin/step3/racingcar/domain/Car.kt index edc95041c3..6593b8d277 100644 --- a/src/main/kotlin/step3/racingcar/domain/Car.kt +++ b/src/main/kotlin/step3/racingcar/domain/Car.kt @@ -11,6 +11,6 @@ class Car(val name: String, var distance: Int = INITIAL_DISTANCE) { } fun isMaximumDistance(maxDistance: Int): Boolean = distance == maxDistance - private fun isMove(randomNumber: Int): Boolean = randomNumber >= MOVE_CRITERIA + fun copy(): Car = Car(name, distance) } diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index eaaa10a46b..15107b88f3 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -1,19 +1,17 @@ package step3.racingcar.domain -import step3.racingcar.view.ResultView - -class Cars private constructor(val elements: List) { - fun elements(): List = elements - +class Cars private constructor(private val elements: List) { fun size(): Int = elements.size operator fun get(index: Int): Car = elements[index] - fun race(currentRoundIndex: Int, numberGenerator: NumberGenerator) { + fun raceRoundResult(numberGenerator: NumberGenerator): RoundResult { + val roundResult = RoundResult() elements.forEach { it.race(numberGenerator.value()) + roundResult.add(it) } - ResultView.printRoundResult(currentRoundIndex, this) + return roundResult } fun winnerNames(): List { diff --git a/src/main/kotlin/step3/racingcar/domain/RoundResult.kt b/src/main/kotlin/step3/racingcar/domain/RoundResult.kt new file mode 100644 index 0000000000..bb2ee7914a --- /dev/null +++ b/src/main/kotlin/step3/racingcar/domain/RoundResult.kt @@ -0,0 +1,9 @@ +package step3.racingcar.domain + +class RoundResult { + private val elements: MutableList = mutableListOf() + + operator fun get(index: Int): Car = elements[index] + fun add(car: Car) = elements.add(car.copy()) + fun size(): Int = elements.size +} diff --git a/src/main/kotlin/step3/racingcar/domain/RoundResults.kt b/src/main/kotlin/step3/racingcar/domain/RoundResults.kt new file mode 100644 index 0000000000..e2e013e5f6 --- /dev/null +++ b/src/main/kotlin/step3/racingcar/domain/RoundResults.kt @@ -0,0 +1,12 @@ +package step3.racingcar.domain + +class RoundResults private constructor(val totalRound: Int, val cars: Cars) { + private val elements: MutableList = mutableListOf() + + operator fun get(index: Int) = elements[index] + fun add(roundResult: RoundResult) = elements.add(roundResult) + + companion object { + fun of(playInfo: PlayInfo): RoundResults = RoundResults(playInfo.totalRound, playInfo.cars) + } +} diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index c219b2a0f0..5926c511b0 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -2,20 +2,23 @@ package step3.racingcar.service import step3.racingcar.domain.Car import step3.racingcar.domain.Cars -import step3.racingcar.domain.PlayInfo import step3.racingcar.domain.NumberGenerator -import step3.racingcar.view.ResultView.Companion.printWinner +import step3.racingcar.domain.PlayInfo +import step3.racingcar.domain.RoundResult +import step3.racingcar.domain.RoundResults class RacingCarService(private val numberGenerator: NumberGenerator) { - fun play(playInfo: PlayInfo) { + fun play(playInfo: PlayInfo): RoundResults { + val roundResults = RoundResults.of(playInfo) repeat(playInfo.totalRound) { - playEachRound(it, playInfo.cars) + val playEachRoundAndReturn = playEachRound(playInfo.cars) + roundResults.add(playEachRoundAndReturn) } - printWinner(playInfo.cars) + return roundResults } - fun playEachRound(currentRoundIndex: Int, cars: Cars) = - cars.race(currentRoundIndex, numberGenerator) + fun playEachRound(cars: Cars): RoundResult = + cars.raceRoundResult(numberGenerator) fun playEachRoundByCar(car: Car) { val randomNumber = numberGenerator.value() diff --git a/src/main/kotlin/step3/racingcar/view/ResultView.kt b/src/main/kotlin/step3/racingcar/view/ResultView.kt index d7c8ffa6e3..a263d789f6 100644 --- a/src/main/kotlin/step3/racingcar/view/ResultView.kt +++ b/src/main/kotlin/step3/racingcar/view/ResultView.kt @@ -2,6 +2,8 @@ package step3.racingcar.view import step3.racingcar.domain.Car import step3.racingcar.domain.Cars +import step3.racingcar.domain.RoundResult +import step3.racingcar.domain.RoundResults class ResultView { companion object { @@ -10,9 +12,18 @@ class ResultView { private const val WINNER_GUIDE_MESSAGE_FORMAT = "%s 가 최종 우승했습니다." private const val WINNER_NAME_JOINING_SEPARATOR = ", " - fun printRoundResult(currentRoundIndex: Int, cars: Cars) { - roundCompleteGuideMessage(currentRoundIndex) - cars.elements().forEach(::printEachCarRoundResult) + fun printResult(roundResults: RoundResults) { + repeat(roundResults.totalRound) { + roundCompleteGuideMessage(it) + printRoundResult(roundResults[it]) + } + printWinner(roundResults.cars) + } + + private fun printRoundResult(roundResult: RoundResult) { + repeat(roundResult.size()) { + printEachCarRoundResult(roundResult[it]) + } } private fun roundCompleteGuideMessage(currentRoundIndex: Int) { @@ -31,7 +42,7 @@ class ResultView { return result } - fun printWinner(cars: Cars) { + private fun printWinner(cars: Cars) { println() val formattedWinnerNames = formatToWinnerNames(cars.winnerNames()) println(WINNER_GUIDE_MESSAGE_FORMAT.format(formattedWinnerNames)) diff --git a/src/test/kotlin/step3/racingcar/domain/CarTest.kt b/src/test/kotlin/step3/racingcar/domain/CarTest.kt index adce90874b..3e34b525ef 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarTest.kt @@ -48,4 +48,4 @@ class CarTest : BehaviorSpec({ } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt index d3658b7c20..de13b87caa 100644 --- a/src/test/kotlin/step3/racingcar/domain/CarsTest.kt +++ b/src/test/kotlin/step3/racingcar/domain/CarsTest.kt @@ -1,7 +1,7 @@ package step3.racingcar.domain import io.kotest.core.spec.style.BehaviorSpec -import io.kotest.matchers.shouldBe +import io.kotest.matchers.collections.shouldContainExactly internal class CarsTest : BehaviorSpec({ given("경주를 완료한 자동차 객체 목록이 주어지고") { @@ -13,7 +13,7 @@ internal class CarsTest : BehaviorSpec({ val 참가_차량_목록 = listOf(첫_번째_차량, 두_번째_차량, 세_번째_차량) val given = Cars.of(참가_차량_목록) then("우승한 차량들의 이름을 반환한다.") { - given.winnerNames() shouldBe "두 번째 차량, 세 번째 차량" + given.winnerNames().shouldContainExactly("두 번째 차량", "세 번째 차량") } } } diff --git a/src/test/kotlin/step3/racingcar/domain/RoundResultTest.kt b/src/test/kotlin/step3/racingcar/domain/RoundResultTest.kt new file mode 100644 index 0000000000..231771489c --- /dev/null +++ b/src/test/kotlin/step3/racingcar/domain/RoundResultTest.kt @@ -0,0 +1,17 @@ +package step3.racingcar.domain + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldNotBe + +class RoundResultTest : StringSpec({ + "각 라운드의 경주 결과를 기록하는 객체 생성 시, 현재 시점의 Car 객체 정보를 DeepCopy 한다." { + val 첫번째_차량 = Car("첫번째 차량", 3) + val 두번째_차량 = Car("두번째 차량", 2) + val roundResult = RoundResult() + roundResult.add(첫번째_차량) + roundResult.add(두번째_차량) + + roundResult[0] shouldNotBe 첫번째_차량 + roundResult[1] shouldNotBe 두번째_차량 + } +}) diff --git a/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt b/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt index 5eca506ae6..68e6f3367a 100644 --- a/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt +++ b/src/test/kotlin/step3/racingcar/service/RacingCarServiceTest.kt @@ -1,6 +1,7 @@ package step3.racingcar.service import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.shouldBe import step3.racingcar.domain.Car import step3.racingcar.domain.Cars @@ -30,14 +31,14 @@ internal class RacingCarServiceTest : BehaviorSpec({ `when`("첫번째 라운드에 4이상의 난수가 발생하면") { racingCarService = of(4) - racingCarService.playEachRound(0, given) + racingCarService.playEachRound(given) then("차량의 주행거리는 1 누적된다.") { 참가_차량.distance shouldBe 1 } } `when`("두번째 라운드에 3이하의 난수가 발생하면") { racingCarService = of(3) - racingCarService.playEachRound(1, given) + racingCarService.playEachRound(given) then("차량의 주행거리는 누적되지 않는다.") { 참가_차량.distance shouldBe 1 } @@ -58,7 +59,7 @@ internal class RacingCarServiceTest : BehaviorSpec({ } then("첫번째 차량이 누적거리 1로 우승한다.") { 첫번째_참가_차량.distance shouldBe 1 - given.winnerNames() shouldBe "첫번째_참가_차량" + given.winnerNames().shouldContainExactly("첫번째_참가_차량") } } } @@ -76,7 +77,7 @@ internal class RacingCarServiceTest : BehaviorSpec({ it.playEachRoundByCar(두번째_참가_차량) } then("첫번째 차량을 우승 차량으로 반환한다.") { - given.winnerNames() shouldBe "첫번째_참가_차량" + given.winnerNames().shouldContainExactly("첫번째_참가_차량") } } @@ -89,7 +90,7 @@ internal class RacingCarServiceTest : BehaviorSpec({ } then("첫번째 차량이 누적거리 2로 우승한다.") { 첫번째_참가_차량.distance shouldBe 2 - given.winnerNames() shouldBe "첫번째_참가_차량" + given.winnerNames().shouldContainExactly("첫번째_참가_차량") } } } From b024f5ae441024d34b4da5c8c8b71fa7b978f445 Mon Sep 17 00:00:00 2001 From: choi-ys Date: Wed, 30 Nov 2022 21:41:18 +0900 Subject: [PATCH 15/15] =?UTF-8?q?style:=20Cars=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=9D=98=20=EB=AA=85=ED=99=95=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=ED=95=A8=EC=88=98=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AS-IS : raceRoundResult() - TO-BE : race --- src/main/kotlin/step3/racingcar/domain/Cars.kt | 2 +- src/main/kotlin/step3/racingcar/service/RacingCarService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/step3/racingcar/domain/Cars.kt b/src/main/kotlin/step3/racingcar/domain/Cars.kt index 15107b88f3..90bd8b9781 100644 --- a/src/main/kotlin/step3/racingcar/domain/Cars.kt +++ b/src/main/kotlin/step3/racingcar/domain/Cars.kt @@ -5,7 +5,7 @@ class Cars private constructor(private val elements: List) { operator fun get(index: Int): Car = elements[index] - fun raceRoundResult(numberGenerator: NumberGenerator): RoundResult { + fun race(numberGenerator: NumberGenerator): RoundResult { val roundResult = RoundResult() elements.forEach { it.race(numberGenerator.value()) diff --git a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt index 5926c511b0..add4a8bf2b 100644 --- a/src/main/kotlin/step3/racingcar/service/RacingCarService.kt +++ b/src/main/kotlin/step3/racingcar/service/RacingCarService.kt @@ -18,7 +18,7 @@ class RacingCarService(private val numberGenerator: NumberGenerator) { } fun playEachRound(cars: Cars): RoundResult = - cars.raceRoundResult(numberGenerator) + cars.race(numberGenerator) fun playEachRoundByCar(car: Car) { val randomNumber = numberGenerator.value()