From 0a25a5a43cb4ccdfe4d7555097495bb0eb7b23ea Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:10:30 +0900 Subject: [PATCH 01/26] =?UTF-8?q?=20README.md=20=EC=97=90=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 491aece1..10c2f1d9 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ -# java-racingcar-precourse \ No newline at end of file +# java-racingcar-precourse + +# 기능 목록 + +## Model + +### Racer Entity + +#### Data +- [ ] 자동차 경주자는 이름이 있다. +- [ ] 자동차 경주자는 이동한 거리를 정수로 기록한다. + +#### Act +- [ ] 자동차 경주자는 전진할 수 있다. + - [ ] 전진할 때 숫자로 된 입력을 받는다. + - [ ] 입력받은 숫자가 threshold 값을 넘어야 전진한다. + - [ ] threshold 값을 넘지 못하면 전진하지 않는다. +- [ ] 자동차 경주자는 이름과 함께 이동한 거리를 출력할 수 있다. +- [ ] 자동차 경주자는 숫자로 된 입력을 받아, 우승했는지 여부를 알려준다. + +### Racer VO + +#### Data +- [ ] 자동차 경주자는 이름이 있다. +- [ ] 자동차 경주자는 이동한 거리를 정수로 기록한다. +- [ ] 자동차 경주자는 우승 여부를 기록한다. + +### Controller + +#### Data +- [ ] 자동차 경주자 목록을 가지고 있는다. +- [ ] 자동차 경주 진행 시도 횟수를 가지고 있는다. +- [ ] 현재 자동차 경주 진행 횟수를 가지고 있는다. + +#### Act +- [ ] 자동차 이름을 1개의 문자열로 입력받아 자동차 목록을 생성한다. + - [ ] 자동차 이름은 쉼표를 기준으로 구분하며, 이름이 NAME_MAX_LENGTH보다 길 경우 IllegalArgumentException을 발생시킨다. +- [ ] 경주 게임을 진행할 횟수를 입력 받는다. + - [ ] 횟수에는 최댓값 제한은 없으며, 음수 또는 숫자가 아닐 시 IllegalArgumentException을 발생시킨다. + - [ ] 입력 받은 숫자로 경주 진행 시도 횟수를 초기화하고, 현재 자동차 경주 진행 횟수는 0으로 리셋한다. +- [ ] 경주 게임을 1번 진행한다. + - [ ] 만약 자동차 목록 또는 경주 게임 진행 횟수가 세팅이 안 되었을 경우 IllegalStateException을 발생시킨다. + - [ ] 이미 게임이 종료 되었을 경우 IllegalStateException을 발생시킨다. + - [ ] 게임이 끝나면 Racer VO 목록과 게임 종료 여부를 반환한다. + +### View + +#### Data +- [ ] 자동차 경주 컨트롤러를 가지고 있는다. + +#### Act +- [ ] 자동차 경주 게임을 시작한다. + - [ ] 경주할 자동차 이름을 입력받는다. + - [ ] 시도할 횟수를 입력 받는다. + - [ ] 자동차 경주 게임을 진행하며 매번 결과를 출력한다. + - [ ] 최종 우승자를 출력한다. + - [ ] 입력을 받을 때 에러가 발생하면 "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. + +### Util + +### RandomNumberGenerator + +#### Act +- [ ] 입력 받은 숫자 범위에서 무작위 정수를 생성한다. + +### ErrorMessageGenerator + +#### Act +- [ ] 입력 받은 에러 메세지를 정해진 포맷으로 바꿔서 반환한다. \ No newline at end of file From d91971f8de1e69751273131febb1cfda5feaff22 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:18:37 +0900 Subject: [PATCH 02/26] =?UTF-8?q?=20Racer=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/entity/RacerTest.java | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/test/java/entity/RacerTest.java diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java new file mode 100644 index 00000000..72bfeb3a --- /dev/null +++ b/src/test/java/entity/RacerTest.java @@ -0,0 +1,24 @@ +package entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; + +import static org.assertj.core.api.Assertions.*; +class RacerTest { + @Test + @DisplayName("Racer 생성자 테스트") + void racerConstructorTest() { + // given: 생성자 데이터 + String name = "Racer"; + BigInteger expectedMovedDistance = new BigInteger("0"); + + // when + Racer racer = new Racer(name); + + // then + assertThat(racer.getName()).isEqualTo(name); + assertThat(racer.getMovedDistance()).isEqualTo(expectedMovedDistance); + } +} From 40df0cd0c72e3b524c07999eb0c7bf15d0f3419a Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:20:43 +0900 Subject: [PATCH 03/26] =?UTF-8?q?=20Racer=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/entity/Racer.java | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/entity/Racer.java diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java new file mode 100644 index 00000000..c875f6c1 --- /dev/null +++ b/src/main/java/entity/Racer.java @@ -0,0 +1,23 @@ +package entity; + +import java.math.BigInteger; + +public class Racer { + public BigInteger MOVE_THRESHOLD = new BigInteger("3"); + private String name; + + private BigInteger movedDistance; + + public Racer(String name) { + this.name = name; + this.movedDistance = new BigInteger("0"); + } + + public String getName() { + return name; + } + + public BigInteger getMovedDistance() { + return movedDistance; + } +} From dd442db7a74f0650e53aeccf98a8b368983585af Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:29:59 +0900 Subject: [PATCH 04/26] =?UTF-8?q?=20Racer=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20Exception=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/entity/Racer.java | 3 ++- src/test/java/entity/RacerTest.java | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index c875f6c1..263db196 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -3,7 +3,8 @@ import java.math.BigInteger; public class Racer { - public BigInteger MOVE_THRESHOLD = new BigInteger("3"); + public static BigInteger MOVE_THRESHOLD = new BigInteger("3"); + private String name; private BigInteger movedDistance; diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index 72bfeb3a..70a1f66d 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -1,11 +1,16 @@ package entity; +import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; import static org.assertj.core.api.Assertions.*; + class RacerTest { @Test @DisplayName("Racer 생성자 테스트") @@ -21,4 +26,21 @@ void racerConstructorTest() { assertThat(racer.getName()).isEqualTo(name); assertThat(racer.getMovedDistance()).isEqualTo(expectedMovedDistance); } + + @Test + @DisplayName("Racer 생성자 실패 테스트") + void racerConstructorWithInvalidDataTest() { + // given: 생성자 데이터 + List invalidNameList = Arrays.asList(null, "", " "); + + for (String invalidName : invalidNameList) { + // when + ThrowableAssert.ThrowingCallable constructorCall = () -> new Racer(invalidName); + + // then + assertThatThrownBy(constructorCall) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Racer.VALIDATE_NAME_ERROR_MESSAGE); + } + } } From 3ef09700b4e4d5432dba5db9c3f93ab1d14b7596 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:32:31 +0900 Subject: [PATCH 05/26] =?UTF-8?q?=20Racer=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=EC=97=90=20validate=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/entity/Racer.java | 12 +++++++++++- src/test/java/entity/RacerTest.java | 7 ++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index 263db196..f2088ff7 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -5,15 +5,25 @@ public class Racer { public static BigInteger MOVE_THRESHOLD = new BigInteger("3"); + public static String VALIDATE_NAME_ERROR_MESSAGE = "name은 null이거나 빈 문자열일 수 없습니다."; + private String name; private BigInteger movedDistance; public Racer(String name) { - this.name = name; + this.name = validateName(name); this.movedDistance = new BigInteger("0"); } + private String validateName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException(VALIDATE_NAME_ERROR_MESSAGE); + } + + return name.trim(); + } + public String getName() { return name; } diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index 70a1f66d..b4b8c391 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -16,14 +16,15 @@ class RacerTest { @DisplayName("Racer 생성자 테스트") void racerConstructorTest() { // given: 생성자 데이터 - String name = "Racer"; + String givenName = "Racer "; + String expectedName = "Racer"; BigInteger expectedMovedDistance = new BigInteger("0"); // when - Racer racer = new Racer(name); + Racer racer = new Racer(givenName); // then - assertThat(racer.getName()).isEqualTo(name); + assertThat(racer.getName()).isEqualTo(expectedName); assertThat(racer.getMovedDistance()).isEqualTo(expectedMovedDistance); } From e0750f18495620287c4519630ff72d630c878ab2 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:38:22 +0900 Subject: [PATCH 06/26] =?UTF-8?q?=20Racer=20moveIfCan=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/entity/RacerTest.java | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index b4b8c391..7151140a 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -44,4 +44,32 @@ void racerConstructorWithInvalidDataTest() { .hasMessage(Racer.VALIDATE_NAME_ERROR_MESSAGE); } } + + @Test + @DisplayName("Racer moveIfCan 전진 테스트") + void racerMoveIfCan() { + // given: 전진하는 데이터 + BigInteger input = Racer.MOVE_THRESHOLD.add(new BigInteger("1")); + Racer racer = new Racer("Tester"); + + // when + racer.moveIfCan(input); + + // then + assertThat(racer.getMovedDistance()).isEqualTo(new BigInteger("1")); + } + + @Test + @DisplayName("Racer moveIfCan 정지 테스트") + void racerMoveIfCan() { + // given: 전진하는 데이터와 전진하지 않는 데이터 + BigInteger input = Racer.MOVE_THRESHOLD; + Racer racer = new Racer("Tester"); + + // when + racer.moveIfCan(input); + + // then + assertThat(racer.getMovedDistance()).isEqualTo(new BigInteger("0")); + } } From 118eeacc6052af75c1ce50257b6ee0b568b0c212 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:41:19 +0900 Subject: [PATCH 07/26] =?UTF-8?q?=20Racer=20moveIfCan=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/entity/Racer.java | 16 +++++++++++----- src/test/java/entity/RacerTest.java | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index f2088ff7..bc1ba608 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -16,12 +16,10 @@ public Racer(String name) { this.movedDistance = new BigInteger("0"); } - private String validateName(String name) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException(VALIDATE_NAME_ERROR_MESSAGE); + public void moveIfCan(BigInteger input) { + if(input.compareTo(MOVE_THRESHOLD) > 0) { + movedDistance = movedDistance.add(new BigInteger("1")); } - - return name.trim(); } public String getName() { @@ -31,4 +29,12 @@ public String getName() { public BigInteger getMovedDistance() { return movedDistance; } + + private String validateName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException(VALIDATE_NAME_ERROR_MESSAGE); + } + + return name.trim(); + } } diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index 7151140a..5187bfc8 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -47,7 +47,7 @@ void racerConstructorWithInvalidDataTest() { @Test @DisplayName("Racer moveIfCan 전진 테스트") - void racerMoveIfCan() { + void racerMoveIfCan_WillMove() { // given: 전진하는 데이터 BigInteger input = Racer.MOVE_THRESHOLD.add(new BigInteger("1")); Racer racer = new Racer("Tester"); @@ -61,7 +61,7 @@ void racerMoveIfCan() { @Test @DisplayName("Racer moveIfCan 정지 테스트") - void racerMoveIfCan() { + void racerMoveIfCan_WillStop() { // given: 전진하는 데이터와 전진하지 않는 데이터 BigInteger input = Racer.MOVE_THRESHOLD; Racer racer = new Racer("Tester"); From 7de8fe58fbbac40177f38c814e3d0670207eb070 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:44:42 +0900 Subject: [PATCH 08/26] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 10c2f1d9..459db0f0 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,15 @@ ### Racer Entity #### Data -- [ ] 자동차 경주자는 이름이 있다. -- [ ] 자동차 경주자는 이동한 거리를 정수로 기록한다. +- [x] 자동차 경주자는 이름이 있다. +- [x] 자동차 경주자는 이동한 거리를 정수로 기록한다. #### Act -- [ ] 자동차 경주자는 전진할 수 있다. - - [ ] 전진할 때 숫자로 된 입력을 받는다. - - [ ] 입력받은 숫자가 threshold 값을 넘어야 전진한다. - - [ ] threshold 값을 넘지 못하면 전진하지 않는다. -- [ ] 자동차 경주자는 이름과 함께 이동한 거리를 출력할 수 있다. +- [x] 자동차 경주자는 전진할 수 있다. + - [x] 전진할 때 숫자로 된 입력을 받는다. + - [x] 입력받은 숫자가 threshold 값을 넘어야 전진한다. + - [x] threshold 값을 넘지 못하면 전진하지 않는다. +- ~~[ ] 자동차 경주자는 이름과 함께 이동한 거리를 출력할 수 있다.~~ - [ ] 자동차 경주자는 숫자로 된 입력을 받아, 우승했는지 여부를 알려준다. ### Racer VO From ea7112084cb936d553803cdc5cf6659198b952bd Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:50:29 +0900 Subject: [PATCH 09/26] =?UTF-8?q?=20Racer=20isWinner=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/entity/RacerTest.java | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index 5187bfc8..a310c9dc 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -47,7 +47,7 @@ void racerConstructorWithInvalidDataTest() { @Test @DisplayName("Racer moveIfCan 전진 테스트") - void racerMoveIfCan_WillMove() { + void racerMoveIfCan_WillMoveTest() { // given: 전진하는 데이터 BigInteger input = Racer.MOVE_THRESHOLD.add(new BigInteger("1")); Racer racer = new Racer("Tester"); @@ -61,7 +61,7 @@ void racerMoveIfCan_WillMove() { @Test @DisplayName("Racer moveIfCan 정지 테스트") - void racerMoveIfCan_WillStop() { + void racerMoveIfCan_WillStopTest() { // given: 전진하는 데이터와 전진하지 않는 데이터 BigInteger input = Racer.MOVE_THRESHOLD; Racer racer = new Racer("Tester"); @@ -72,4 +72,28 @@ void racerMoveIfCan_WillStop() { // then assertThat(racer.getMovedDistance()).isEqualTo(new BigInteger("0")); } + + @Test + @DisplayName("Racer isWinner 테스트") + void racerIsWinnerTest_WillTrueTest() { + // given: 전진하는 데이터와 전진하지 않는 데이터 + Racer racer = new Racer("Tester"); + BigInteger input = racer.getMovedDistance(); + boolean expectedResult = true; + + // when & then + assertThat(racer.isWinner(input)).isEqualTo(expectedResult); + } + + @Test + @DisplayName("Racer isWinner 테스트") + void racerIsWinnerTest_WillFalseTest() { + // given: 전진하는 데이터와 전진하지 않는 데이터 + Racer racer = new Racer("Tester"); + BigInteger input = racer.getMovedDistance().add("1"); + boolean expectedResult = false; + + // when & then + assertThat(racer.isWinner(input)).isEqualTo(expectedResult); + } } From 90d51456c65fdc7c863006493a40bad67553aa3b Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 10:52:06 +0900 Subject: [PATCH 10/26] =?UTF-8?q?=20Racer=20isWinner=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/entity/Racer.java | 4 ++++ src/test/java/entity/RacerTest.java | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index bc1ba608..c68bf13c 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -22,6 +22,10 @@ public void moveIfCan(BigInteger input) { } } + public boolean isWinner(BigInteger input) { + return input.compareTo(movedDistance) <= 0; + } + public String getName() { return name; } diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index a310c9dc..d8c97747 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -62,7 +62,7 @@ void racerMoveIfCan_WillMoveTest() { @Test @DisplayName("Racer moveIfCan 정지 테스트") void racerMoveIfCan_WillStopTest() { - // given: 전진하는 데이터와 전진하지 않는 데이터 + // given: 전진하지 않는 데이터 BigInteger input = Racer.MOVE_THRESHOLD; Racer racer = new Racer("Tester"); @@ -76,7 +76,7 @@ void racerMoveIfCan_WillStopTest() { @Test @DisplayName("Racer isWinner 테스트") void racerIsWinnerTest_WillTrueTest() { - // given: 전진하는 데이터와 전진하지 않는 데이터 + // given Racer racer = new Racer("Tester"); BigInteger input = racer.getMovedDistance(); boolean expectedResult = true; @@ -88,9 +88,9 @@ void racerIsWinnerTest_WillTrueTest() { @Test @DisplayName("Racer isWinner 테스트") void racerIsWinnerTest_WillFalseTest() { - // given: 전진하는 데이터와 전진하지 않는 데이터 + // given Racer racer = new Racer("Tester"); - BigInteger input = racer.getMovedDistance().add("1"); + BigInteger input = racer.getMovedDistance().add(new BigInteger("1")); boolean expectedResult = false; // when & then From 63e9c2c09f0ed567609a22e01b7c00332d4fd2c8 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 11:05:22 +0900 Subject: [PATCH 11/26] =?UTF-8?q?=20Racer=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20javadoc=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QUESTION.md | 27 +++++++++++++++++++++++++++ README.md | 2 +- src/main/java/entity/Racer.java | 3 +++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 QUESTION.md diff --git a/QUESTION.md b/QUESTION.md new file mode 100644 index 00000000..cae086de --- /dev/null +++ b/QUESTION.md @@ -0,0 +1,27 @@ +# 질문 모음 + ++ 입력값 검증에 대한 테스트 코드를 짤 때, 잘못된 입력값 여러 개를 테스트 하고 싶다면 주로 어떻게 하는 것이 좋은지? + +아래처럼 배열로 하는 것이 좋은건지, 아니면 다른 더 좋은 방법이 있는건지?! +```java +class RacerTest { + @Test + @DisplayName("Racer 생성자 실패 테스트") + void racerConstructorWithInvalidDataTest() { + // given: 생성자 데이터 + List invalidNameList = Arrays.asList(null, "", " "); + + for (String invalidName : invalidNameList) { + // when + ThrowableAssert.ThrowingCallable constructorCall = () -> new Racer(invalidName); + + // then + assertThatThrownBy(constructorCall) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Racer.VALIDATE_NAME_ERROR_MESSAGE); + } + } +} +``` + ++ \ No newline at end of file diff --git a/README.md b/README.md index 459db0f0..4fc2da04 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [x] 입력받은 숫자가 threshold 값을 넘어야 전진한다. - [x] threshold 값을 넘지 못하면 전진하지 않는다. - ~~[ ] 자동차 경주자는 이름과 함께 이동한 거리를 출력할 수 있다.~~ -- [ ] 자동차 경주자는 숫자로 된 입력을 받아, 우승했는지 여부를 알려준다. +- [x] 자동차 경주자는 숫자로 된 입력을 받아, 우승했는지 여부를 알려준다. ### Racer VO diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index c68bf13c..1c34b1dd 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -11,6 +11,9 @@ public class Racer { private BigInteger movedDistance; + /** + * @throws IllegalArgumentException name이 null이거나 blank 문자열 일 때 + */ public Racer(String name) { this.name = validateName(name); this.movedDistance = new BigInteger("0"); From be79e4f6b0c4dcfe5183eabb88896ec1d9777828 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 11:09:06 +0900 Subject: [PATCH 12/26] =?UTF-8?q?=20RacerDto=20of=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/dto/RacerDtoTest.java | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/test/java/dto/RacerDtoTest.java diff --git a/src/test/java/dto/RacerDtoTest.java b/src/test/java/dto/RacerDtoTest.java new file mode 100644 index 00000000..754780de --- /dev/null +++ b/src/test/java/dto/RacerDtoTest.java @@ -0,0 +1,24 @@ +package dto; + +import entity.Racer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class RacerDtoTest { + @Test + @DisplayName("RacerDto of 메소드 테스트") + void ofTest() { + // given + Racer givenRacer = new Racer("Tester"); + boolean givenIsWinner = givenRacer.isWinner(givenRacer.getMovedDistance()); + + // when + RacerDto racerDto = RacerDto.of(givenRacer); + + assertThat(racerDto.name()).isEqualTo(givenRacer.getName()); + assertThat(racerDto.movedDistance()).isEqualTo(givenRacer.getMovedDistance()); + assertThat(racerDto.isWinner()).isEqualTo(givenIsWinner); + } +} From 10c5092b00e3be518f3c81e84568ca032cc81fe6 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 11:11:51 +0900 Subject: [PATCH 13/26] =?UTF-8?q?=20RacerDto=20of=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/dto/RacerDto.java | 21 +++++++++++++++++++++ src/test/java/dto/RacerDtoTest.java | 9 ++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/main/java/dto/RacerDto.java diff --git a/src/main/java/dto/RacerDto.java b/src/main/java/dto/RacerDto.java new file mode 100644 index 00000000..cf872e2a --- /dev/null +++ b/src/main/java/dto/RacerDto.java @@ -0,0 +1,21 @@ +package dto; + +import entity.Racer; + +import java.math.BigInteger; + +public record RacerDto( + String name, + + BigInteger movedDistance, + + boolean isWinner +) { + public static RacerDto of(Racer racer, BigInteger input) { + return new RacerDto( + racer.getName(), + racer.getMovedDistance(), + racer.isWinner(input) + ); + } +} diff --git a/src/test/java/dto/RacerDtoTest.java b/src/test/java/dto/RacerDtoTest.java index 754780de..8befa2a7 100644 --- a/src/test/java/dto/RacerDtoTest.java +++ b/src/test/java/dto/RacerDtoTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.math.BigInteger; + import static org.assertj.core.api.Assertions.*; class RacerDtoTest { @@ -12,13 +14,14 @@ class RacerDtoTest { void ofTest() { // given Racer givenRacer = new Racer("Tester"); - boolean givenIsWinner = givenRacer.isWinner(givenRacer.getMovedDistance()); + BigInteger givenInput = givenRacer.getMovedDistance();; // when - RacerDto racerDto = RacerDto.of(givenRacer); + RacerDto racerDto = RacerDto.of(givenRacer, givenInput); + // then assertThat(racerDto.name()).isEqualTo(givenRacer.getName()); assertThat(racerDto.movedDistance()).isEqualTo(givenRacer.getMovedDistance()); - assertThat(racerDto.isWinner()).isEqualTo(givenIsWinner); + assertThat(racerDto.isWinner()).isEqualTo(givenRacer.isWinner(givenInput)); } } From 55d3a452178e00e187c188ade0727f389039d3e8 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:13:44 +0900 Subject: [PATCH 14/26] =?UTF-8?q?=20RacerController=20setUp=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++-- .../java/controller/RacerControllerTest.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/test/java/controller/RacerControllerTest.java diff --git a/README.md b/README.md index 4fc2da04..3abc2059 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ ### Racer VO #### Data -- [ ] 자동차 경주자는 이름이 있다. -- [ ] 자동차 경주자는 이동한 거리를 정수로 기록한다. -- [ ] 자동차 경주자는 우승 여부를 기록한다. +- [x] 자동차 경주자는 이름이 있다. +- [x] 자동차 경주자는 이동한 거리를 정수로 기록한다. +- [x] 자동차 경주자는 우승 여부를 기록한다. ### Controller diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java new file mode 100644 index 00000000..a8bf5b69 --- /dev/null +++ b/src/test/java/controller/RacerControllerTest.java @@ -0,0 +1,32 @@ +package controller; + +import org.assertj.core.api.ThrowableAssert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class RacerControllerTest { + @Test + @DisplayName("Controller setUp 메소드 테스트") + void setUpTest() { + // given + RacerController controller = new RacerController(); + + // when + ThrowableAssert.ThrowingCallable setUpName = () -> controller.setUpRacer(getValidNameInputString()); + ThrowableAssert.ThrowingCallable setUpNames = () -> controller.setUpRacer(getValidNamesInputString()); + + // then + assertThatCode(setUpName).doesNotThrowAnyException(); + assertThatCode(setUpNames).doesNotThrowAnyException(); + } + + private String getValidNameInputString() { + return "pobi "; + } + + private String getValidNamesInputString() { + return "pobi,woni , jun "; + } +} From 44f830af5fa6170cb27a3825675d2891b00cece7 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:21:22 +0900 Subject: [PATCH 15/26] =?UTF-8?q?=20RacerController=20setUpRacer=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/RacerController.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/controller/RacerController.java diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java new file mode 100644 index 00000000..9c472a56 --- /dev/null +++ b/src/main/java/controller/RacerController.java @@ -0,0 +1,31 @@ +package controller; + +import entity.Racer; + +import java.util.*; +import java.util.stream.Collectors; + +public class RacerController { + private List raceList; + + public RacerController() { + this.raceList = new ArrayList<>(); + } + + public void setUpRacer(String nameString) { + validateName(nameString); + + List newRacerSet = Arrays.stream(nameString.split(",")) + .map(Racer::new) + .toList(); + + raceList.clear(); + raceList.addAll(newRacerSet); + } + + private void validateName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException(Racer.VALIDATE_NAME_ERROR_MESSAGE); + } + } +} From 40c3f9598d5eb820d0a601947db803192e783024 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:26:21 +0900 Subject: [PATCH 16/26] =?UTF-8?q?=20RacerController=20setUpRacer=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=8B=A4=ED=8C=A8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/RacerController.java | 2 ++ .../java/controller/RacerControllerTest.java | 30 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java index 9c472a56..07273123 100644 --- a/src/main/java/controller/RacerController.java +++ b/src/main/java/controller/RacerController.java @@ -19,6 +19,8 @@ public void setUpRacer(String nameString) { .map(Racer::new) .toList(); + System.out.println(newRacerSet); + raceList.clear(); raceList.addAll(newRacerSet); } diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index a8bf5b69..771100fe 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -1,15 +1,19 @@ package controller; +import entity.Racer; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.Arrays; +import java.util.List; + import static org.assertj.core.api.Assertions.*; class RacerControllerTest { @Test - @DisplayName("Controller setUp 메소드 테스트") - void setUpTest() { + @DisplayName("Controller setUpRacer 메소드 성공 테스트") + void setUpRacerTest() { // given RacerController controller = new RacerController(); @@ -22,6 +26,24 @@ void setUpTest() { assertThatCode(setUpNames).doesNotThrowAnyException(); } + @Test + @DisplayName("Controller setUpRacer 메소드 실패 테스트") + void setUpRacer_WillThrownTest() { + // given + RacerController controller = new RacerController(); + List invalidNameInputStringList = Arrays.asList(null, " ", "", getInvalidNameInputString()); + + for (String invalidNameInputString : invalidNameInputStringList) { + // when + ThrowableAssert.ThrowingCallable setUpName = () -> controller.setUpRacer(invalidNameInputString); + + // then + assertThatThrownBy(setUpName) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(Racer.VALIDATE_NAME_ERROR_MESSAGE); + } + } + private String getValidNameInputString() { return "pobi "; } @@ -29,4 +51,8 @@ private String getValidNameInputString() { private String getValidNamesInputString() { return "pobi,woni , jun "; } + + private String getInvalidNameInputString() { + return "pobi, "; + } } From 86e8aee05a13a1688105e9bc5b427bc06e36b204 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:29:04 +0900 Subject: [PATCH 17/26] =?UTF-8?q?=20RaceController=20setUpGameCount?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/RacerController.java | 1 + .../java/controller/RacerControllerTest.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java index 07273123..8269e6b2 100644 --- a/src/main/java/controller/RacerController.java +++ b/src/main/java/controller/RacerController.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; public class RacerController { + private List raceList; public RacerController() { diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index 771100fe..9be00a37 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.math.BigInteger; import java.util.Arrays; import java.util.List; @@ -44,6 +45,36 @@ void setUpRacer_WillThrownTest() { } } + @Test + @DisplayName("Controller setUpGameCount 메소드 성공 테스트") + void setUpGameCountTest() { + // given + RacerController controller = new RacerController(); + BigInteger givenInput = new BigInteger("0"); + + // when + ThrowableAssert.ThrowingCallable setUpGameCount = () -> controller.setUpGameCount(givenInput); + + // then + assertThatCode(setUpGameCount).doesNotThrowAnyException(); + } + + @Test + @DisplayName("Controller setUpGameCount 메소드 성공 테스트") + void setUpGameCount_WillThrownTest() { + // given + RacerController controller = new RacerController(); + BigInteger givenInput = new BigInteger("-1"); + + // when + ThrowableAssert.ThrowingCallable setUpGameCount = () -> controller.setUpGameCount(givenInput); + + // then + assertThatThrownBy(setUpGameCount) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(RacerController.VALIDATE_GAME_COUNT_ERROR_MESSAGE); + } + private String getValidNameInputString() { return "pobi "; } From fd3ffdc8b8f4c284118d4c88511411b2a5462905 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:34:26 +0900 Subject: [PATCH 18/26] =?UTF-8?q?=20RacerController=20setUpGameCount?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/RacerController.java | 29 +++++++++++++++++-- src/main/java/entity/Racer.java | 4 +-- .../java/controller/RacerControllerTest.java | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java index 8269e6b2..21c0dc5c 100644 --- a/src/main/java/controller/RacerController.java +++ b/src/main/java/controller/RacerController.java @@ -2,17 +2,28 @@ import entity.Racer; +import java.math.BigInteger; import java.util.*; import java.util.stream.Collectors; public class RacerController { + public static final String VALIDATE_GAME_COUNT_ERROR_MESSAGE = "gameCount는 null이거나 음수일 수 없습니다."; private List raceList; + private BigInteger maxGameCount; + + private BigInteger currentGameCount; + public RacerController() { this.raceList = new ArrayList<>(); + this.maxGameCount = BigInteger.ZERO; + this.currentGameCount = BigInteger.ZERO; } + /** + * @throws IllegalArgumentException nameString이 null 또는 빈 문자열일 때, ","를 기준으로 split 했을 때 빈 문자열인 경우 + */ public void setUpRacer(String nameString) { validateName(nameString); @@ -20,15 +31,29 @@ public void setUpRacer(String nameString) { .map(Racer::new) .toList(); - System.out.println(newRacerSet); - raceList.clear(); raceList.addAll(newRacerSet); } + /** + * @throws IllegalArgumentException input이 null 또는 음수일 때 + */ + public void setUpGameCount(BigInteger input) { + validateGameCount(input); + + maxGameCount = input; + currentGameCount = BigInteger.ZERO; + } + private void validateName(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException(Racer.VALIDATE_NAME_ERROR_MESSAGE); } } + + private void validateGameCount(BigInteger integer) { + if (integer == null || integer.compareTo(BigInteger.ZERO) < 0) { + throw new IllegalArgumentException(VALIDATE_GAME_COUNT_ERROR_MESSAGE); + } + } } diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index 1c34b1dd..92d2b29f 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -3,9 +3,9 @@ import java.math.BigInteger; public class Racer { - public static BigInteger MOVE_THRESHOLD = new BigInteger("3"); + public static final BigInteger MOVE_THRESHOLD = new BigInteger("3"); - public static String VALIDATE_NAME_ERROR_MESSAGE = "name은 null이거나 빈 문자열일 수 없습니다."; + public static final String VALIDATE_NAME_ERROR_MESSAGE = "name은 null이거나 빈 문자열일 수 없습니다."; private String name; diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index 9be00a37..29fa5b94 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -60,7 +60,7 @@ void setUpGameCountTest() { } @Test - @DisplayName("Controller setUpGameCount 메소드 성공 테스트") + @DisplayName("Controller setUpGameCount 메소드 실패 테스트") void setUpGameCount_WillThrownTest() { // given RacerController controller = new RacerController(); From 02fe2f5100d92dd05001f94410a10ca6bfb81e01 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:35:51 +0900 Subject: [PATCH 19/26] Update README.md --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3abc2059..33a8f34f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ #### Data - [x] 자동차 경주자는 이름이 있다. + - [ ] NAME_MAX_LENGTH 이하의 길이여야 한다. - [x] 자동차 경주자는 이동한 거리를 정수로 기록한다. #### Act @@ -28,16 +29,16 @@ ### Controller #### Data -- [ ] 자동차 경주자 목록을 가지고 있는다. -- [ ] 자동차 경주 진행 시도 횟수를 가지고 있는다. -- [ ] 현재 자동차 경주 진행 횟수를 가지고 있는다. +- [x] 자동차 경주자 목록을 가지고 있는다. +- [x] 자동차 경주 진행 시도 횟수를 가지고 있는다. +- [x] 현재 자동차 경주 진행 횟수를 가지고 있는다. #### Act -- [ ] 자동차 이름을 1개의 문자열로 입력받아 자동차 목록을 생성한다. +- [x] 자동차 이름을 1개의 문자열로 입력받아 자동차 목록을 생성한다. - [ ] 자동차 이름은 쉼표를 기준으로 구분하며, 이름이 NAME_MAX_LENGTH보다 길 경우 IllegalArgumentException을 발생시킨다. -- [ ] 경주 게임을 진행할 횟수를 입력 받는다. - - [ ] 횟수에는 최댓값 제한은 없으며, 음수 또는 숫자가 아닐 시 IllegalArgumentException을 발생시킨다. - - [ ] 입력 받은 숫자로 경주 진행 시도 횟수를 초기화하고, 현재 자동차 경주 진행 횟수는 0으로 리셋한다. +- [x] 경주 게임을 진행할 횟수를 입력 받는다. + - [x] 횟수에는 최댓값 제한은 없으며, 음수 또는 숫자가 아닐 시 IllegalArgumentException을 발생시킨다. + - [x] 입력 받은 숫자로 경주 진행 시도 횟수를 초기화하고, 현재 자동차 경주 진행 횟수는 0으로 리셋한다. - [ ] 경주 게임을 1번 진행한다. - [ ] 만약 자동차 목록 또는 경주 게임 진행 횟수가 세팅이 안 되었을 경우 IllegalStateException을 발생시킨다. - [ ] 이미 게임이 종료 되었을 경우 IllegalStateException을 발생시킨다. From 24ec9d24c61ba9787944d0628f7d4d557a2c1ad3 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 12:38:54 +0900 Subject: [PATCH 20/26] =?UTF-8?q?=20Racer=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=EC=97=90=205=EC=9E=90=20=EC=9D=B4?= =?UTF-8?q?=ED=95=98=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/entity/Racer.java | 6 ++++-- src/test/java/controller/RacerControllerTest.java | 2 +- src/test/java/dto/RacerDtoTest.java | 6 +++++- src/test/java/entity/RacerTest.java | 14 +++++++++----- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index 92d2b29f..f3b1014b 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -3,9 +3,11 @@ import java.math.BigInteger; public class Racer { + public static final int NAME_MAX_LENGTH = 5; + public static final BigInteger MOVE_THRESHOLD = new BigInteger("3"); - public static final String VALIDATE_NAME_ERROR_MESSAGE = "name은 null이거나 빈 문자열일 수 없습니다."; + public static final String VALIDATE_NAME_ERROR_MESSAGE = "name은 null이거나 빈 문자열일 수 없습니다. 그리고 " + NAME_MAX_LENGTH + "자 이하 여야합니다."; private String name; @@ -38,7 +40,7 @@ public BigInteger getMovedDistance() { } private String validateName(String name) { - if (name == null || name.isBlank()) { + if (name == null || name.isBlank() || name.trim().length() > NAME_MAX_LENGTH) { throw new IllegalArgumentException(VALIDATE_NAME_ERROR_MESSAGE); } diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index 29fa5b94..66b07990 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -84,6 +84,6 @@ private String getValidNamesInputString() { } private String getInvalidNameInputString() { - return "pobi, "; + return "pobi, Tester, "; } } diff --git a/src/test/java/dto/RacerDtoTest.java b/src/test/java/dto/RacerDtoTest.java index 8befa2a7..f75541d5 100644 --- a/src/test/java/dto/RacerDtoTest.java +++ b/src/test/java/dto/RacerDtoTest.java @@ -13,7 +13,7 @@ class RacerDtoTest { @DisplayName("RacerDto of 메소드 테스트") void ofTest() { // given - Racer givenRacer = new Racer("Tester"); + Racer givenRacer = new Racer(getValidName()); BigInteger givenInput = givenRacer.getMovedDistance();; // when @@ -24,4 +24,8 @@ void ofTest() { assertThat(racerDto.movedDistance()).isEqualTo(givenRacer.getMovedDistance()); assertThat(racerDto.isWinner()).isEqualTo(givenRacer.isWinner(givenInput)); } + + private String getValidName() { + return "Test"; + } } diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index d8c97747..56a627ea 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -32,7 +32,7 @@ void racerConstructorTest() { @DisplayName("Racer 생성자 실패 테스트") void racerConstructorWithInvalidDataTest() { // given: 생성자 데이터 - List invalidNameList = Arrays.asList(null, "", " "); + List invalidNameList = Arrays.asList(null, "", " ", "5자이상문자"); for (String invalidName : invalidNameList) { // when @@ -50,7 +50,7 @@ void racerConstructorWithInvalidDataTest() { void racerMoveIfCan_WillMoveTest() { // given: 전진하는 데이터 BigInteger input = Racer.MOVE_THRESHOLD.add(new BigInteger("1")); - Racer racer = new Racer("Tester"); + Racer racer = new Racer(getValidName()); // when racer.moveIfCan(input); @@ -64,7 +64,7 @@ void racerMoveIfCan_WillMoveTest() { void racerMoveIfCan_WillStopTest() { // given: 전진하지 않는 데이터 BigInteger input = Racer.MOVE_THRESHOLD; - Racer racer = new Racer("Tester"); + Racer racer = new Racer(getValidName()); // when racer.moveIfCan(input); @@ -77,7 +77,7 @@ void racerMoveIfCan_WillStopTest() { @DisplayName("Racer isWinner 테스트") void racerIsWinnerTest_WillTrueTest() { // given - Racer racer = new Racer("Tester"); + Racer racer = new Racer(getValidName()); BigInteger input = racer.getMovedDistance(); boolean expectedResult = true; @@ -89,11 +89,15 @@ void racerIsWinnerTest_WillTrueTest() { @DisplayName("Racer isWinner 테스트") void racerIsWinnerTest_WillFalseTest() { // given - Racer racer = new Racer("Tester"); + Racer racer = new Racer(getValidName()); BigInteger input = racer.getMovedDistance().add(new BigInteger("1")); boolean expectedResult = false; // when & then assertThat(racer.isWinner(input)).isEqualTo(expectedResult); } + + private String getValidName() { + return "Test"; + } } From 2dfe34b9a723e89c4fd0e5789cf8a5b2e3b6d989 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 15:50:35 +0900 Subject: [PATCH 21/26] =?UTF-8?q?=20RaceController=20playGame=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +-- build.gradle | 1 + src/main/java/controller/RacerController.java | 4 +-- .../java/controller/RacerControllerTest.java | 29 +++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 33a8f34f..038db267 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ #### Data - [x] 자동차 경주자는 이름이 있다. - - [ ] NAME_MAX_LENGTH 이하의 길이여야 한다. + - [x] NAME_MAX_LENGTH 이하의 길이여야 한다. - [x] 자동차 경주자는 이동한 거리를 정수로 기록한다. #### Act @@ -35,7 +35,7 @@ #### Act - [x] 자동차 이름을 1개의 문자열로 입력받아 자동차 목록을 생성한다. - - [ ] 자동차 이름은 쉼표를 기준으로 구분하며, 이름이 NAME_MAX_LENGTH보다 길 경우 IllegalArgumentException을 발생시킨다. + - [x] 자동차 이름은 쉼표를 기준으로 구분하며, 이름이 NAME_MAX_LENGTH보다 길 경우 IllegalArgumentException을 발생시킨다. - [x] 경주 게임을 진행할 횟수를 입력 받는다. - [x] 횟수에는 최댓값 제한은 없으며, 음수 또는 숫자가 아닐 시 IllegalArgumentException을 발생시킨다. - [x] 입력 받은 숫자로 경주 진행 시도 횟수를 초기화하고, 현재 자동차 경주 진행 횟수는 0으로 리셋한다. diff --git a/build.gradle b/build.gradle index 20a92c9e..55680f59 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ repositories { dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.assertj:assertj-core:3.25.3' + testImplementation 'org.mockito:mockito-core:5.12.0' } test { diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java index 21c0dc5c..6a5425b4 100644 --- a/src/main/java/controller/RacerController.java +++ b/src/main/java/controller/RacerController.java @@ -17,8 +17,8 @@ public class RacerController { public RacerController() { this.raceList = new ArrayList<>(); - this.maxGameCount = BigInteger.ZERO; - this.currentGameCount = BigInteger.ZERO; + this.maxGameCount = new BigInteger("-1"); + this.currentGameCount = new BigInteger("-1"); } /** diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index 66b07990..8982c8c3 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -1,5 +1,6 @@ package controller; +import dto.RacerDto; import entity.Racer; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.DisplayName; @@ -10,6 +11,9 @@ import java.util.List; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.when; class RacerControllerTest { @Test @@ -75,6 +79,31 @@ void setUpGameCount_WillThrownTest() { .hasMessage(RacerController.VALIDATE_GAME_COUNT_ERROR_MESSAGE); } + @Test + @DisplayName("Controller playGame 메소드 성공 테스트") + void playGameTest() { + // given + RacerController controller = new RacerController(); + List racerDtoList = Arrays.asList(new RacerDto( + getValidNameInputString(), + BigInteger.ZERO, + false + ) + ); + + controller.setUpRacer(getValidNameInputString()); + controller.setUpGameCount(new BigInteger("2")); + + when(RandomNumberGenerator.getRandomNumber(any(int))).thenReturn(3); + + // when + RacerResult result = controller.playGame(); + + // then + assertThat(result.isEnded()).isEqualTo(false); + assertThat(result.racers()).isEqualTo(racerDtoList); + } + private String getValidNameInputString() { return "pobi "; } From 6ffccbb11b3757282f3261931a1a9ce5f3e338de Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 16:38:29 +0900 Subject: [PATCH 22/26] =?UTF-8?q?=20RacerController=20playGame=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/RacerController.java | 76 +++++++++++++++++-- src/main/java/dto/RacerResult.java | 8 ++ src/main/java/entity/Racer.java | 6 +- .../java/utils/RandomNumberGenerator.java | 28 +++++++ .../java/controller/RacerControllerTest.java | 35 ++++++--- src/test/java/entity/RacerTest.java | 4 +- 6 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 src/main/java/dto/RacerResult.java create mode 100644 src/main/java/utils/RandomNumberGenerator.java diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java index 6a5425b4..9281c6b2 100644 --- a/src/main/java/controller/RacerController.java +++ b/src/main/java/controller/RacerController.java @@ -1,22 +1,28 @@ package controller; +import dto.RacerDto; +import dto.RacerResult; import entity.Racer; +import utils.RandomNumberGenerator; import java.math.BigInteger; import java.util.*; -import java.util.stream.Collectors; public class RacerController { public static final String VALIDATE_GAME_COUNT_ERROR_MESSAGE = "gameCount는 null이거나 음수일 수 없습니다."; - private List raceList; + public static final String VALIDATE_RACER_LIST_ERROR_MESSAGE = "racerList는 null이거나 빈 배열일 수 없습니다."; + + public static final String VALIDATE_GAME_ENDED_ERROR_MESSAGE = "이미 종료된 게임입니다."; + + private List racerList; private BigInteger maxGameCount; private BigInteger currentGameCount; public RacerController() { - this.raceList = new ArrayList<>(); + this.racerList = new ArrayList<>(); this.maxGameCount = new BigInteger("-1"); this.currentGameCount = new BigInteger("-1"); } @@ -31,8 +37,8 @@ public void setUpRacer(String nameString) { .map(Racer::new) .toList(); - raceList.clear(); - raceList.addAll(newRacerSet); + racerList.clear(); + racerList.addAll(newRacerSet); } /** @@ -45,15 +51,75 @@ public void setUpGameCount(BigInteger input) { currentGameCount = BigInteger.ZERO; } + public RacerResult playGame() { + validatePlayGame(); + + currentGameCount = currentGameCount.add(BigInteger.ONE); + + int randomInteger = RandomNumberGenerator.getInstance().getRandomNumber(0, 9); + + for (Racer racer : racerList) { + racer.moveIfCan(randomInteger); + } + + return new RacerResult( + isEnded(), + racerList.stream().map(this::getRacerDto).toList() + ); + } + + private RacerDto getRacerDto(Racer racer) { + if (isEnded()) { + return RacerDto.of(racer, getMax()); + } + + return new RacerDto( + racer.getName(), + racer.getMovedDistance(), + false + ); + } + + private BigInteger getMax() { + if (isEnded()) { + return racerList.stream() + .map(Racer::getMovedDistance) + .max(BigInteger::compareTo) + .orElseThrow(() -> new IllegalStateException(VALIDATE_RACER_LIST_ERROR_MESSAGE)); + } + + return null; + } + private void validateName(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException(Racer.VALIDATE_NAME_ERROR_MESSAGE); } } + private void validateRacerList() { + if (racerList == null || racerList.isEmpty()) { + throw new IllegalStateException(VALIDATE_RACER_LIST_ERROR_MESSAGE); + } + } + private void validateGameCount(BigInteger integer) { if (integer == null || integer.compareTo(BigInteger.ZERO) < 0) { throw new IllegalArgumentException(VALIDATE_GAME_COUNT_ERROR_MESSAGE); } } + + private void validatePlayGame() { + validateRacerList(); + validateGameCount(maxGameCount); + validateGameCount(currentGameCount); + + if (isEnded()) { + throw new IllegalStateException(VALIDATE_GAME_ENDED_ERROR_MESSAGE); + } + } + + private boolean isEnded() { + return maxGameCount.equals(currentGameCount); + } } diff --git a/src/main/java/dto/RacerResult.java b/src/main/java/dto/RacerResult.java new file mode 100644 index 00000000..69b1c1f9 --- /dev/null +++ b/src/main/java/dto/RacerResult.java @@ -0,0 +1,8 @@ +package dto; + +public record RacerResult ( + boolean isEnded, + + Iterable racerDtos +) { +} diff --git a/src/main/java/entity/Racer.java b/src/main/java/entity/Racer.java index f3b1014b..4d21abec 100644 --- a/src/main/java/entity/Racer.java +++ b/src/main/java/entity/Racer.java @@ -5,7 +5,7 @@ public class Racer { public static final int NAME_MAX_LENGTH = 5; - public static final BigInteger MOVE_THRESHOLD = new BigInteger("3"); + public static final int MOVE_THRESHOLD = 3; public static final String VALIDATE_NAME_ERROR_MESSAGE = "name은 null이거나 빈 문자열일 수 없습니다. 그리고 " + NAME_MAX_LENGTH + "자 이하 여야합니다."; @@ -21,8 +21,8 @@ public Racer(String name) { this.movedDistance = new BigInteger("0"); } - public void moveIfCan(BigInteger input) { - if(input.compareTo(MOVE_THRESHOLD) > 0) { + public void moveIfCan(int input) { + if(input > MOVE_THRESHOLD) { movedDistance = movedDistance.add(new BigInteger("1")); } } diff --git a/src/main/java/utils/RandomNumberGenerator.java b/src/main/java/utils/RandomNumberGenerator.java new file mode 100644 index 00000000..e4cf23b9 --- /dev/null +++ b/src/main/java/utils/RandomNumberGenerator.java @@ -0,0 +1,28 @@ +package utils; + +import java.util.Random; + +public class RandomNumberGenerator { + private RandomNumberGenerator() { + + } + + public static RandomNumberGenerator getInstance() { + return LazyHolder.INSTANCE; + } + + public int getRandomNumber(int start, int end) { + if (start > end) { + int temp = start; + start = end; + end = temp; + } + + return LazyHolder.random.nextInt((end - start) + 1) + start; + } + + private static class LazyHolder { + private static final Random random = new Random(); + private static final RandomNumberGenerator INSTANCE = new RandomNumberGenerator(); + } +} diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index 8982c8c3..cfd82203 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -1,21 +1,28 @@ package controller; import dto.RacerDto; +import dto.RacerResult; import entity.Racer; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import utils.RandomNumberGenerator; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; class RacerControllerTest { + private RandomNumberGenerator mockRNG = Mockito.mock(RandomNumberGenerator.class); + @Test @DisplayName("Controller setUpRacer 메소드 성공 테스트") void setUpRacerTest() { @@ -83,8 +90,9 @@ void setUpGameCount_WillThrownTest() { @DisplayName("Controller playGame 메소드 성공 테스트") void playGameTest() { // given + RandomNumberGenerator generator = mock(RandomNumberGenerator.class); RacerController controller = new RacerController(); - List racerDtoList = Arrays.asList(new RacerDto( + List racerDtoList = List.of(new RacerDto( getValidNameInputString(), BigInteger.ZERO, false @@ -94,18 +102,23 @@ void playGameTest() { controller.setUpRacer(getValidNameInputString()); controller.setUpGameCount(new BigInteger("2")); - when(RandomNumberGenerator.getRandomNumber(any(int))).thenReturn(3); + try (MockedStatic generatorMockedStatic = mockStatic(RandomNumberGenerator.class)) { + given(RandomNumberGenerator.getInstance()).willReturn(generator); - // when - RacerResult result = controller.playGame(); + when(generator.getRandomNumber(anyInt(), anyInt())) + .thenReturn(Racer.MOVE_THRESHOLD); - // then - assertThat(result.isEnded()).isEqualTo(false); - assertThat(result.racers()).isEqualTo(racerDtoList); + // when + RacerResult result = controller.playGame(); + + // then + assertThat(result.isEnded()).isEqualTo(false); + assertThat(result.racerDtos()).isEqualTo(racerDtoList); + } } private String getValidNameInputString() { - return "pobi "; + return "pobi"; } private String getValidNamesInputString() { diff --git a/src/test/java/entity/RacerTest.java b/src/test/java/entity/RacerTest.java index 56a627ea..ca23d38f 100644 --- a/src/test/java/entity/RacerTest.java +++ b/src/test/java/entity/RacerTest.java @@ -49,7 +49,7 @@ void racerConstructorWithInvalidDataTest() { @DisplayName("Racer moveIfCan 전진 테스트") void racerMoveIfCan_WillMoveTest() { // given: 전진하는 데이터 - BigInteger input = Racer.MOVE_THRESHOLD.add(new BigInteger("1")); + int input = Racer.MOVE_THRESHOLD + 1; Racer racer = new Racer(getValidName()); // when @@ -63,7 +63,7 @@ void racerMoveIfCan_WillMoveTest() { @DisplayName("Racer moveIfCan 정지 테스트") void racerMoveIfCan_WillStopTest() { // given: 전진하지 않는 데이터 - BigInteger input = Racer.MOVE_THRESHOLD; + int input = Racer.MOVE_THRESHOLD; Racer racer = new Racer(getValidName()); // when From 4b241682e2f43cb69cae65254bf94eb11d6bd6a3 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 17:50:17 +0900 Subject: [PATCH 23/26] RacerView --- README.md | 8 +- src/main/java/RacerApplication.java | 12 ++ src/main/java/controller/RacerController.java | 30 ++-- src/main/java/dto/RacerResult.java | 4 +- src/main/java/view/RacerView.java | 130 +++++++++++++++++ .../java/controller/RacerControllerTest.java | 138 +++++++++++------- 6 files changed, 248 insertions(+), 74 deletions(-) create mode 100644 src/main/java/RacerApplication.java create mode 100644 src/main/java/view/RacerView.java diff --git a/README.md b/README.md index 038db267..cc35cdf5 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ - [x] 경주 게임을 진행할 횟수를 입력 받는다. - [x] 횟수에는 최댓값 제한은 없으며, 음수 또는 숫자가 아닐 시 IllegalArgumentException을 발생시킨다. - [x] 입력 받은 숫자로 경주 진행 시도 횟수를 초기화하고, 현재 자동차 경주 진행 횟수는 0으로 리셋한다. -- [ ] 경주 게임을 1번 진행한다. - - [ ] 만약 자동차 목록 또는 경주 게임 진행 횟수가 세팅이 안 되었을 경우 IllegalStateException을 발생시킨다. - - [ ] 이미 게임이 종료 되었을 경우 IllegalStateException을 발생시킨다. - - [ ] 게임이 끝나면 Racer VO 목록과 게임 종료 여부를 반환한다. +- [x] 경주 게임을 1번 진행한다. + - [x] 만약 자동차 목록 또는 경주 게임 진행 횟수가 세팅이 안 되었을 경우 IllegalStateException을 발생시킨다. + - [x] 이미 게임이 종료 되었을 경우 IllegalStateException을 발생시킨다. + - [x] 게임이 끝나면 Racer VO 목록과 게임 종료 여부를 반환한다. ### View diff --git a/src/main/java/RacerApplication.java b/src/main/java/RacerApplication.java new file mode 100644 index 00000000..fe0452ff --- /dev/null +++ b/src/main/java/RacerApplication.java @@ -0,0 +1,12 @@ +import controller.RacerController; +import view.RacerView; + +public class RacerApplication { + public static void main(String[] args) { + RacerView racerView = new RacerView(new RacerController()); + + while (true) { + racerView.render(); + } + } +} diff --git a/src/main/java/controller/RacerController.java b/src/main/java/controller/RacerController.java index 9281c6b2..efa6e9ff 100644 --- a/src/main/java/controller/RacerController.java +++ b/src/main/java/controller/RacerController.java @@ -30,25 +30,18 @@ public RacerController() { /** * @throws IllegalArgumentException nameString이 null 또는 빈 문자열일 때, ","를 기준으로 split 했을 때 빈 문자열인 경우 */ - public void setUpRacer(String nameString) { - validateName(nameString); + public void setUp(List nameList, BigInteger input) { + validateGameCount(input); - List newRacerSet = Arrays.stream(nameString.split(",")) + List newRacerList = nameList.stream() .map(Racer::new) .toList(); - racerList.clear(); - racerList.addAll(newRacerSet); - } - - /** - * @throws IllegalArgumentException input이 null 또는 음수일 때 - */ - public void setUpGameCount(BigInteger input) { - validateGameCount(input); - maxGameCount = input; currentGameCount = BigInteger.ZERO; + + racerList.clear(); + racerList.addAll(newRacerList); } public RacerResult playGame() { @@ -56,9 +49,8 @@ public RacerResult playGame() { currentGameCount = currentGameCount.add(BigInteger.ONE); - int randomInteger = RandomNumberGenerator.getInstance().getRandomNumber(0, 9); - for (Racer racer : racerList) { + int randomInteger = RandomNumberGenerator.getInstance().getRandomNumber(0, 9); racer.moveIfCan(randomInteger); } @@ -68,6 +60,10 @@ public RacerResult playGame() { ); } + public boolean isEnded() { + return maxGameCount.equals(currentGameCount); + } + private RacerDto getRacerDto(Racer racer) { if (isEnded()) { return RacerDto.of(racer, getMax()); @@ -118,8 +114,4 @@ private void validatePlayGame() { throw new IllegalStateException(VALIDATE_GAME_ENDED_ERROR_MESSAGE); } } - - private boolean isEnded() { - return maxGameCount.equals(currentGameCount); - } } diff --git a/src/main/java/dto/RacerResult.java b/src/main/java/dto/RacerResult.java index 69b1c1f9..c08a9be3 100644 --- a/src/main/java/dto/RacerResult.java +++ b/src/main/java/dto/RacerResult.java @@ -1,8 +1,10 @@ package dto; +import java.util.Collection; + public record RacerResult ( boolean isEnded, - Iterable racerDtos + Collection racerDtos ) { } diff --git a/src/main/java/view/RacerView.java b/src/main/java/view/RacerView.java new file mode 100644 index 00000000..fbaa9060 --- /dev/null +++ b/src/main/java/view/RacerView.java @@ -0,0 +1,130 @@ +package view; + +import controller.RacerController; +import dto.RacerDto; +import dto.RacerResult; +import entity.Racer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class RacerView { + private final RacerController racerController; + + private final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + + private List nameList = null; + + private BigInteger tryCount = null; + + private RacerResult result = null; + + public RacerView(RacerController racerController) { + this.racerController = racerController; + } + + public void render() { + + if (nameList == null) { + try { + String input = getInput(); + + nameList = Arrays.stream(input.split(",")) + .map(this::validateName) + .toList(); + } catch (IllegalArgumentException e) { + System.out.println(getFormattedErrorMessage(e.getMessage())); + return; + } + } + + if (tryCount == null) { + try { + tryCount = getTryCount(); + } catch (IllegalArgumentException e) { + System.out.println(getFormattedErrorMessage(e.getMessage())); + return; + } + } + + racerController.setUp(nameList, tryCount); + + System.out.println("출력 결과"); + + while (!racerController.isEnded()) { + result = racerController.playGame(); + + for (RacerDto racerDto : result.racerDtos()) { + String distanceString = getDistanceString(racerDto); + + System.out.println(racerDto.name() + " : " + distanceString); + } + + System.out.print("\n"); + } + + if (result != null) { + String winners = result.racerDtos().stream() + .filter(RacerDto::isWinner) + .map(RacerDto::name) + .collect(Collectors.joining(", ")); + System.out.println("최종 우승자 : " + winners); + } + + nameList = null; + tryCount = null; + result = null; + } + + private String getDistanceString(RacerDto racerDto) { + BigInteger movedDistance = racerDto.movedDistance(); + StringBuilder distanceStrBuilder = new StringBuilder(); + + // BigInteger를 사용하여 반복문 구현 + for (BigInteger i = BigInteger.ZERO; i.compareTo(movedDistance) < 0; i = i.add(BigInteger.ONE)) { + distanceStrBuilder.append('-'); + } + + return distanceStrBuilder.toString(); + } + + private String getInput() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + try { + return bufferedReader.readLine(); + } catch (Exception e) { + System.out.println(getFormattedErrorMessage("입력을 처리할 수 없습니다.")); + return null; + } + } + + private BigInteger getTryCount() { + System.out.println("시도할 회수는 몇회인가요?"); + + try { + return new BigInteger(bufferedReader.readLine().trim()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자만 입력 가능합니다."); + } catch (Exception e) { + System.out.println(getFormattedErrorMessage("입력을 처리할 수 없습니다.")); + return null; + } + } + + private String getFormattedErrorMessage(String errorMessage) { + return "[ERROR] " + errorMessage; + } + + private String validateName(String name) { + if (name == null || name.isBlank() || name.trim().length() > Racer.NAME_MAX_LENGTH) { + throw new IllegalArgumentException(Racer.VALIDATE_NAME_ERROR_MESSAGE); + } + + return name.trim(); + } +} diff --git a/src/test/java/controller/RacerControllerTest.java b/src/test/java/controller/RacerControllerTest.java index cfd82203..a128faf8 100644 --- a/src/test/java/controller/RacerControllerTest.java +++ b/src/test/java/controller/RacerControllerTest.java @@ -24,64 +24,31 @@ class RacerControllerTest { private RandomNumberGenerator mockRNG = Mockito.mock(RandomNumberGenerator.class); @Test - @DisplayName("Controller setUpRacer 메소드 성공 테스트") - void setUpRacerTest() { - // given - RacerController controller = new RacerController(); - - // when - ThrowableAssert.ThrowingCallable setUpName = () -> controller.setUpRacer(getValidNameInputString()); - ThrowableAssert.ThrowingCallable setUpNames = () -> controller.setUpRacer(getValidNamesInputString()); - - // then - assertThatCode(setUpName).doesNotThrowAnyException(); - assertThatCode(setUpNames).doesNotThrowAnyException(); - } - - @Test - @DisplayName("Controller setUpRacer 메소드 실패 테스트") - void setUpRacer_WillThrownTest() { - // given - RacerController controller = new RacerController(); - List invalidNameInputStringList = Arrays.asList(null, " ", "", getInvalidNameInputString()); - - for (String invalidNameInputString : invalidNameInputStringList) { - // when - ThrowableAssert.ThrowingCallable setUpName = () -> controller.setUpRacer(invalidNameInputString); - - // then - assertThatThrownBy(setUpName) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(Racer.VALIDATE_NAME_ERROR_MESSAGE); - } - } - - @Test - @DisplayName("Controller setUpGameCount 메소드 성공 테스트") - void setUpGameCountTest() { + @DisplayName("Controller setUp 메소드 성공 테스트") + void setUpTest() { // given RacerController controller = new RacerController(); BigInteger givenInput = new BigInteger("0"); // when - ThrowableAssert.ThrowingCallable setUpGameCount = () -> controller.setUpGameCount(givenInput); + ThrowableAssert.ThrowingCallable setUpName = () -> controller.setUp(getValidNameInputString(), givenInput); // then - assertThatCode(setUpGameCount).doesNotThrowAnyException(); + assertThatCode(setUpName).doesNotThrowAnyException(); } @Test - @DisplayName("Controller setUpGameCount 메소드 실패 테스트") - void setUpGameCount_WillThrownTest() { + @DisplayName("Controller setUp 메소드 숫자로 인한 실패 테스트") + void setUp_WillThrownByIntegerTest() { // given RacerController controller = new RacerController(); BigInteger givenInput = new BigInteger("-1"); // when - ThrowableAssert.ThrowingCallable setUpGameCount = () -> controller.setUpGameCount(givenInput); + ThrowableAssert.ThrowingCallable setUp = () -> controller.setUp(getValidNameInputString(), givenInput); // then - assertThatThrownBy(setUpGameCount) + assertThatThrownBy(setUp) .isInstanceOf(IllegalArgumentException.class) .hasMessage(RacerController.VALIDATE_GAME_COUNT_ERROR_MESSAGE); } @@ -93,14 +60,13 @@ void playGameTest() { RandomNumberGenerator generator = mock(RandomNumberGenerator.class); RacerController controller = new RacerController(); List racerDtoList = List.of(new RacerDto( - getValidNameInputString(), + getValidNameInputString().get(0), BigInteger.ZERO, false ) ); - controller.setUpRacer(getValidNameInputString()); - controller.setUpGameCount(new BigInteger("2")); + controller.setUp(getValidNameInputString(), new BigInteger("2")); try (MockedStatic generatorMockedStatic = mockStatic(RandomNumberGenerator.class)) { given(RandomNumberGenerator.getInstance()).willReturn(generator); @@ -117,15 +83,87 @@ void playGameTest() { } } - private String getValidNameInputString() { - return "pobi"; + @Test + @DisplayName("Controller playGame 메소드에서 게임 종료 테스트") + void playGameTest_WillEndedTest() { + // given + RandomNumberGenerator generator = mock(RandomNumberGenerator.class); + RacerController controller = new RacerController(); + List racerDtoList = List.of(new RacerDto( + getValidNameInputString().get(0), + BigInteger.ONE, + true + ) + ); + + controller.setUp(getValidNameInputString(), new BigInteger("1")); + + try (MockedStatic generatorMockedStatic = mockStatic(RandomNumberGenerator.class)) { + given(RandomNumberGenerator.getInstance()).willReturn(generator); + + when(generator.getRandomNumber(anyInt(), anyInt())) + .thenReturn(Racer.MOVE_THRESHOLD + 1); + + // when + RacerResult result = controller.playGame(); + + // then + assertThat(result.isEnded()).isEqualTo(true); + assertThat(result.racerDtos()).isEqualTo(racerDtoList); + } + } + + @Test + @DisplayName("Controller setUp 없이 playGame 메소드 실행하면 실패하는 테스트") + void playGameTest_ThrowsExceptionWhenNotSetUp_Test() { + // given + RandomNumberGenerator generator = mock(RandomNumberGenerator.class); + RacerController controller = new RacerController(); + + try (MockedStatic generatorMockedStatic = mockStatic(RandomNumberGenerator.class)) { + given(RandomNumberGenerator.getInstance()).willReturn(generator); + + when(generator.getRandomNumber(anyInt(), anyInt())) + .thenReturn(Racer.MOVE_THRESHOLD + 1); + + // when + ThrowableAssert.ThrowingCallable throwingCallable = controller::playGame; + + // then + assertThatThrownBy(throwingCallable) + .isInstanceOf(IllegalStateException.class) + .hasMessage(RacerController.VALIDATE_RACER_LIST_ERROR_MESSAGE); + } } - private String getValidNamesInputString() { - return "pobi,woni , jun "; + @Test + @DisplayName("Controller playGame 메소드에서 게임 종료 이후 게임을 진행하면 실패하는 테스트") + void playGameTest_ThrowsExceptionWhenEnded_Test() { + // given + RandomNumberGenerator generator = mock(RandomNumberGenerator.class); + RacerController controller = new RacerController(); + + controller.setUp(getValidNameInputString(), new BigInteger("1")); + + try (MockedStatic generatorMockedStatic = mockStatic(RandomNumberGenerator.class)) { + given(RandomNumberGenerator.getInstance()).willReturn(generator); + + when(generator.getRandomNumber(anyInt(), anyInt())) + .thenReturn(Racer.MOVE_THRESHOLD + 1); + + controller.playGame(); + + // when + ThrowableAssert.ThrowingCallable throwingCallable = controller::playGame; + + // then + assertThatThrownBy(throwingCallable) + .isInstanceOf(IllegalStateException.class) + .hasMessage(RacerController.VALIDATE_GAME_ENDED_ERROR_MESSAGE); + } } - private String getInvalidNameInputString() { - return "pobi, Tester, "; + private List getValidNameInputString() { + return List.of("pobi"); } } From f02a0244517a3a4e015be4339005d26ee7e7c5ee Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 18:04:48 +0900 Subject: [PATCH 24/26] Update README.md --- README.md | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index cc35cdf5..c8780c0c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [x] 전진할 때 숫자로 된 입력을 받는다. - [x] 입력받은 숫자가 threshold 값을 넘어야 전진한다. - [x] threshold 값을 넘지 못하면 전진하지 않는다. -- ~~[ ] 자동차 경주자는 이름과 함께 이동한 거리를 출력할 수 있다.~~ +- [ ] ~~자동차 경주자는 이름과 함께 이동한 거리를 출력할 수 있다.~~ - [x] 자동차 경주자는 숫자로 된 입력을 받아, 우승했는지 여부를 알려준다. ### Racer VO @@ -47,24 +47,37 @@ ### View #### Data -- [ ] 자동차 경주 컨트롤러를 가지고 있는다. +- [x] 자동차 경주 컨트롤러를 가지고 있는다. #### Act -- [ ] 자동차 경주 게임을 시작한다. - - [ ] 경주할 자동차 이름을 입력받는다. - - [ ] 시도할 횟수를 입력 받는다. - - [ ] 자동차 경주 게임을 진행하며 매번 결과를 출력한다. - - [ ] 최종 우승자를 출력한다. - - [ ] 입력을 받을 때 에러가 발생하면 "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. +- [x] 자동차 경주 게임을 시작한다. + - [x] 경주할 자동차 이름을 입력받는다. + - [x] 시도할 횟수를 입력 받는다. + - [x] 자동차 경주 게임을 진행하며 매번 결과를 출력한다. + - [x] 최종 우승자를 출력한다. + - [x] 입력을 받을 때 에러가 발생하면 "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. ### Util ### RandomNumberGenerator #### Act -- [ ] 입력 받은 숫자 범위에서 무작위 정수를 생성한다. +- [x] 입력 받은 숫자 범위에서 무작위 정수를 생성한다. -### ErrorMessageGenerator +### ~~ErrorMessageGenerator~~ -#### Act -- [ ] 입력 받은 에러 메세지를 정해진 포맷으로 바꿔서 반환한다. \ No newline at end of file +#### ~~Act~~ +- [ ] ~~입력 받은 에러 메세지를 정해진 포맷으로 바꿔서 반환한다.~~ + +# 후기 + +기능 단위를 먼저 쭉 정리하고 개발을 시작하니까 좀 더 수월하게 할 수 있었습니다. 고민도 미리 하고 어느정도 설계가 나온 상태여서 그런 것 같습니다. 물론 진행 과정에서 여러모로 수정이 있었지만, 크게 수정된 것은 없었던 것 같습니다. + + +RandomNumberGenerator를 Mocking하는 과정에서, mocking은 static 키워드와 바이트 코드 수준에서의 선언 순서로 인한 문제점이 있다는 것을 처음 알게 되었습니다. 여러 해결 방법을 찾아보고 직접 적용해볼 수 있어서 흥미로웠습니다. 그동안 별 생각 없이 static 키워드와 함께 Util 클래스를 자주 사용하고, 테스트에서도 mockStatic를 사용했습니다. 근데 테스팅 과정에서 static은 bad pattern 이라는 의견도 많이 보여서 스스로 static이 포함된 테스트 코드의 작성 난이도를 고민해보게 되었습니다. + + +Generator를 싱글톤 또는 static 메소드로 쓰고는 싶은데, static 키워드 때문에 테스팅이 어려워져서 고민이 좀 많아졌습니다. Spring을 쓰면 Util class를 Bean으로 등록해서 싱글톤으로 쓰면 되지만, 이번 프로그램에서는 DI Container 같은 것 없이 직접 의존성을 해결해줘야 하다 보니까 더 고민이 많았네요. + + +고민거리가 많았던 토이 프로젝트라 재밌었습니다ㅎㅎ \ No newline at end of file From 0b2e21b5401027b83f7e325cf8aa2758cbbd6584 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 18:08:06 +0900 Subject: [PATCH 25/26] Update README.md --- QUESTION.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/QUESTION.md b/QUESTION.md index cae086de..c15e6951 100644 --- a/QUESTION.md +++ b/QUESTION.md @@ -24,4 +24,5 @@ class RacerTest { } ``` -+ \ No newline at end of file ++ static 키워드에 대한 테스트에 대해서 강사님은 어떻게 생각하시는지? ++ entity, controller, view 여러 곳에 퍼져있는 validate 로직을 통합하는게 나은지, 아니면 지금대로 분산시켜서 각각 검증 로직을 들고 있는게 나은건지? \ No newline at end of file From 38c9804d88c0a5473d09cad721e14918c96cd376 Mon Sep 17 00:00:00 2001 From: Long9725 Date: Sun, 9 Jun 2024 18:09:51 +0900 Subject: [PATCH 26/26] Update README.md --- QUESTION.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/QUESTION.md b/QUESTION.md index c15e6951..032385ed 100644 --- a/QUESTION.md +++ b/QUESTION.md @@ -25,4 +25,5 @@ class RacerTest { ``` + static 키워드에 대한 테스트에 대해서 강사님은 어떻게 생각하시는지? -+ entity, controller, view 여러 곳에 퍼져있는 validate 로직을 통합하는게 나은지, 아니면 지금대로 분산시켜서 각각 검증 로직을 들고 있는게 나은건지? \ No newline at end of file ++ entity, controller, view 여러 곳에 퍼져있는 validate 로직을 통합하는게 나은지, 아니면 지금대로 분산시켜서 각각 검증 로직을 들고 있는게 나은건지? ++ Test case, Test Fixture 등을 쉽게 만들어주는 라이브러리가 있는지? 아님 방법론이라던가?! \ No newline at end of file