diff --git a/src/main/java/ladder/Application.java b/src/main/java/ladder/Application.java new file mode 100644 index 00000000..7fe80989 --- /dev/null +++ b/src/main/java/ladder/Application.java @@ -0,0 +1,35 @@ +package ladder; + +import java.util.Map; +import java.util.Random; +import ladder.controller.LadderGameController; +import ladder.domain.Ladder; +import ladder.domain.LadderGameResult; +import ladder.domain.LadderHeight; +import ladder.domain.LadderResults; +import ladder.domain.Participants; +import ladder.view.InputView; +import ladder.view.OutputView; + +public class Application { + + private static final Random RANDOM = new Random(); + + public static void main(String[] args) { + Participants participants = InputView.inputNames(); + LadderResults results = InputView.inputLadderResults(participants.size()); + + LadderHeight height = InputView.inputHeight(); + + Ladder ladder = Ladder.of(participants, height, RANDOM::nextBoolean); + + Map path = ladder.generateResults(); + + LadderGameResult gameResult = LadderGameResult.of(participants, results, path); + + OutputView.printLadder(participants, ladder, results); + LadderGameController.runInquiry(gameResult); + + } + +} diff --git a/src/main/java/ladder/controller/LadderGameController.java b/src/main/java/ladder/controller/LadderGameController.java new file mode 100644 index 00000000..346cac8e --- /dev/null +++ b/src/main/java/ladder/controller/LadderGameController.java @@ -0,0 +1,28 @@ +package ladder.controller; + +import ladder.domain.LadderGameResult; +import ladder.view.InputView; +import ladder.view.OutputView; + +public class LadderGameController { + + public static void runInquiry(LadderGameResult gameResult) { + String nameQuery; + do { + nameQuery = InputView.inputNameQuery(); + printResult(gameResult, nameQuery); + } while (!"all".equals(nameQuery)); + } + + private static void printResult(LadderGameResult gameResult, String nameQuery) { + if ("all".equals(nameQuery)) { + OutputView.printAllResults(gameResult.getAllResults()); + return; + } + try { + OutputView.printSingleResult(gameResult.getResultByName(nameQuery)); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } +} diff --git a/src/main/java/ladder/domain/Ladder.java b/src/main/java/ladder/domain/Ladder.java new file mode 100644 index 00000000..74f98717 --- /dev/null +++ b/src/main/java/ladder/domain/Ladder.java @@ -0,0 +1,54 @@ +package ladder.domain; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BooleanSupplier; + +public class Ladder { + + private final List lines; + private final int width; + + private Ladder(List lines, int width) { + this.lines = lines; + this.width = width; + } + + public static Ladder of(Participants participants, LadderHeight height, BooleanSupplier strategy) { + int participantCount = participants.size(); + List lines = generateLines(participantCount, height, strategy); + + return new Ladder(lines, participantCount); + } + + public int climb(int startIndex) { + int currentIndex = startIndex; + for (Line line : lines) { + currentIndex = line.move(currentIndex); + } + return currentIndex; + } + + public Map generateResults() { + Map results = new LinkedHashMap<>(); + for (int i = 0; i < width; i++) { + results.put(i, climb(i)); + } + return results; + } + + private static List generateLines(int participantCount, LadderHeight height, BooleanSupplier strategy) { + List lines = new ArrayList<>(); + int pointCount = participantCount - 1; + for (int i = 0; i < height.getValue(); i++) { + lines.add(Line.from(pointCount, strategy)); + } + return lines; + } + + public List getLines() { + return lines; + } +} diff --git a/src/main/java/ladder/domain/LadderGameResult.java b/src/main/java/ladder/domain/LadderGameResult.java new file mode 100644 index 00000000..2cca86f1 --- /dev/null +++ b/src/main/java/ladder/domain/LadderGameResult.java @@ -0,0 +1,41 @@ +package ladder.domain; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class LadderGameResult { + + private final Map gameResults; + + private LadderGameResult(Map gameResults) { + this.gameResults = gameResults; + } + + public static LadderGameResult of(Participants participants, LadderResults results, Map ladderPath) { + Map mappedResult = new LinkedHashMap<>(); + + for (int i = 0; i < participants.size(); i++) { + Name participant = participants.getValues().get(i); + int arrivalIndex = ladderPath.get(i); + LadderResult result = results.getValues().get(arrivalIndex); + + mappedResult.put(participant, result); + } + + return new LadderGameResult(mappedResult); + } + + public String getResultByName(String name) { + return gameResults.entrySet().stream() + .filter(entry -> entry.getKey().getName().equals(name)) + .map(entry -> entry.getValue().getValue()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 이름의 참가자가 없습니다.")); + } + + public Map getAllResults() { + return Collections.unmodifiableMap(gameResults); + } + +} diff --git a/src/main/java/ladder/domain/LadderHeight.java b/src/main/java/ladder/domain/LadderHeight.java new file mode 100644 index 00000000..e75c2ad6 --- /dev/null +++ b/src/main/java/ladder/domain/LadderHeight.java @@ -0,0 +1,25 @@ +package ladder.domain; + +public class LadderHeight { + private final int value; + + private LadderHeight(int value) { + this.value = value; + } + + public static LadderHeight from(int value) { + validate(value); + return new LadderHeight(value); + } + + public int getValue() { + return value; + } + + private static void validate(int value) { + if (value < 1) { + throw new IllegalArgumentException("참여할 사람은 최소 2명 이상이어야 합니다."); + } + } + +} diff --git a/src/main/java/ladder/domain/LadderResult.java b/src/main/java/ladder/domain/LadderResult.java new file mode 100644 index 00000000..7d6c2b2d --- /dev/null +++ b/src/main/java/ladder/domain/LadderResult.java @@ -0,0 +1,27 @@ +package ladder.domain; + +public class LadderResult { + private final String value; + + private LadderResult(String value) { + validate(value); + this.value = value; + } + + public static LadderResult from(String value) { + return new LadderResult(value.trim()); + } + + public String getValue() { + return value; + } + + private void validate(String value) { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("실행 결과는 빈 값일 수 없습니다."); + } + if (value.length() > 5) { + throw new IllegalArgumentException("실행 결과는 최대 5글자입니다."); + } + } +} diff --git a/src/main/java/ladder/domain/LadderResults.java b/src/main/java/ladder/domain/LadderResults.java new file mode 100644 index 00000000..94c44ec7 --- /dev/null +++ b/src/main/java/ladder/domain/LadderResults.java @@ -0,0 +1,26 @@ +package ladder.domain; + +import java.util.List; + +public class LadderResults { + private final List results; + + private LadderResults(List results) { + this.results = results; + } + + public static LadderResults of(List rawResults, int participantCount) { + if (rawResults.size() != participantCount) { + throw new IllegalArgumentException("참가자 수와 결과의 개수가 일치해야 합니다."); + } + + List results = rawResults.stream() + .map(LadderResult::from) + .toList(); + return new LadderResults(results); + } + + public List getValues() { + return results; + } +} diff --git a/src/main/java/ladder/domain/Line.java b/src/main/java/ladder/domain/Line.java new file mode 100644 index 00000000..3923a493 --- /dev/null +++ b/src/main/java/ladder/domain/Line.java @@ -0,0 +1,47 @@ +package ladder.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; + +public class Line { + + private final List points; + + private Line(List points) { + this.points = points; + } + + public static Line from(int size, BooleanSupplier strategy) { + return new Line(generatePoints(size, strategy)); + } + + public List getPoints() { + return points; + } + + public int move(int index) { + if (index < points.size() && points.get(index).hasBridge()) { + return index + 1; + } + if (index > 0 && points.get(index - 1).hasBridge()) { + return index - 1; + } + return index; + } + + private static List generatePoints(int size, BooleanSupplier strategy) { + List points = new ArrayList<>(); + boolean lastBridge = false; + for (int i = 0; i < size; i++) { + lastBridge = addPoint(points, lastBridge, strategy); + } + return points; + } + + private static boolean addPoint(List points, boolean lastBridge, BooleanSupplier strategy) { + boolean currentBridge = !lastBridge && strategy.getAsBoolean(); + points.add(Point.from(currentBridge)); + return currentBridge; + } +} diff --git a/src/main/java/ladder/domain/Name.java b/src/main/java/ladder/domain/Name.java new file mode 100644 index 00000000..0146b3b4 --- /dev/null +++ b/src/main/java/ladder/domain/Name.java @@ -0,0 +1,29 @@ +package ladder.domain; + +public class Name { + + private final String name; + + private Name(String name) { + this.name = name; + } + + public static Name from(String name) { + String trimmedName = name.trim(); + validate(trimmedName); + return new Name(trimmedName); + } + + public String getName() { + return name; + } + + private static void validate(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("이름은 빈 값일 수 없습니다."); + } + if (name.length() > 5) { + throw new IllegalArgumentException("이름은 최대 5글자입니다."); + } + } +} diff --git a/src/main/java/ladder/domain/Participants.java b/src/main/java/ladder/domain/Participants.java new file mode 100644 index 00000000..e40fa9e1 --- /dev/null +++ b/src/main/java/ladder/domain/Participants.java @@ -0,0 +1,43 @@ +package ladder.domain; + +import java.util.List; +import java.util.stream.Collectors; + +public class Participants { + + private final List participants; + + private Participants(List participants) { + validate(participants); + this.participants = participants; + } + + public static Participants from(List names) { + List nameList = names.stream() + .map(Name::from) + .collect(Collectors.toList()); + return new Participants(nameList); + } + + private void validate(List participants) { + if (participants.size() < 2) { + throw new IllegalArgumentException("참여할 사람은 최소 2명 이상이어야 합니다."); + } + long distinctCount = participants.stream() + .map(Name::toString) + .distinct() + .count(); + if (distinctCount != participants.size()) { + throw new IllegalArgumentException("이름은 중복될 수 없습니다."); + } + } + + public int size() { + return participants.size(); + } + + public List getValues() { + return participants; + } + +} diff --git a/src/main/java/ladder/domain/Point.java b/src/main/java/ladder/domain/Point.java new file mode 100644 index 00000000..a613c2a2 --- /dev/null +++ b/src/main/java/ladder/domain/Point.java @@ -0,0 +1,24 @@ +package ladder.domain; + +public class Point { + + private static final Point BRIDGE = new Point(true); + private static final Point EMPTY = new Point(false); + + private final boolean hasBridge; + + private Point(boolean hasBridge) { + this.hasBridge = hasBridge; + } + + public static Point from(boolean hasBridge) { + if (hasBridge) { + return BRIDGE; + } + return EMPTY; + } + + public boolean hasBridge() { + return hasBridge; + } +} diff --git a/src/main/java/ladder/view/InputView.java b/src/main/java/ladder/view/InputView.java new file mode 100644 index 00000000..1ffdedfd --- /dev/null +++ b/src/main/java/ladder/view/InputView.java @@ -0,0 +1,74 @@ +package ladder.view; + +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.function.Supplier; +import ladder.domain.LadderHeight; +import ladder.domain.LadderResults; +import ladder.domain.Participants; + +public class InputView { + + private static final Scanner sc = new Scanner(System.in); + + private InputView() {} + + private static T input(String message, Supplier supplier) { + while (true) { + try { + System.out.println(message); + return supplier.get(); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } + } + + public static Participants inputNames() { + return input("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)", () -> { + String input = sc.nextLine(); + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("참가자 이름을 입력해야 합니다."); + } + List names = Arrays.asList(input.split(",")); + return Participants.from(names); + }); + } + + public static LadderResults inputLadderResults(int participantCount) { + return input("\n실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)", () -> { + String input = sc.nextLine(); + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("실행 결과는 빈 값일 수 없습니다."); + } + + List rawResults = Arrays.stream(input.split(",")) + .map(String::trim) + .toList(); + + return LadderResults.of(rawResults, participantCount); + }); + } + + public static LadderHeight inputHeight() { + return input("사다리의 높이는 몇 개인가요?", () -> { + String input = sc.nextLine(); + return LadderHeight.from(parseToInt(input)); + }); + } + + private static int parseToInt(String input) { + try { + return Integer.parseInt(input.trim()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자만 입력 가능합니다."); + } + } + + public static String inputNameQuery() { + System.out.println("\n결과를 보고 싶은 사람은?"); + return sc.nextLine(); + } + +} diff --git a/src/main/java/ladder/view/OutputView.java b/src/main/java/ladder/view/OutputView.java new file mode 100644 index 00000000..69471842 --- /dev/null +++ b/src/main/java/ladder/view/OutputView.java @@ -0,0 +1,71 @@ +package ladder.view; + +import java.util.Map; +import ladder.domain.Ladder; +import ladder.domain.LadderResult; +import ladder.domain.LadderResults; +import ladder.domain.Line; +import ladder.domain.Name; +import ladder.domain.Participants; +import ladder.domain.Point; + +public class OutputView { + + private static final String VERTICAL_BAR = "|"; + private static final String BRIDGE = "-----"; + private static final String EMPTY = " "; + + public static void printLadder(Participants participants, Ladder ladder, LadderResults results) { + System.out.println("\n사다리 결과\n\n"); + + printParticipants(participants); + + ladder.getLines().forEach(OutputView::printLine); + + printLadderResults(results); + } + + private static void printParticipants(Participants participants) { + participants.getValues().forEach(name -> + System.out.print(format(name.getName())) + ); + System.out.println(); + } + + private static void printLadderResults(LadderResults results) { + results.getValues().forEach(result -> + System.out.print(format(result.getValue())) + ); + System.out.println(); + } + + private static String format(String value) { + return String.format("%6s", value); + } + + private static void printLine(Line line) { + System.out.print(" " + VERTICAL_BAR); + line.getPoints().forEach(OutputView::printPoint); + System.out.println(); + } + + private static void printPoint(Point point) { + if (point.hasBridge()) { + System.out.print(BRIDGE + VERTICAL_BAR); + return; + } + System.out.print(EMPTY + VERTICAL_BAR); + } + + public static void printSingleResult(String result) { + System.out.println("\n실행 결과"); + System.out.println(result); + } + + public static void printAllResults(Map results) { + System.out.println("\n실행 결과"); + results.forEach((name, result) -> + System.out.println(name.getName() + " : " + result.getValue()) + ); + } +} diff --git a/src/test/java/ladder/domain/LadderGameResultTest.java b/src/test/java/ladder/domain/LadderGameResultTest.java new file mode 100644 index 00000000..e9f181fc --- /dev/null +++ b/src/test/java/ladder/domain/LadderGameResultTest.java @@ -0,0 +1,63 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LadderGameResultTest { + + private Participants participants; + private LadderResults results; + private Map ladderPath; + + @BeforeEach + void setUp() { + participants = Participants.from(List.of("jin", "park")); + results = LadderResults.of(List.of("win", "lose"), 2); + + ladderPath = Map.of(0, 1, 1, 0); + } + + @DisplayName("사다리 경로에 따라 이름과 결과가 올바르게 매핑된다.") + @Test + void mapResults_Success() { + // when + LadderGameResult gameResult = LadderGameResult.of(participants, results, ladderPath); + + // then + assertThat(gameResult.getResultByName("jin")).isEqualTo("lose"); + assertThat(gameResult.getResultByName("park")).isEqualTo("win"); + } + + @DisplayName("존재하지 않는 이름으로 결과를 조회하면 예외가 발생한다.") + @Test + void getResult_InvalidName_Exception() { + // given + LadderGameResult gameResult = LadderGameResult.of(participants, results, ladderPath); + + // then + assertThatThrownBy(() -> gameResult.getResultByName("hyeong")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("해당 이름의 참가자가 없습니다."); + } + + @DisplayName("전체 결과 조회 시 모든 매핑 정보가 포함되어야 한다.") + @Test + void getAllResults_Success() { + // given + LadderGameResult gameResult = LadderGameResult.of(participants, results, ladderPath); + + // when + Map allResults = gameResult.getAllResults(); + + // then + assertThat(allResults).hasSize(2); + Name jin = participants.getValues().get(0); + assertThat(allResults.get(jin).getValue()).isEqualTo("lose"); + } +} diff --git a/src/test/java/ladder/domain/LadderHeightTest.java b/src/test/java/ladder/domain/LadderHeightTest.java new file mode 100644 index 00000000..ba7d6ab4 --- /dev/null +++ b/src/test/java/ladder/domain/LadderHeightTest.java @@ -0,0 +1,29 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class LadderHeightTest { + + @DisplayName("유효한 사다리 높이로 객체를 생성할 수 있다.") + @ParameterizedTest + @ValueSource(ints = {1, 10, 100}) + void createHeight(int value) { + LadderHeight height = LadderHeight.from(value); + assertThat(height.getValue()).isEqualTo(value); + } + + @DisplayName("사다리 높이가 1 미만일 경우 예외가 발생한다.") + @ParameterizedTest + @ValueSource(ints = {0, -1}) + void invalidHeight(int value) { + assertThatThrownBy(() -> LadderHeight.from(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("참여할 사람은 최소 2명 이상이어야 합니다."); + } + +} diff --git a/src/test/java/ladder/domain/LadderResultTest.java b/src/test/java/ladder/domain/LadderResultTest.java new file mode 100644 index 00000000..15d591a6 --- /dev/null +++ b/src/test/java/ladder/domain/LadderResultTest.java @@ -0,0 +1,44 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class LadderResultTest { + + @DisplayName("1자 이상 5자 이하의 결과는 정상적으로 생성된다.") + @ParameterizedTest + @ValueSource(strings = {"꽝", "5000", "pass", "abcde"}) + void create_Success(String input) { + LadderResult result = LadderResult.from(input); + assertThat(result.getValue()).isEqualTo(input); + } + + @DisplayName("결과 값 앞뒤에 공백이 있을 경우 제거한다.") + @Test + void create_Trim() { + LadderResult result = LadderResult.from(" 꽝 "); + assertThat(result.getValue()).isEqualTo("꽝"); + } + + @DisplayName("결과가 빈 값일 경우 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void create_Blank_Exception(String input) { + assertThatThrownBy(() -> LadderResult.from(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("실행 결과는 빈 값일 수 없습니다."); + } + + @DisplayName("결과가 5자를 초과하면 예외가 발생한다.") + @Test + void create_OverLength_Exception() { + assertThatThrownBy(() -> LadderResult.from("sixchar")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("실행 결과는 최대 5글자입니다."); + } +} diff --git a/src/test/java/ladder/domain/LadderResultsTest.java b/src/test/java/ladder/domain/LadderResultsTest.java new file mode 100644 index 00000000..32a83ca0 --- /dev/null +++ b/src/test/java/ladder/domain/LadderResultsTest.java @@ -0,0 +1,39 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LadderResultsTest { + + @DisplayName("참가자 수와 결과의 개수가 일치하면 정상적으로 생성된다.") + @Test + void create_Success() { + // given + List rawResults = List.of("꽝", "5000", "3000"); + int participantCount = 3; + + // when + LadderResults ladderResults = LadderResults.of(rawResults, participantCount); + + // then + assertThat(ladderResults.getValues()).hasSize(3); + assertThat(ladderResults.getValues().get(0).getValue()).isEqualTo("꽝"); + } + + @DisplayName("참가자 수와 결과의 개수가 다르면 예외가 발생한다.") + @Test + void create_SizeMismatch_Exception() { + // given + List rawResults = List.of("꽝", "5000"); + int participantCount = 3; + + // then + assertThatThrownBy(() -> LadderResults.of(rawResults, participantCount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("참가자 수와 결과의 개수가 일치해야 합니다."); + } +} diff --git a/src/test/java/ladder/domain/LadderTest.java b/src/test/java/ladder/domain/LadderTest.java new file mode 100644 index 00000000..ecc2f149 --- /dev/null +++ b/src/test/java/ladder/domain/LadderTest.java @@ -0,0 +1,81 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LadderTest { + + private static final List NAMES_3 = List.of("p1", "p2", "p3"); + private static final List NAMES_4 = List.of("p1", "p2", "p3", "p4"); + + @DisplayName("설정한 높이만큼 사다리의 가로 줄(Line)이 생성된다.") + @Test + void createLadder_HeightCheck() { + int heightValue = 5; + Ladder ladder = createLadder(NAMES_3, heightValue); + + // then + assertThat(ladder.getLines()).hasSize(heightValue); + } + + @DisplayName("각 가로 줄(Line)은 사람 수보다 1개 적은 포인트(Point)를 가진다.") + @Test + void createLadder_WidthCheck() { + Ladder ladder = createLadder(NAMES_4, 3); + + assertThat(ladder.getLines()).allSatisfy(line -> { + assertThat(line.getPoints()).hasSize(NAMES_4.size() - 1); + }); + } + + @DisplayName("주입된 전략에 따라 사다리 내부의 다리들이 규칙적으로 생성된다.") + @Test + void createLadder_StrategyCheck() { + Ladder ladder = createLadder(NAMES_3, 1); + Line firstLine = ladder.getLines().get(0); + + assertAll( + () -> assertThat(firstLine.getPoints().get(0).hasBridge()).isTrue(), + () -> assertThat(firstLine.getPoints().get(1).hasBridge()).isFalse() + ); + } + + @DisplayName("사다리를 타고 내려가 최종 도착 지점의 인덱스를 반환한다.") + @Test + void climb() { + Ladder ladder = createLadder(List.of("p1", "p2"), 1); + + assertAll( + () -> assertThat(ladder.climb(0)).isEqualTo(1), + () -> assertThat(ladder.climb(1)).isEqualTo(0) + ); + } + + @DisplayName("사다리 전체 실행 결과를 Map 형태로 반환한다.") + @Test + void generateResults() { + Ladder ladder = createLadder(NAMES_3, 1); + + Map results = ladder.generateResults(); + + assertAll( + () -> assertThat(results).hasSize(NAMES_3.size()), + () -> assertThat(results.get(0)).isEqualTo(1), + () -> assertThat(results.get(1)).isEqualTo(0), + () -> assertThat(results.get(2)).isEqualTo(2) + ); + } + + private Ladder createLadder(List names, int height) { + return Ladder.of( + Participants.from(names), + LadderHeight.from(height), + () -> true + ); + } +} diff --git a/src/test/java/ladder/domain/LineTest.java b/src/test/java/ladder/domain/LineTest.java new file mode 100644 index 00000000..410e9d4e --- /dev/null +++ b/src/test/java/ladder/domain/LineTest.java @@ -0,0 +1,70 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class LineTest { + + @DisplayName("전략이 항상 true여도 다리는 연속해서 생성되지 않고 건너서 생성된다.") + @Test + void createLine_AlwaysTrueStrategy() { + // given + int size = 3; + // when + Line line = Line.from(size, () -> true); + List points = line.getPoints(); + + // then + assertAll( + () -> assertThat(points.get(0).hasBridge()).isTrue(), + () -> assertThat(points.get(1).hasBridge()).isFalse(), + () -> assertThat(points.get(2).hasBridge()).isTrue() + ); + } + + @DisplayName("전략이 항상 false이면 모든 지점에 다리가 없어야 한다.") + @Test + void createLine_AlwaysFalseStrategy() { + // given + int size = 5; + // when + Line line = Line.from(size, () -> false); + + // then + assertThat(line.getPoints()) + .extracting(Point::hasBridge) + .containsOnly(false); + } + + @DisplayName("요청한 사이즈만큼 Point가 생성된다.") + @Test + void createLine_SizeCheck() { + int size = 10; + Line line = Line.from(size, () -> true); + assertThat(line.getPoints()).hasSize(size); + } + + @DisplayName("다리 유무에 따라 인덱스가 좌, 우로 이동하거나 그대로 유지된다.") + @Test + void move() { + Line line = Line.from(3, () -> true); + + assertAll( + // 0번 기둥: 0번 포인트가 T이므로 오른쪽(1)으로 이동 + () -> assertThat(line.move(0)).isEqualTo(1), + + // 1번 기둥: 0번 포인트가 T이므로 왼쪽(0)으로 이동 + () -> assertThat(line.move(1)).isEqualTo(0), + + // 2번 기둥: 2번 포인트가 T이므로 오른쪽(3)으로 이동 (1번 포인트는 F) + () -> assertThat(line.move(2)).isEqualTo(3), + + // 3번 기둥: 2번 포인트가 T이므로 왼쪽(2)으로 이동 + () -> assertThat(line.move(3)).isEqualTo(2) + ); + } +} diff --git a/src/test/java/ladder/domain/NameTest.java b/src/test/java/ladder/domain/NameTest.java new file mode 100644 index 00000000..8f89ce3f --- /dev/null +++ b/src/test/java/ladder/domain/NameTest.java @@ -0,0 +1,45 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class NameTest { + + @DisplayName("1자 이상 5자 이하의 이름은 정상적으로 생성된다.") + @ParameterizedTest + @ValueSource(strings = {"jin", "hyeon", "soo"}) + void createName_Success(String input) { + Name name = Name.from(input); + assertThat(name.getName()).isEqualTo(input); + } + + @DisplayName("이름 앞뒤에 공백이 있을 경우 제거(trim) 후 저장한다.") + @Test + void createName_Trim() { + Name name = Name.from(" jin "); + assertThat(name.getName()).isEqualTo("jin"); + } + + @DisplayName("이름이 null이거나 빈 문자열, 혹은 공백만 있으면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void createName_Blank_Exception(String input) { + assertThatThrownBy(() -> Name.from(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이름은 빈 값일 수 없습니다."); + } + + @DisplayName("이름이 5자를 초과하면 예외가 발생한다.") + @Test + void createName_OverLength_Exception() { + assertThatThrownBy(() -> Name.from("sixchar")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이름은 최대 5글자입니다."); + } + +} diff --git a/src/test/java/ladder/domain/ParticipantsTest.java b/src/test/java/ladder/domain/ParticipantsTest.java new file mode 100644 index 00000000..51309064 --- /dev/null +++ b/src/test/java/ladder/domain/ParticipantsTest.java @@ -0,0 +1,52 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ParticipantsTest { + + @DisplayName("2명 이상의 중복되지 않은 이름 리스트로 참가자 명단을 생성할 수 있다.") + @Test + void create_Success() { + // given + List names = List.of("pobi", "honux", "crong"); + + // when + Participants participants = Participants.from(names); + + // then + assertThat(participants.size()).isEqualTo(3); + assertThat(participants.getValues()) + .extracting(Name::getName) + .containsExactly("pobi", "honux", "crong"); + } + + @DisplayName("참여 인원이 2명 미만이면 예외가 발생한다.") + @Test + void validate_Size_Exception() { + // given + List names = List.of("pobi"); + + // then + assertThatThrownBy(() -> Participants.from(names)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("참여할 사람은 최소 2명 이상이어야 합니다."); + } + + @DisplayName("참가자 이름 중에 중복이 있으면 예외가 발생한다.") + @Test + void validate_Duplicate_Exception() { + // given + List names = List.of("pobi", "pobi", "honux"); + + // then + assertThatThrownBy(() -> Participants.from(names)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("이름은 중복될 수 없습니다."); + } + +} diff --git a/src/test/java/ladder/domain/PointTest.java b/src/test/java/ladder/domain/PointTest.java new file mode 100644 index 00000000..6b93da23 --- /dev/null +++ b/src/test/java/ladder/domain/PointTest.java @@ -0,0 +1,31 @@ +package ladder.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class PointTest { + + @DisplayName("Point 생성 시 상태 값이 올바르게 저장된다.") + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void createPoint(boolean hasBridge) { + Point point = Point.from(hasBridge); + assertThat(point.hasBridge()).isEqualTo(hasBridge); + } + + @DisplayName("동일한 상태 값으로 생성 시 같은 인스턴스를 반환한다. (캐싱 확인)") + @Test + void pointCaching() { + + Point first = Point.from(true); + Point second = Point.from(true); + + assertThat(first).isSameAs(second); + } + +}