diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..73f411f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..3058433 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index bc8d0a3..32e560b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,13 @@ - + + + + + + \ No newline at end of file diff --git a/.idea/racing-game.iml b/.idea/racing-game.iml new file mode 100644 index 0000000..78b2cc5 --- /dev/null +++ b/.idea/racing-game.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/README.md b/README.md index 52004ea..3bd2386 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,6 @@ - 테스트 커버리지는 최소 60%를 넘겨야한다. - 한 메소드에 10줄을 넘지 않는다. + + +![image](https://user-images.githubusercontent.com/47847993/77644495-694a0600-6fa4-11ea-82ad-b565725fa219.png) diff --git a/build.gradle b/build.gradle index efbbce1..5877349 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ jacoco { toolVersion = '0.8.5' } + group 'org.javabom' version '1.0-SNAPSHOT' @@ -21,6 +22,7 @@ dependencies { testCompile group: 'org.assertj', name: 'assertj-core', version: '3.15.0' } + test { useJUnitPlatform() jacoco { @@ -76,4 +78,4 @@ jacocoTestCoverageVerification { } } -} \ No newline at end of file +} diff --git a/src/main/java/racing/RacingGameApplication.java b/src/main/java/racing/RacingGameApplication.java new file mode 100644 index 0000000..139efd5 --- /dev/null +++ b/src/main/java/racing/RacingGameApplication.java @@ -0,0 +1,19 @@ +package racing; + +import racing.domain.RacingGame; +import racing.dto.RacingGameInfo; +import racing.support.DefaultEngine; +import racing.view.InputView; +import racing.view.OutputView; + +public class RacingGameApplication { + + public static void main(String[] args) { + RacingGameInfo racingGameInfo = new RacingGameInfo(InputView.askNameOfCar(), InputView.askCountOfMovement()); + RacingGame racingGame = new RacingGame(racingGameInfo); + + racingGame.raceWith(new DefaultEngine()); + + OutputView.printResult(racingGame.getRacingGameResult()); + } +} diff --git a/src/main/java/racing/domain/Car.java b/src/main/java/racing/domain/Car.java new file mode 100644 index 0000000..2a71339 --- /dev/null +++ b/src/main/java/racing/domain/Car.java @@ -0,0 +1,28 @@ +package racing.domain; + +import racing.support.Engine; + +public class Car { + + private final String name; + private int location; + + public Car(String name) { + this.name = name; + } + + public void tryMoveWith(Engine engine) { + if (engine.enough()) { + location++; + } + } + + public int getLocation() { + return location; + } + + public String getName() { + return name; + } + +} diff --git a/src/main/java/racing/domain/RacingCars.java b/src/main/java/racing/domain/RacingCars.java new file mode 100644 index 0000000..c86eb46 --- /dev/null +++ b/src/main/java/racing/domain/RacingCars.java @@ -0,0 +1,36 @@ +package racing.domain; + +import racing.dto.RacingGameInfo; +import racing.support.Engine; +import racing.vo.CarSnapshot; +import racing.vo.RacingCarsSnapshot; + +import java.util.ArrayList; +import java.util.List; + +public class RacingCars { + + private final List cars; + + public RacingCars(final RacingGameInfo racingGameInfo) { + this.cars = createCar(racingGameInfo.getCarNameGroup()); + } + + private List createCar(final List carGroup) { + List cars = new ArrayList<>(carGroup.size()); + for (String carName : carGroup) { + cars.add(new Car(carName)); + } + return cars; + } + + public RacingCarsSnapshot runWith(final int round, final Engine engine) { + List carSnapshots = new ArrayList<>(cars.size()); + for (Car car : cars) { + car.tryMoveWith(engine); + carSnapshots.add(new CarSnapshot(car.getName(), car.getLocation())); + } + return new RacingCarsSnapshot(round, carSnapshots); + } + +} diff --git a/src/main/java/racing/domain/RacingGame.java b/src/main/java/racing/domain/RacingGame.java new file mode 100644 index 0000000..a548027 --- /dev/null +++ b/src/main/java/racing/domain/RacingGame.java @@ -0,0 +1,40 @@ +package racing.domain; + +import racing.dto.RacingGameInfo; +import racing.dto.RacingGameResult; +import racing.support.Engine; +import racing.vo.RacingCarsSnapshot; + +public class RacingGame { + + private final RacingCars racingCars; + private final RacingGameInfo racingGameInfo; + private final RacingGameResult racingGameResult; + + public RacingGame(RacingGameInfo racingGameInfo) { + this(new RacingCars(racingGameInfo), racingGameInfo, new RacingGameResult(racingGameInfo)); + } + + private RacingGame(RacingCars racingCars, RacingGameInfo racingGameInfo, RacingGameResult racingGameResult) { + this.racingCars = racingCars; + this.racingGameInfo = racingGameInfo; + this.racingGameResult = racingGameResult; + } + + public void raceWith(Engine engine) { + for (int currentRound = 1; currentRound < racingGameInfo.getCountOfMovement() + 1; currentRound++) { + RacingCarsSnapshot snapshot = racingCars.runWith(currentRound, engine); + recordRacingGameSnapshot(snapshot); + } + } + + private void recordRacingGameSnapshot(RacingCarsSnapshot racingCars) { + racingGameResult.record(racingCars); + } + + public RacingGameResult getRacingGameResult() { + return racingGameResult; + } + + +} diff --git a/src/main/java/racing/domain/empty.txt b/src/main/java/racing/domain/empty.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/racing/dto/RacingGameInfo.java b/src/main/java/racing/dto/RacingGameInfo.java new file mode 100644 index 0000000..346be3c --- /dev/null +++ b/src/main/java/racing/dto/RacingGameInfo.java @@ -0,0 +1,45 @@ +package racing.dto; + +import java.util.Arrays; +import java.util.List; + +public class RacingGameInfo { + private static final String NAME_SPLITTER = ","; + + private final List carNameGroup; + private final int countOfMovement; + + public RacingGameInfo(String nameOfCars, String countOfMovement) { + try { + List carGroup = toCarGroup(nameOfCars.trim()); + int inputCountOfMovement = Integer.parseInt(countOfMovement); + + validation(carGroup, inputCountOfMovement); + + this.carNameGroup = carGroup; + this.countOfMovement = inputCountOfMovement; + } catch (NumberFormatException ne) { + throw new IllegalArgumentException("Integer 값만 들어올 수 있습니다"); + } + } + + private List toCarGroup(String carGroup) { + return Arrays.asList(carGroup.split(NAME_SPLITTER)); + } + + private void validation(final List inputNumberOfCar, final int inputCountOfMovement) { + if (inputNumberOfCar.size() <= 1 || inputCountOfMovement <= 0) { + throw new IllegalArgumentException("올바른 입력값이 아닙니다"); + } + } + + public List getCarNameGroup() { + return carNameGroup; + } + + public int getCountOfMovement() { + return countOfMovement; + } + + +} diff --git a/src/main/java/racing/dto/RacingGameResult.java b/src/main/java/racing/dto/RacingGameResult.java new file mode 100644 index 0000000..19ec72c --- /dev/null +++ b/src/main/java/racing/dto/RacingGameResult.java @@ -0,0 +1,30 @@ +package racing.dto; + +import racing.vo.RacingCarsSnapshot; + +import java.util.ArrayList; +import java.util.List; + +public class RacingGameResult { + private final int totalGameRound; + private final List racingCarsSnapshots = new ArrayList<>(); + + public RacingGameResult(final RacingGameInfo racingGameInfo) { + this.totalGameRound = racingGameInfo.getCountOfMovement(); + } + + public void record(RacingCarsSnapshot racingCarsSnapshot) { + if (disableRecord()) { + throw new IllegalArgumentException(String.format("%d경기 이상 기록할 수 없습니다.", totalGameRound)); + } + racingCarsSnapshots.add(racingCarsSnapshot); + } + + private boolean disableRecord() { + return racingCarsSnapshots.size() >= totalGameRound; + } + + public List getRacingCarsSnapshots() { + return racingCarsSnapshots; + } +} diff --git a/src/main/java/racing/support/DefaultEngine.java b/src/main/java/racing/support/DefaultEngine.java new file mode 100644 index 0000000..b6143d6 --- /dev/null +++ b/src/main/java/racing/support/DefaultEngine.java @@ -0,0 +1,17 @@ +package racing.support; + +import racing.util.RandomGenerator; + +public class DefaultEngine implements Engine { + + private static final int MOVE_CONDITION = 4; + + @Override + public boolean enough() { + return isMovable(RandomGenerator.pickRandomNumber()); + } + + private boolean isMovable(final int power) { + return power >= MOVE_CONDITION; + } +} diff --git a/src/main/java/racing/support/Engine.java b/src/main/java/racing/support/Engine.java new file mode 100644 index 0000000..9085d39 --- /dev/null +++ b/src/main/java/racing/support/Engine.java @@ -0,0 +1,5 @@ +package racing.support; + +public interface Engine { + boolean enough(); +} diff --git a/src/main/java/racing/util/RandomGenerator.java b/src/main/java/racing/util/RandomGenerator.java new file mode 100644 index 0000000..7924cf4 --- /dev/null +++ b/src/main/java/racing/util/RandomGenerator.java @@ -0,0 +1,13 @@ +package racing.util; + +import java.util.Random; + +public class RandomGenerator { + + private static final int LIMIT = 10; + private static final Random RANDOM = new Random(); + + public static int pickRandomNumber() { + return RANDOM.nextInt(LIMIT); + } +} diff --git a/src/main/java/racing/view/InputView.java b/src/main/java/racing/view/InputView.java new file mode 100644 index 0000000..406dd81 --- /dev/null +++ b/src/main/java/racing/view/InputView.java @@ -0,0 +1,16 @@ +package racing.view; + +import java.util.Scanner; + +public class InputView { + + public static String askNameOfCar() { + System.out.println("경주할 자동차 이름을 입력하세요. 이름은 쉼표를 기준으로 구분."); + return new Scanner(System.in).nextLine(); + } + + public static String askCountOfMovement() { + System.out.println("이동횟수를 입력하세요"); + return new Scanner(System.in).next(); + } +} diff --git a/src/main/java/racing/view/OutputView.java b/src/main/java/racing/view/OutputView.java new file mode 100644 index 0000000..e353637 --- /dev/null +++ b/src/main/java/racing/view/OutputView.java @@ -0,0 +1,38 @@ +package racing.view; + +import racing.dto.RacingGameResult; +import racing.vo.CarSnapshot; +import racing.vo.RacingCarsSnapshot; + +import java.util.List; + +public class OutputView { + + private static final String TRACK_SIGNATURE = "-"; + + public static void printResult(RacingGameResult racingGameResult) { + List racingCarsPerRound = racingGameResult.getRacingCarsSnapshots(); + for (RacingCarsSnapshot racingCarsSnapshot : racingCarsPerRound) { + printNameAndTrack(racingCarsSnapshot); + System.out.println(); + } + + } + + private static void printNameAndTrack(RacingCarsSnapshot racingCarsSnapshot) { + for (CarSnapshot carSnapshot : racingCarsSnapshot.getCarSnapshots()) { + System.out.printf("%s: ", carSnapshot.getName()); + drawTrack(carSnapshot); + } + } + + private static void drawTrack(CarSnapshot car) { + StringBuilder track = new StringBuilder(); + for (int i = 0; i < car.getLocation(); i++) { + track.append(TRACK_SIGNATURE); + } + System.out.print(track.toString()); + System.out.println(); + } + +} diff --git a/src/main/java/racing/view/empty.txt b/src/main/java/racing/view/empty.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/racing/vo/CarSnapshot.java b/src/main/java/racing/vo/CarSnapshot.java new file mode 100644 index 0000000..d6d3100 --- /dev/null +++ b/src/main/java/racing/vo/CarSnapshot.java @@ -0,0 +1,43 @@ +package racing.vo; + +import java.util.Objects; + +public class CarSnapshot { + private final String name; + private final int location; + + public CarSnapshot(String name, int location) { + this.name = name; + this.location = location; + } + + public int getLocation() { + return location; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "CarOfPerRound{" + + "name='" + name + '\'' + + ", location=" + location + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CarSnapshot that = (CarSnapshot) o; + return location == that.location && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, location); + } +} diff --git a/src/main/java/racing/vo/RacingCarsSnapshot.java b/src/main/java/racing/vo/RacingCarsSnapshot.java new file mode 100644 index 0000000..98d0a78 --- /dev/null +++ b/src/main/java/racing/vo/RacingCarsSnapshot.java @@ -0,0 +1,40 @@ +package racing.vo; + +import java.util.List; +import java.util.Objects; + +public class RacingCarsSnapshot { + private final int round; + private final List carSnapshots; + + public RacingCarsSnapshot(final int round, List carSnapshots) { + this.round = round; + this.carSnapshots = carSnapshots; + } + + public List getCarSnapshots() { + return carSnapshots; + } + + @Override + public String toString() { + return "RacingCarsOfPerRound{" + + "round=" + round + + ", carOfPerRounds=" + carSnapshots + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RacingCarsSnapshot that = (RacingCarsSnapshot) o; + return round == that.round && + Objects.equals(carSnapshots, that.carSnapshots); + } + + @Override + public int hashCode() { + return Objects.hash(round, carSnapshots); + } +} diff --git a/src/test/java/racing/domain/CarTest.java b/src/test/java/racing/domain/CarTest.java new file mode 100644 index 0000000..76094e3 --- /dev/null +++ b/src/test/java/racing/domain/CarTest.java @@ -0,0 +1,28 @@ +package racing.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import racing.support.Engine; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class CarTest { + + @DisplayName("Car 가 4이상의 숫자를 얻으면 이동하고, 4이하의 숫자를 얻으면 이동하지 않는다") + @CsvSource({"4, 1", "3, 0"}) + @ParameterizedTest + void moveCar(int input, int expected) { + //given + int moveCondition = 4; + Engine engine = () -> input >= moveCondition; + Car car = new Car("이름"); + + // when + car.tryMoveWith(engine); + + assertThat(car.getLocation()).isEqualTo(expected); + } + + +} diff --git a/src/test/java/racing/domain/RacingCarsTest.java b/src/test/java/racing/domain/RacingCarsTest.java new file mode 100644 index 0000000..48e252f --- /dev/null +++ b/src/test/java/racing/domain/RacingCarsTest.java @@ -0,0 +1,29 @@ +package racing.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racing.dto.RacingGameInfo; +import racing.vo.CarSnapshot; +import racing.vo.RacingCarsSnapshot; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class RacingCarsTest { + + @DisplayName("현재라운드와 엔진을 가지고 RacingCars가 경기를 한번 하면 경기기록을 반환한다") + @Test + void currentRacing() { + //given + RacingCars racingCars = new RacingCars(new RacingGameInfo("a,b", "5")); + List carSnapshots = Arrays.asList(new CarSnapshot("a", 1), new CarSnapshot("b", 1)); + RacingCarsSnapshot expectRacingCarsSnapshot = new RacingCarsSnapshot(1, carSnapshots); + //when + RacingCarsSnapshot snapshot = racingCars.runWith(1, () -> true); + + //then + assertThat(snapshot).isEqualTo(expectRacingCarsSnapshot); + } +} diff --git a/src/test/java/racing/domain/RacingGameTest.java b/src/test/java/racing/domain/RacingGameTest.java new file mode 100644 index 0000000..b6ba5ec --- /dev/null +++ b/src/test/java/racing/domain/RacingGameTest.java @@ -0,0 +1,26 @@ +package racing.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racing.dto.RacingGameInfo; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class RacingGameTest { + + @DisplayName("경기 진행 횟수만큼 경기스냅샷을 기록한다") + @Test + void snapshot() { + //given + RacingGameInfo racingGameInfo = new RacingGameInfo("a, b, c", "5"); + RacingGame racingGame = new RacingGame(racingGameInfo); + + //when + racingGame.raceWith(() -> true); + + //then + assertThat(racingGame.getRacingGameResult().getRacingCarsSnapshots().size()).isEqualTo(5); + + } + +} diff --git a/src/test/java/racing/domain/empty.txt b/src/test/java/racing/domain/empty.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/racing/dto/RacingGameInfoTest.java b/src/test/java/racing/dto/RacingGameInfoTest.java new file mode 100644 index 0000000..afa0807 --- /dev/null +++ b/src/test/java/racing/dto/RacingGameInfoTest.java @@ -0,0 +1,30 @@ +package racing.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RacingGameInfoTest { + + @DisplayName("이동 횟수에 숫자가 아닌 값을 받으면 익셉션을 던진다") + @CsvSource({"abc, 한글", "123, 한글"}) + @ParameterizedTest + void throwException(String carNames, String countOfMovement) { + assertThatThrownBy(() -> new RacingGameInfo(carNames, countOfMovement)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Integer 값만 들어올 수 있습니다"); + } + + @DisplayName("경기하는 자동차이름 입력이 1개 이하거나, 이동횟수가 0이하이면 익셉션을 던진다") + @CsvSource({"'',-1", "a,0", "a,1", "'a,b',-1", "'a,b', 0"}) + @ParameterizedTest + void invalidInputException(String carGroups, String countOfMovement) { + assertThatThrownBy(() -> new RacingGameInfo(carGroups, countOfMovement)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 입력값이 아닙니다"); + + } + +} diff --git a/src/test/java/racing/dto/RacingGameResultTest.java b/src/test/java/racing/dto/RacingGameResultTest.java new file mode 100644 index 0000000..68507a8 --- /dev/null +++ b/src/test/java/racing/dto/RacingGameResultTest.java @@ -0,0 +1,35 @@ +package racing.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import racing.vo.RacingCarsSnapshot; + +import java.util.ArrayList; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RacingGameResultTest { + + @DisplayName("총 이동횟수만큼 기록이 된 상태에서 다시 기록을 시도하면 익셉션을 던진다") + @Test + void upperAttemptOfMovement() { + //given + RacingGameResult racingGameResult = new RacingGameResult(new RacingGameInfo("a, b", "5")); + recordFiveRacingGame(racingGameResult); + + //when + //then + assertThatThrownBy(() -> racingGameResult.record(new RacingCarsSnapshot(6, new ArrayList<>()))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("5경기 이상 기록할 수 없습니다."); + } + + private void recordFiveRacingGame(RacingGameResult racingGameResult) { + racingGameResult.record(new RacingCarsSnapshot(1, new ArrayList<>())); + racingGameResult.record(new RacingCarsSnapshot(2, new ArrayList<>())); + racingGameResult.record(new RacingCarsSnapshot(3, new ArrayList<>())); + racingGameResult.record(new RacingCarsSnapshot(4, new ArrayList<>())); + racingGameResult.record(new RacingCarsSnapshot(5, new ArrayList<>())); + } + +} diff --git a/src/test/java/racing/util/RandomGeneratorTest.java b/src/test/java/racing/util/RandomGeneratorTest.java new file mode 100644 index 0000000..609a8d6 --- /dev/null +++ b/src/test/java/racing/util/RandomGeneratorTest.java @@ -0,0 +1,17 @@ +package racing.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class RandomGeneratorTest { + + @DisplayName("항상 10이하의 랜덤숫자가 나온다") + @Test + void random() { + assertThat(RandomGenerator.pickRandomNumber()).isLessThanOrEqualTo(10); + } + +} + diff --git a/src/test/java/racing/view/empty.txt b/src/test/java/racing/view/empty.txt deleted file mode 100644 index e69de29..0000000