diff --git a/README.md b/README.md index bb0c84ebb..de63c02df 100644 --- a/README.md +++ b/README.md @@ -1,253 +1,70 @@ # 지하철 노선도 경로 조회 미션 - 등록된 지하철 노선도에서 경로를 조회하는 기능을 구현한다. -
- -## 🚀 기능 요구사항 - -> 프리코스 3주차 미션에서 사용한 코드를 참고해도 무관하다. - -### 초기 설정 -- 프로그램 시작 시 역, 노선, 구간 정보를 초기 설정 해야 한다. -- 거리와 소요 시간은 양의 정수이며 단위는 km와 분을 의미한다. -- 아래의 사전 등록 정보로 반드시 초기 설정을 한다. - -``` - 1. 지하철역으로 교대역, 강남역, 역삼역, 남부터미널역, 양재역, 양재시민의숲역, 매봉역이 등록되어 있다. - 2. 지하철 노선으로 2호선, 3호선, 신분당선이 등록되어 있다. - 3. 노선에 역이 아래와 같이 등록되어 있다.(왼쪽 끝이 상행 종점) - - 2호선: 교대역 - ( 2km / 3분 ) - 강남역 - ( 2km / 3분 ) - 역삼역 - - 3호선: 교대역 - ( 3km / 2분 ) - 남부터미널역 - ( 6km / 5분 ) - 양재역 - ( 1km / 1분 ) - 매봉역 - - 신분당선: 강남역 - ( 2km / 8분 ) - 양재역 - ( 10km / 3분 ) - 양재시민의숲역 - ``` - -### 경로 조회 기능 -- 출발역과 도착역을 입력받아 경로를 조회한다. -- 경로 조회 시 총 거리, 총 소요 시간도 함께 출력한다. -- 경로 조회 기준은 `최단 거리` `최소 시간`이 있다. - -### 예외 처리 -- 경로 조회 시 출발역과 도착역이 같으면 에러를 출력한다. -- 경로 조회 시 출발역과 도착역이 연결되어 있지 않으면 에러를 출력한다. -- 그 외 정상적으로 프로그램이 수행되지 않은 경우 에러를 출력한다. - -
- -## ✍🏻 입출력 요구사항 -- `프로그래밍 실행 결과 예시`와 동일하게 입출력을 구현한다. -- 기대하는 출력 결과는 `[INFO]`를 붙여서 출력한다. 출력값의 형식은 예시와 동일하게 한다. -- 에러 발생 시 `[ERROR]`를 붙여서 출력한다. 에러의 문구는 자유롭게 작성한다. - -### 💻 프로그래밍 실행 결과 예시 -#### 경로 조회 -``` -## 메인 화면 -1. 경로 조회 -Q. 종료 - -## 원하는 기능을 선택하세요. -1 - -## 경로 기준 -1. 최단 거리 -2. 최소 시간 -B. 돌아가기 - -## 원하는 기능을 선택하세요. -1 - -## 출발역을 입력하세요. -교대역 - -## 도착역을 입력하세요. -양재역 - -## 조회 결과 -[INFO] --- -[INFO] 총 거리: 4km -[INFO] 총 소요 시간: 11분 -[INFO] --- -[INFO] 교대역 -[INFO] 강남역 -[INFO] 양재역 - -## 메인 화면 -1. 경로 조회 -Q. 종료 - -... -``` - -#### 에러 출력 예시 - -``` -## 메인 화면 -1. 경로 조회 -Q. 종료 - -## 원하는 기능을 선택하세요. -1 - -## 경로 기준 -1. 최단 거리 -2. 최소 시간 -B. 돌아가기 - -## 원하는 기능을 선택하세요. -1 - -## 출발역을 입력하세요. -강남역 - -## 도착역을 입력하세요. -강남역 - -[ERROR] 출발역과 도착역이 동일합니다. - -## 경로 기준 -1. 최단 거리 -2. 최소 시간 -B. 돌아가기 - -## 원하는 기능을 선택하세요. - -... - -``` - -
- -## 🎱 프로그래밍 요구사항 -- 자바 코드 컨벤션을 지키면서 프로그래밍한다. - - 기본적으로 [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)을 원칙으로 한다. - - 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. -- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. -- 3항 연산자를 쓰지 않는다. -- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다. - - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. -- else 예약어를 쓰지 않는다. - - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. -- 프로그래밍 요구사항에서 별도로 변경 불가 안내가 없는 경우 파일 수정과 패키지 이동을 자유롭게 할 수 있다. -- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 `[ERROR]` 로 시작해야 한다. - -### 프로그래밍 요구사항 - Application -- Application 클래스를 활용해 구현해야 한다. -- Application의 패키지 구조는 변경하지 않는다. -- Application 클래스에 있는 Scanner를 사용하고 별도의 Scanner 객체를 만들지 않는다. -```java -public class Application { - public static void main(String[] args) { - final Scanner scanner = new Scanner(System.in); - ... - } -} -``` - -### 프로그래밍 요구사항 - Station, Line -- Station, Line 클래스를 활용하여 지하철역과 노선을 구현해야 한다. -- 제공하는 각 클래스의 기본 생성자를 추가할 수 없다. -- 필드(인스턴스 변수)인 name의 접근 제어자 private을 변경할 수 없다. -- 가능하면 setter 메소드(ex. setXXX)를 추가하지 않고 구현한다. - -```java -public class Station { - private String name; - - public Station(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - // 추가 기능 구현 -} - -``` - -### 프로그래밍 요구사항 - StationRepository, LineRepository -- Station과 Line의 상태를 저장할 수 있는 StationRepository, LineRepository를 제공한다. -- 필요 시 StationRepository, LineRepository 이 외 추가로 Repository를 만들 수 있다. -- 추가로 생성되는 객체에 대해서 XXXRepository 네이밍으로 저장 클래스를 추가한다. -- 객체들의 상태를 관리하기 위해서 XXXRepository 클래스를 활용해 저장 로직을 구현해야 한다. -- 작성된 메서드는 수정할 수 없고, 필요에 따라 메서드를 자유롭게 추가할 수 있다. - -```java -public class StationRepository { - private static final List stations = new ArrayList<>(); - - public static List stations() { - return Collections.unmodifiableList(stations); - } - - public static void addStation(Station station) { - stations.add(station); - } - - public static boolean deleteStation(String name) { - return stations.removeIf(station -> Objects.equals(station.getName(), name)); - } - - public static void deleteAll() { - stations.clear(); - } -} -``` - -
- -## ❗️힌트 -### 최단 경로 라이브러리 -- jgrapht 라이브러리를 활용하면 간편하게 최단거리를 조회할 수 있음 -- Dijkstra 알고리즘을 반드시 이해할 필요는 없고 미션에 적용할 정도로만 이해하면 됨 -- JGraphtTest 클래스의 테스트를 활용하여 미션에 필요한 라이브러리의 기능을 학습할 수 있음 -- 정점(vertex)과 간선(edge), 그리고 가중치 개념을 이용 - - 정점: 지하철역 - - 간선: 지하철역 연결정보 - - 가중치: 거리 or 소요 시간 -- 최단 거리 기준 조회 시 가중치를 거리로 설정 - -```java -@Test -public void getDijkstraShortestPath() { - WeightedMultigraph graph - = new WeightedMultigraph(DefaultWeightedEdge.class); - graph.addVertex("v1"); - graph.addVertex("v2"); - graph.addVertex("v3"); - graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2); - graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2); - graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100); - - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph); - List shortestPath = dijkstraShortestPath.getPath("v3", "v1").getVertexList(); - - assertThat(shortestPath.size()).isEqualTo(3); -} -``` - -#### 테스트 설명 - - - -- 역 사이의 거리를 고려하지 않는 경우 V1->V3 경로가 최단 경로 -- 역 사이의 거리를 고려할 경우 V1->V3 경로의 거리는 100km, V1->V2->V3 경로의 거리는 4km이므로 최단 경로는 V1->V2->V3 - -
- -## 📈 진행 요구사항 -- 미션은 [java-subway-path-precourse 저장소](https://github.com/woowacourse/java-subway-path-precourse) 를 fork/clone해 시작한다. -- 기능을 구현하기 전에 java-subway-path-precourse/docs/README.md 파일에 구현할 기능 목록을 정리해 추가한다. -- git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다. - - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다. -- [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 절차를 따라 미션을 제출한다. - - [프리코스 과제 FAQ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse/faq) 문서를 참고하여 진행할 수 있다. -
- -## 📝 License - -This project is [MIT](https://github.com/woowacourse/java-subway-path-precourse/blob/master/LICENSE.md) licensed. +## 기능 구현 목록 +> 기능 작동 순서대로 작성 + +1. 지하철 역 및 노선 초기 설정 +2. 기능 입력 및 검증 + - 예외 사항 + - 1,Q가 아닌 경우 + - 잘못된 형식입니다. +3. 경로 조회 + 1. 기능 입력 및 검증 + - 예외 사항 + - 1,2,B가 아닌 경우 + - 잘못된 형식입니다. + 2. 최단 거리 + 1. 출발역 입력 및 검증 + - 예외 사항 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + 2. 도착역 입력 및 검증 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + - 출발역과 같은 경우 + - 출발역과 도착역이 동일합니다. + 3. 계산 + - 예외 사항 + - 출발역과 도착역이 연결되어있지 않은 경우 + - IllegalArgumentException(no such vertex in graph: %s)이 터지면 잡아서 처리 + - 출발역과 도착역이 연결되어있지 않습니다. + 4. 출력 + - 총 거리 + - 총 거리: %dkm + - 총 소요 시간 + - 총 소요 시간: %d분 + - 경로 + 3. 최소 시간 + 1. 출발역 입력 및 검증 + - 예외 사항 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + 2. 도착역 입력 및 검증 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + - 출발역과 같은 경우 + - 출발역과 도착역이 동일합니다. + 3. 계산 + - 예외 사항 + - 출발역과 도착역이 연결되어있지 않은 경우 + - IllegalArgumentException(no such vertex in graph: %s)이 터지면 잡아서 처리 + - 출발역과 도착역이 연결되어있지 않습니다. + 4. 출력 + - 총 거리 + - 총 거리: %dkm + - 총 소요 시간 + - 총 소요 시간: %d분 + - 경로 +4. 돌아가기 + 1. 메인 화면으로 이동 +5. 종료 + +## 특이 사항 +* Application 클래스에 있는 Scanner를 사용하고 별도의 Scanner 객체를 만들지 않는다. +- **Station, Line** + - 제공하는 각 클래스의 기본 생성자를 추가할 수 없다. + * 필드(인스턴스 변수)인 name의 접근 제어자 private을 변경할 수 없다. + * 가능하면 setter 메소드(ex. setXXX)를 추가하지 않고 구현한다. +- **StationRepository, LineRepository** + - 작성된 메서드는 수정할 수 없고, 필요에 따라 메서드를 자유롭게 추가할 수 있다. diff --git a/build.gradle b/build.gradle index ed9d9eeda..7f08631c1 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,13 @@ version '1.0-SNAPSHOT' repositories { mavenCentral() + maven { url 'https://jitpack.io' } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } dependencies { @@ -14,6 +21,7 @@ dependencies { testImplementation('org.junit.jupiter:junit-jupiter:5.7.0') testImplementation('org.assertj:assertj-core:3.18.1') testImplementation('org.mockito:mockito-inline:3.6.0') + implementation ('com.github.woowacourse-projects:mission-utils:1.2.0') } test { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dcf98e9d0..bbb293702 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..692e72112 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,26 @@ package subway; +import java.io.IOException; import java.util.Scanner; +import subway.command.query.QueryMenuCommandRegistry; +import subway.command.main.MainMenuCommandRegistry; +import subway.controller.SubwayController; +import subway.service.SubwayService; public class Application { public static void main(String[] args) { final Scanner scanner = new Scanner(System.in); - // TODO: 프로그램 구현 + + SubwayService service = new SubwayService(); + QueryMenuCommandRegistry queryMenuCommandRegistry = QueryMenuCommandRegistry.from(service, scanner); + + MainMenuCommandRegistry mainRegistry = MainMenuCommandRegistry.from(queryMenuCommandRegistry, scanner); + + SubwayController subwayController = new SubwayController(mainRegistry, service, scanner); + try { + subwayController.run(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/subway/command/Command.java b/src/main/java/subway/command/Command.java new file mode 100644 index 000000000..a25f0a800 --- /dev/null +++ b/src/main/java/subway/command/Command.java @@ -0,0 +1,5 @@ +package subway.command; + +public interface Command { + void execute(); +} diff --git a/src/main/java/subway/command/main/MainMenuCommandRegistry.java b/src/main/java/subway/command/main/MainMenuCommandRegistry.java new file mode 100644 index 000000000..a7a92e612 --- /dev/null +++ b/src/main/java/subway/command/main/MainMenuCommandRegistry.java @@ -0,0 +1,26 @@ +package subway.command.main; + +import java.util.EnumMap; +import java.util.Scanner; +import subway.command.Command; +import subway.command.query.QueryMenuCommandRegistry; +import subway.command.main.impl.QueryCommand; + +public class MainMenuCommandRegistry { + + private final EnumMap commands; + + private MainMenuCommandRegistry(EnumMap commands) { + this.commands = commands; + } + + public static MainMenuCommandRegistry from(QueryMenuCommandRegistry queryRegistry, Scanner scanner) { + EnumMap map = new EnumMap<>(MainMenuOption.class); + map.put(MainMenuOption.QUERY, new QueryCommand(queryRegistry, scanner)); + return new MainMenuCommandRegistry(map); + } + + public void execute(MainMenuOption option) { + commands.get(option).execute(); + } +} diff --git a/src/main/java/subway/command/main/MainMenuOption.java b/src/main/java/subway/command/main/MainMenuOption.java new file mode 100644 index 000000000..8b27cfa7b --- /dev/null +++ b/src/main/java/subway/command/main/MainMenuOption.java @@ -0,0 +1,23 @@ +package subway.command.main; + +import java.util.Arrays; +import subway.constant.ErrorMessage; + +public enum MainMenuOption { + QUERY("1"), + QUIT("Q"); + + private final String command; + + MainMenuOption(String command) { + this.command = command; + } + + public static MainMenuOption from(String command) { + String normalized = command.trim(); + return Arrays.stream(values()) + .filter(opt -> opt.command.equals(normalized)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } +} diff --git a/src/main/java/subway/command/main/impl/QueryCommand.java b/src/main/java/subway/command/main/impl/QueryCommand.java new file mode 100644 index 000000000..9d0e5632b --- /dev/null +++ b/src/main/java/subway/command/main/impl/QueryCommand.java @@ -0,0 +1,33 @@ +package subway.command.main.impl; + +import java.util.Scanner; +import subway.command.Command; +import subway.command.query.QueryMenuCommandRegistry; +import subway.command.query.QueryMenuOption; +import subway.util.InputParser; +import subway.util.Retry; +import subway.view.InputView; + +public class QueryCommand implements Command { + + private final QueryMenuCommandRegistry stationRegistry; + private final Scanner scanner; + + public QueryCommand(QueryMenuCommandRegistry stationRegistry, Scanner scanner) { + this.stationRegistry = stationRegistry; + this.scanner = scanner; + } + + @Override + public void execute() { + Retry.retryUntilSuccess(() -> { + QueryMenuOption option = InputParser.parseQueryMenuOption(InputView.readQueryMenuSelection(scanner)); + + if (option.equals(QueryMenuOption.BACK)) { + return; + } + + stationRegistry.execute(option); + }); + } +} diff --git a/src/main/java/subway/command/query/QueryMenuCommandRegistry.java b/src/main/java/subway/command/query/QueryMenuCommandRegistry.java new file mode 100644 index 000000000..53da03250 --- /dev/null +++ b/src/main/java/subway/command/query/QueryMenuCommandRegistry.java @@ -0,0 +1,28 @@ +package subway.command.query; + +import java.util.EnumMap; +import java.util.Scanner; +import subway.command.Command; +import subway.command.query.impl.DistanceCommand; +import subway.command.query.impl.TimeCommand; +import subway.service.SubwayService; + +public class QueryMenuCommandRegistry { + + private final EnumMap commands; + + private QueryMenuCommandRegistry(EnumMap commands) { + this.commands = commands; + } + + public static QueryMenuCommandRegistry from(SubwayService service, Scanner scanner) { + EnumMap map = new EnumMap<>(QueryMenuOption.class); + map.put(QueryMenuOption.DISTANCE, new DistanceCommand(service, scanner)); + map.put(QueryMenuOption.TIME, new TimeCommand(service, scanner)); + return new QueryMenuCommandRegistry(map); + } + + public void execute(QueryMenuOption option) { + commands.get(option).execute(); + } +} diff --git a/src/main/java/subway/command/query/QueryMenuOption.java b/src/main/java/subway/command/query/QueryMenuOption.java new file mode 100644 index 000000000..c2bdd598a --- /dev/null +++ b/src/main/java/subway/command/query/QueryMenuOption.java @@ -0,0 +1,24 @@ +package subway.command.query; + +import java.util.Arrays; +import subway.constant.ErrorMessage; + +public enum QueryMenuOption { + DISTANCE("1"), + TIME("2"), + BACK("B"); + + private final String command; + + QueryMenuOption(String command) { + this.command = command; + } + + public static QueryMenuOption from(String command) { + String normalized = command.trim(); + return Arrays.stream(values()) + .filter(opt -> opt.command.equals(normalized)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } +} diff --git a/src/main/java/subway/command/query/impl/DistanceCommand.java b/src/main/java/subway/command/query/impl/DistanceCommand.java new file mode 100644 index 000000000..2dd71685b --- /dev/null +++ b/src/main/java/subway/command/query/impl/DistanceCommand.java @@ -0,0 +1,49 @@ +package subway.command.query.impl; + +import java.util.Scanner; +import subway.command.Command; +import subway.constant.ErrorMessage; +import subway.domain.Result; +import subway.domain.Station; +import subway.service.SubwayService; +import subway.util.InputParser; +import subway.view.InputView; +import subway.view.OutputView; + +public class DistanceCommand implements Command { + + private final SubwayService service; + private final Scanner scanner; + + public DistanceCommand(SubwayService service, Scanner scanner) { + this.service = service; + this.scanner = scanner; + } + + @Override + public void execute() { + Station startStation = getStartStation(); + Station endStation = getEndStation(startStation); + + Result result = getResult(startStation, endStation); + + OutputView.printRoute(result); + } + + private Station getStartStation() { + String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); + return service.getStation(stationName); + } + + private Station getEndStation(Station startStation) { + String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); + if (startStation.equals(service.getStation(stationName))) { + throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + return service.getStation(stationName); + } + + private Result getResult(Station startStation, Station endStation) { + return service.calculateMinDistanceRoute(startStation, endStation); + } +} diff --git a/src/main/java/subway/command/query/impl/TimeCommand.java b/src/main/java/subway/command/query/impl/TimeCommand.java new file mode 100644 index 000000000..c3f5f7378 --- /dev/null +++ b/src/main/java/subway/command/query/impl/TimeCommand.java @@ -0,0 +1,49 @@ +package subway.command.query.impl; + +import java.util.Scanner; +import subway.command.Command; +import subway.constant.ErrorMessage; +import subway.domain.Result; +import subway.domain.Station; +import subway.service.SubwayService; +import subway.util.InputParser; +import subway.view.InputView; +import subway.view.OutputView; + +public class TimeCommand implements Command { + + private final SubwayService service; + private final Scanner scanner; + + public TimeCommand(SubwayService service, Scanner scanner) { + this.service = service; + this.scanner = scanner; + } + + @Override + public void execute() { + Station startStation = getStartStation(); + Station endStation = getEndStation(startStation); + + Result result = getResult(startStation, endStation); + + OutputView.printRoute(result); + } + + private Station getStartStation() { + String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); + return service.getStation(stationName); + } + + private Station getEndStation(Station startStation) { + String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); + if (startStation.equals(service.getStation(stationName))) { + throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + return service.getStation(stationName); + } + + private Result getResult(Station startStation, Station endStation) { + return service.calculateMinTimeRoute(startStation, endStation); + } +} diff --git a/src/main/java/subway/constant/ErrorMessage.java b/src/main/java/subway/constant/ErrorMessage.java new file mode 100644 index 000000000..b1eb80f1a --- /dev/null +++ b/src/main/java/subway/constant/ErrorMessage.java @@ -0,0 +1,22 @@ +package subway.constant; + +public enum ErrorMessage { + + FORMAT_ERROR("잘못된 형식입니다."), + + NO_EXIST_STATION("존재하지 않는 역입니다."), + SAME_START_END_STATION("출발역과 도착역이 동일합니다."), + NO_CONNECTED_STATIONS("출발역과 도착역이 연결되어있지 않습니다."), + ; + + private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage(Object... args) { + return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); + } +} diff --git a/src/main/java/subway/controller/SubwayController.java b/src/main/java/subway/controller/SubwayController.java new file mode 100644 index 000000000..a70eae32b --- /dev/null +++ b/src/main/java/subway/controller/SubwayController.java @@ -0,0 +1,60 @@ +package subway.controller; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import subway.command.main.MainMenuCommandRegistry; +import subway.command.main.MainMenuOption; +import subway.service.SubwayService; +import subway.util.InputParser; +import subway.util.Retry; +import subway.view.InputView; + +public class SubwayController { + + private final MainMenuCommandRegistry registry; + private final SubwayService service; + private final Scanner scanner; + + public SubwayController(MainMenuCommandRegistry registry, SubwayService service, Scanner scanner) { + this.registry = registry; + this.service = service; + this.scanner = scanner; + } + + public void run() throws IOException { + setInit(); + + while (true) { + MainMenuOption option = readOption(); + + if (option.equals(MainMenuOption.QUIT)) { + return; + } + + registry.execute(option); + } + } + + private void setInit() { + List stations = Arrays.asList("교대역", "강남역", "역삼역", "남부터미널역", "양재역", "양재시민의숲역", "매봉역"); + List> stationPairs = Arrays.asList( + Arrays.asList("교대역", "강남역"), Arrays.asList("강남역", "역삼역"), + Arrays.asList("교대역", "남부터미널역"), Arrays.asList("남부터미널역", "양재역"), Arrays.asList("양재역", "매봉역"), + Arrays.asList("강남역", "양재역"), Arrays.asList("양재역", "양재시민의숲역") + ); + List> weightPairs = Arrays.asList( + Arrays.asList(2, 3), Arrays.asList(2, 3), + Arrays.asList(3, 2), Arrays.asList(6, 5), Arrays.asList(1, 1), + Arrays.asList(2, 8), Arrays.asList(10, 3) + ); + service.setStations(stations, stationPairs, weightPairs); + } + + private MainMenuOption readOption() { + return Retry.retryUntilSuccess(() -> + InputParser.parseMainMenuOption(InputView.readMainMenuSelection(scanner)) + ); + } +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index f4d738d5a..4e47d7f1d 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,15 +1,17 @@ package subway.domain; public class Line { - private String name; + private final String name; public Line(String name) { this.name = name; } + public static Line from(String name) { + return new Line(name); + } + public String getName() { return name; } - - // 추가 기능 구현 } diff --git a/src/main/java/subway/domain/Result.java b/src/main/java/subway/domain/Result.java new file mode 100644 index 000000000..2f589f988 --- /dev/null +++ b/src/main/java/subway/domain/Result.java @@ -0,0 +1,6 @@ +package subway.domain; + +import java.util.List; + +public record Result(List stations, int distance, int time) { +} diff --git a/src/main/java/subway/domain/RouteRepository.java b/src/main/java/subway/domain/RouteRepository.java new file mode 100644 index 000000000..6d556c119 --- /dev/null +++ b/src/main/java/subway/domain/RouteRepository.java @@ -0,0 +1,83 @@ +package subway.domain; + +import java.util.List; +import java.util.stream.Collectors; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; +import subway.constant.ErrorMessage; + +public class RouteRepository { + + private static final WeightedMultigraph distanceGraph = new WeightedMultigraph( + DefaultWeightedEdge.class); + private static final WeightedMultigraph timeGraph = new WeightedMultigraph( + DefaultWeightedEdge.class); + private static final DijkstraShortestPath minDistancePath = new DijkstraShortestPath(distanceGraph); + private static final DijkstraShortestPath minTimePath = new DijkstraShortestPath(timeGraph); + + public static void addStation(Station station) { + distanceGraph.addVertex(station); + timeGraph.addVertex(station); + } + + public static void addRoute(Station start, Station end, int distance, int time) { + distanceGraph.setEdgeWeight(distanceGraph.addEdge(start, end), distance); + timeGraph.setEdgeWeight(timeGraph.addEdge(start, end), time); + } + + public static Result getMinDistanceRoute(Station startStation, Station endStation) { + GraphPath path = minDistancePath.getPath(startStation, endStation); + if (path == null) { + throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } + + List stations = path.getVertexList(); + List stationNames = stations.stream() + .map(Station::getName) + .collect(Collectors.toList()); + int misDistance = (int) minDistancePath.getPath(startStation, endStation).getWeight(); + int timeSum = getTime(stations); + + return new Result(stationNames, misDistance, timeSum); + } + + private static int getTime(List stations) { + int timeSum = 0; + for (int i = 0; i < stations.size() - 1; i++) { + Station firstStation = stations.get(i); + Station secondStation = stations.get(i + 1); + + timeSum += (int) minTimePath.getPath(firstStation, secondStation).getWeight(); + } + return timeSum; + } + + public static Result getMinTimeRoute(Station startStation, Station endStation) { + GraphPath path = minTimePath.getPath(startStation, endStation); + if (path == null) { + throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } + + List stations = path.getVertexList(); + List stationNames = stations.stream() + .map(Station::getName) + .collect(Collectors.toList()); + int distanceSum = getDistance(stations); + int minTime = (int) minTimePath.getPath(startStation, endStation).getWeight(); + + return new Result(stationNames, distanceSum, minTime); + } + + private static int getDistance(List stations) { + int distanceSum = 0; + for (int i = 0; i < stations.size() - 1; i++) { + Station firstStation = stations.get(i); + Station secondStation = stations.get(i + 1); + + distanceSum += (int) minDistancePath.getPath(firstStation, secondStation).getWeight(); + } + return distanceSum; + } +} diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index bdb142590..671a711d4 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -1,15 +1,33 @@ package subway.domain; +import java.util.Objects; + public class Station { - private String name; + + private final String name; public Station(String name) { this.name = name; } + public static Station from(String name) { + return new Station(name); + } + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + Station station = (Station) object; + return Objects.equals(name, station.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + public String getName() { return name; } - - // 추가 기능 구현 } diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/StationRepository.java index 8ed9d103f..ef1137c4e 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/StationRepository.java @@ -1,23 +1,21 @@ package subway.domain; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Objects; +import subway.constant.ErrorMessage; public class StationRepository { private static final List stations = new ArrayList<>(); - public static List stations() { - return Collections.unmodifiableList(stations); - } - public static void addStation(Station station) { stations.add(station); } - public static boolean deleteStation(String name) { - return stations.removeIf(station -> Objects.equals(station.getName(), name)); + public static Station getStation(String stationName) { + return stations.stream() + .filter(station -> station.getName().equals(stationName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_STATION.getErrorMessage())); } public static void deleteAll() { diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java new file mode 100644 index 000000000..17cac4b43 --- /dev/null +++ b/src/main/java/subway/service/SubwayService.java @@ -0,0 +1,46 @@ +package subway.service; + +import java.util.List; +import subway.domain.Result; +import subway.domain.RouteRepository; +import subway.domain.Station; +import subway.domain.StationRepository; + +public class SubwayService { + + public void setStations(List stations, List> stationPairs, List> weightPairs) { + setStations(stations); + setRoutes(stationPairs, weightPairs); + } + + private static void setStations(List stations) { + for (String stationName : stations) { + Station station = Station.from(stationName); + StationRepository.addStation(station); + RouteRepository.addStation(station); + } + } + + private static void setRoutes(List> stationPairs, List> weightPairs) { + for (int i = 0; i < stationPairs.size(); i++) { + Station start = StationRepository.getStation(stationPairs.get(i).get(0)); + Station end = StationRepository.getStation(stationPairs.get(i).get(1)); + int distance = weightPairs.get(i).get(0); + int time = weightPairs.get(i).get(1); + + RouteRepository.addRoute(start, end, distance, time); + } + } + + public Station getStation(String station) { + return StationRepository.getStation(station); + } + + public Result calculateMinDistanceRoute(Station startStation, Station endStation) { + return RouteRepository.getMinDistanceRoute(startStation, endStation); + } + + public Result calculateMinTimeRoute(Station startStation, Station endStation) { + return RouteRepository.getMinTimeRoute(startStation, endStation); + } +} diff --git a/src/main/java/subway/util/InputParser.java b/src/main/java/subway/util/InputParser.java new file mode 100644 index 000000000..1def4054d --- /dev/null +++ b/src/main/java/subway/util/InputParser.java @@ -0,0 +1,22 @@ +package subway.util; + +import subway.command.query.QueryMenuOption; +import subway.command.main.MainMenuOption; + +public final class InputParser { + + private InputParser() { + } + + public static String parseStation(String rawInput) { + return rawInput.strip(); + } + + public static MainMenuOption parseMainMenuOption(String rawInput) { + return MainMenuOption.from(rawInput.strip()); + } + + public static QueryMenuOption parseQueryMenuOption(String rawInput) { + return QueryMenuOption.from(rawInput.strip()); + } +} diff --git a/src/main/java/subway/util/Retry.java b/src/main/java/subway/util/Retry.java new file mode 100644 index 000000000..8dddf25c1 --- /dev/null +++ b/src/main/java/subway/util/Retry.java @@ -0,0 +1,31 @@ +package subway.util; + +import java.util.function.Supplier; +import subway.view.OutputView; + +public final class Retry { + + private Retry() { + } + + public static T retryUntilSuccess(Supplier action) { + while (true) { + try { + return action.get(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } + + public static void retryUntilSuccess(Runnable action) { + while (true) { + try { + action.run(); + return; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } +} diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java new file mode 100644 index 000000000..b4aa0aff5 --- /dev/null +++ b/src/main/java/subway/view/InputView.java @@ -0,0 +1,35 @@ +package subway.view; + +import java.util.Scanner; + +public class InputView { + + public static String readMainMenuSelection(Scanner scanner) { + System.out.println("\n## 메인 화면\n" + + "1. 경로 조회\n" + + "Q. 종료\n" + + "\n" + + "## 원하는 기능을 선택하세요."); + return scanner.nextLine(); + } + + public static String readQueryMenuSelection(Scanner scanner) { + System.out.println("\n## 경로 기준\n" + + "1. 최단 거리\n" + + "2. 최소 시간\n" + + "B. 돌아가기\n" + + "\n" + + "## 원하는 기능을 선택하세요."); + return scanner.nextLine(); + } + + public static String readStartStation(Scanner scanner) { + System.out.println("\n## 출발역을 입력하세요."); + return scanner.nextLine(); + } + + public static String readEndStation(Scanner scanner) { + System.out.println("\n## 도착역을 입력하세요."); + return scanner.nextLine(); + } +} diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java new file mode 100644 index 000000000..ecfc336cd --- /dev/null +++ b/src/main/java/subway/view/OutputView.java @@ -0,0 +1,24 @@ +package subway.view; + +import subway.domain.Result; + +public class OutputView { + + private OutputView() { + } + + public static void printErrorMessage(IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + + public static void printRoute(Result result) { + System.out.println("\n## 조회 결과"); + System.out.println("[INFO] ---"); + System.out.printf("[INFO] 총 거리: %dkm\n", result.distance()); + System.out.printf("[INFO] 총 소요 시간: %d분\n", result.time()); + System.out.println("[INFO] ---"); + for (String station : result.stations()) { + System.out.println("[INFO] " + station); + } + } +} diff --git a/src/test/java/subway/ApplicationTest.java b/src/test/java/subway/ApplicationTest.java new file mode 100644 index 000000000..7ddde4bcf --- /dev/null +++ b/src/test/java/subway/ApplicationTest.java @@ -0,0 +1,114 @@ +package subway; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; + +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import subway.constant.ErrorMessage; + +class ApplicationTest extends NsTest { + + @Test + void 메인화면_기능_선택_오류() { + assertSimpleTest( + () -> { + runException("2"); + assertThat(output()).contains(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + ); + } + + @Test + void 경로조회화면_기능_선택_오류() { + assertSimpleTest( + () -> { + runException("1", "3"); + assertThat(output()).contains(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + ); + } + + @Test + void 없는_역_선택_오류() { + assertSimpleTest( + () -> { + runException("1", "1", "잠실역"); + assertThat(output()).contains(ErrorMessage.NO_EXIST_STATION.getErrorMessage()); + } + ); + } + + @Test + void 출발역_도착역_동일_오류() { + assertSimpleTest( + () -> { + runException("1", "1", "교대역", "교대역"); + assertThat(output()).contains(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + ); + } + + @ParameterizedTest + @MethodSource("minDistanceResultProvider") + void 최단거리_경로_구하기(String startStation, String endStation, int distance, int time) { + assertSimpleTest( + () -> { + runException("1", "1", startStation, endStation); + assertThat(output()).contains(distance + "km"); + assertThat(output()).contains(time + "분"); + } + ); + } + + static Stream minDistanceResultProvider() { + return Stream.of( + Arguments.of("교대역", "양재역", 4, 11), + Arguments.of("교대역", "강남역", 2, 3), + Arguments.of("교대역", "매봉역", 5, 12), + Arguments.of("교대역", "양재시민의숲역", 14, 14), + Arguments.of("양재역", "교대역", 4, 11), + Arguments.of("양재역", "역삼역", 4, 11), + Arguments.of("양재역", "남부터미널역", 6, 5), + Arguments.of("매봉역", "역삼역", 5, 12), + Arguments.of("매봉역", "강남역", 3, 9), + Arguments.of("강남역", "남부터미널역", 5, 5) + ); + } + + @ParameterizedTest + @MethodSource("minTimeResultProvider") + void 최소거리_경로_구하기(String startStation, String endStation, int distance, int time) { + assertSimpleTest( + () -> { + runException("1", "2", startStation, endStation); + assertThat(output()).contains(distance + "km"); + assertThat(output()).contains(time + "분"); + } + ); + } + + static Stream minTimeResultProvider() { + return Stream.of( + Arguments.of("교대역", "양재역", 9, 7), + Arguments.of("교대역", "강남역", 2, 3), + Arguments.of("교대역", "매봉역", 10, 8), + Arguments.of("교대역", "양재시민의숲역", 19, 10), + Arguments.of("양재역", "교대역", 9, 7), + Arguments.of("양재역", "역삼역", 4, 11), + Arguments.of("양재역", "남부터미널역", 6, 5), + Arguments.of("매봉역", "역삼역", 5, 12), + Arguments.of("매봉역", "강남역", 3, 9), + Arguments.of("강남역", "남부터미널역", 5, 5) + ); + } + + @Override + protected void runMain() { + Application.main(new String[]{}); + } +} diff --git a/src/test/java/subway/command/main/MainMenuOptionTest.java b/src/test/java/subway/command/main/MainMenuOptionTest.java new file mode 100644 index 000000000..685f869e3 --- /dev/null +++ b/src/test/java/subway/command/main/MainMenuOptionTest.java @@ -0,0 +1,23 @@ +package subway.command.main; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import subway.constant.ErrorMessage; + +class MainMenuOptionTest { + + @Test + void 기능_선택_오류() { + assertThatThrownBy(() -> MainMenuOption.from("3")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + + @Test + void 기능_선택_정상() { + MainMenuOption option = MainMenuOption.from("1"); + assertThat(option).isEqualTo(MainMenuOption.QUERY); + } +} diff --git a/src/test/java/subway/command/query/QueryMenuOptionTest.java b/src/test/java/subway/command/query/QueryMenuOptionTest.java new file mode 100644 index 000000000..c135b0965 --- /dev/null +++ b/src/test/java/subway/command/query/QueryMenuOptionTest.java @@ -0,0 +1,23 @@ +package subway.command.query; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import subway.constant.ErrorMessage; + +class QueryMenuOptionTest { + + @Test + void 기능_선택_오류() { + assertThatThrownBy(() -> QueryMenuOption.from("3")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + + @Test + void 기능_선택_정상() { + QueryMenuOption option = QueryMenuOption.from("1"); + assertThat(option).isEqualTo(QueryMenuOption.DISTANCE); + } +} diff --git a/src/test/java/subway/domain/RouteRepositoryTest.java b/src/test/java/subway/domain/RouteRepositoryTest.java new file mode 100644 index 000000000..8b0a9b446 --- /dev/null +++ b/src/test/java/subway/domain/RouteRepositoryTest.java @@ -0,0 +1,119 @@ +package subway.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import subway.constant.ErrorMessage; + +class RouteRepositoryTest { + + @BeforeAll + static void setUpAll() { + List stations = Arrays.asList("교대역", "강남역", "역삼역", "남부터미널역", "양재역", "양재시민의숲역", "매봉역"); + List> stationPairs = Arrays.asList( + Arrays.asList("교대역", "강남역"), Arrays.asList("강남역", "역삼역"), + Arrays.asList("교대역", "남부터미널역"), Arrays.asList("남부터미널역", "양재역"), Arrays.asList("양재역", "매봉역"), + Arrays.asList("강남역", "양재역"), Arrays.asList("양재역", "양재시민의숲역") + ); + List> weightPairs = Arrays.asList( + Arrays.asList(2, 3), Arrays.asList(2, 3), + Arrays.asList(3, 2), Arrays.asList(6, 5), Arrays.asList(1, 1), + Arrays.asList(2, 8), Arrays.asList(10, 3) + ); + + for (String stationName : stations) { + Station station = Station.from(stationName); + StationRepository.addStation(station); + RouteRepository.addStation(station); + } + + for (int i = 0; i < stationPairs.size(); i++) { + Station start = StationRepository.getStation(stationPairs.get(i).get(0)); + Station end = StationRepository.getStation(stationPairs.get(i).get(1)); + int distance = weightPairs.get(i).get(0); + int time = weightPairs.get(i).get(1); + + RouteRepository.addRoute(start, end, distance, time); + } + } + + @ParameterizedTest + @MethodSource("minDistanceResultProvider") + void 최단거리_경로_테스트(Station startStation, Station endStation, int minDistance, int time, int size) { + Result result = RouteRepository.getMinDistanceRoute(startStation, endStation); + + assertThat(result.distance()).isEqualTo(minDistance); + assertThat(result.time()).isEqualTo(time); + assertThat(result.stations().size()).isEqualTo(size); + } + + static Stream minDistanceResultProvider() { + return Stream.of( + Arguments.of(Station.from("교대역"), Station.from("양재역"), 4, 11, 3), + Arguments.of(Station.from("교대역"), Station.from("강남역"), 2, 3, 2), + Arguments.of(Station.from("교대역"), Station.from("매봉역"), 5, 12, 4), + Arguments.of(Station.from("교대역"), Station.from("양재시민의숲역"), 14, 14, 4), + Arguments.of(Station.from("양재역"), Station.from("교대역"), 4, 11, 3), + Arguments.of(Station.from("양재역"), Station.from("역삼역"), 4, 11, 3), + Arguments.of(Station.from("양재역"), Station.from("남부터미널역"), 6, 5, 2), + Arguments.of(Station.from("매봉역"), Station.from("역삼역"), 5, 12, 4), + Arguments.of(Station.from("매봉역"), Station.from("강남역"), 3, 9, 3), + Arguments.of(Station.from("강남역"), Station.from("남부터미널역"), 5, 5, 3) + ); + } + + @ParameterizedTest + @MethodSource("minTimeResultProvider") + void 최소시간_경로_테스트(Station startStation, Station endStation, int minDistance, int time, int size) { + Result result = RouteRepository.getMinTimeRoute(startStation, endStation); + + assertThat(result.distance()).isEqualTo(minDistance); + assertThat(result.time()).isEqualTo(time); + assertThat(result.stations().size()).isEqualTo(size); + } + + static Stream minTimeResultProvider() { + return Stream.of( + Arguments.of(Station.from("교대역"), Station.from("양재역"), 9, 7, 3), + Arguments.of(Station.from("교대역"), Station.from("강남역"), 2, 3, 2), + Arguments.of(Station.from("교대역"), Station.from("매봉역"), 10, 8, 4), + Arguments.of(Station.from("교대역"), Station.from("양재시민의숲역"), 19, 10, 4), + Arguments.of(Station.from("양재역"), Station.from("교대역"), 9, 7, 3), + Arguments.of(Station.from("양재역"), Station.from("역삼역"), 4, 11, 3), + Arguments.of(Station.from("양재역"), Station.from("남부터미널역"), 6, 5, 2), + Arguments.of(Station.from("매봉역"), Station.from("역삼역"), 5, 12, 4), + Arguments.of(Station.from("매봉역"), Station.from("강남역"), 3, 9, 3), + Arguments.of(Station.from("강남역"), Station.from("남부터미널역"), 5, 5, 3) + ); + } + + @Test + void 두_역이_이어져_있지_않을때_오류() { + Station newFirstStation = Station.from("잠실역"); + Station newSecondStation = Station.from("송파역"); + + StationRepository.addStation(newFirstStation); + StationRepository.addStation(newSecondStation); + RouteRepository.addStation(newFirstStation); + RouteRepository.addStation(newSecondStation); + RouteRepository.addRoute(newFirstStation, newSecondStation, 3, 5); + + Station startStation = StationRepository.getStation("교대역"); + Station endStation = StationRepository.getStation("잠실역"); + + assertThatThrownBy(() -> RouteRepository.getMinDistanceRoute(startStation, endStation)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + assertThatThrownBy(() -> RouteRepository.getMinTimeRoute(startStation, endStation)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } +} diff --git a/src/test/java/subway/domain/StationRepositoryTest.java b/src/test/java/subway/domain/StationRepositoryTest.java new file mode 100644 index 000000000..5f4757734 --- /dev/null +++ b/src/test/java/subway/domain/StationRepositoryTest.java @@ -0,0 +1,42 @@ +package subway.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.BeforeEach; +import org.junit.jupiter.api.Test; +import subway.constant.ErrorMessage; + +class StationRepositoryTest { + + @BeforeEach + void setUp() { + StationRepository.deleteAll(); + } + + @Test + void 등록된_역_찾기_성공() { + List stations = List.of(Station.from("강남역"), Station.from("역삼역")); + + for (Station station : stations) { + StationRepository.addStation(station); + } + + Station findStation = Station.from("강남역"); + assertThat(StationRepository.getStation("강남역")).isEqualTo(findStation); + } + + @Test + void 등록된_역_찾기_실패() { + List stations = List.of(Station.from("강남역"), Station.from("교대역")); + + for (Station station : stations) { + StationRepository.addStation(station); + } + + assertThatThrownBy(() -> StationRepository.getStation("역삼역")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NO_EXIST_STATION.getErrorMessage()); + } +}