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());
+ }
+}