From 9eae40a27526b4aad143e4bc9378ae7c44204303 Mon Sep 17 00:00:00 2001 From: yongju Date: Mon, 22 Jan 2024 09:40:06 +0900 Subject: [PATCH 1/6] test: cucumber station step --- build.gradle | 12 ++++- .../subway/cucumber/CucumberTest.java | 24 ++++++++++ .../subway/cucumber/steps/StationStepDef.java | 45 +++++++++++++++++++ src/test/resources/station.feature | 6 +++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/test/java/nextstep/subway/cucumber/CucumberTest.java create mode 100644 src/test/java/nextstep/subway/cucumber/steps/StationStepDef.java create mode 100644 src/test/resources/station.feature diff --git a/build.gradle b/build.gradle index 26314f8f6..b569f1af5 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,16 @@ dependencies { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } + // cucumber + testImplementation("io.cucumber:cucumber-java:7.14.0") + testImplementation("io.cucumber:cucumber-java8:7.14.0") + testImplementation("io.cucumber:cucumber-spring:7.14.0") + testImplementation("io.cucumber:cucumber-junit-platform-engine:7.14.0") + testImplementation("org.junit.platform:junit-platform-suite:1.10.0") + testImplementation("org.junit.platform:junit-platform-suite-api:1.10.0") + testImplementation("org.junit.platform:junit-platform-commons:1.10.0") + testImplementation("org.junit.platform:junit-platform-engine:1.10.0") + runtimeOnly 'com.h2database:h2' } @@ -81,4 +91,4 @@ task copyDocument(type: Copy) { from file("build/docs/asciidoc") into file("src/main/resources/static/docs") -} \ No newline at end of file +} diff --git a/src/test/java/nextstep/subway/cucumber/CucumberTest.java b/src/test/java/nextstep/subway/cucumber/CucumberTest.java new file mode 100644 index 000000000..2116ea8bf --- /dev/null +++ b/src/test/java/nextstep/subway/cucumber/CucumberTest.java @@ -0,0 +1,24 @@ +package nextstep.subway.cucumber; + +import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("features") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "nextstep") +@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") +@CucumberContextConfiguration +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class CucumberTest { + +} diff --git a/src/test/java/nextstep/subway/cucumber/steps/StationStepDef.java b/src/test/java/nextstep/subway/cucumber/steps/StationStepDef.java new file mode 100644 index 000000000..33ad7a98b --- /dev/null +++ b/src/test/java/nextstep/subway/cucumber/steps/StationStepDef.java @@ -0,0 +1,45 @@ +package nextstep.subway.cucumber.steps; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.java8.En; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class StationStepDef implements En { + + ExtractableResponse response; + + public StationStepDef() { + When("지하철역을 생성하면", () -> { + Map params = new HashMap<>(); + params.put("name", "강남역"); + response = RestAssured.given().log().all() + .body(params) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when() + .post("/stations") + .then().log().all() + .extract(); + }); + + Then("지하철역이 생성된다", () -> { + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + }); + + Then("지하철역 목록 조회 시 생성한 역을 찾을 수 있다", () -> { + List stationNames = + RestAssured.given().log().all() + .when().get("/stations") + .then().log().all() + .extract().jsonPath().getList("name", String.class); + assertThat(stationNames).containsAnyOf("강남역"); + }); + } +} diff --git a/src/test/resources/station.feature b/src/test/resources/station.feature new file mode 100644 index 000000000..4e27af5a8 --- /dev/null +++ b/src/test/resources/station.feature @@ -0,0 +1,6 @@ +Feature: 지하철역 관련 기능 + + Scenario: 지하철역을 생성한다. + When 지하철역을 생성하면 + Then 지하철역이 생성된다 + And 지하철역 목록 조회 시 생성한 역을 찾을 수 있다 From 38d92ddd40323fdcf3622def59c2cceb9138c547 Mon Sep 17 00:00:00 2001 From: yongju Date: Mon, 22 Jan 2024 09:40:53 +0900 Subject: [PATCH 2/6] =?UTF-8?q?test:=20=EA=B2=BD=EB=A1=9C=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/path.feature | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/resources/path.feature diff --git a/src/test/resources/path.feature b/src/test/resources/path.feature new file mode 100644 index 000000000..cccae2a57 --- /dev/null +++ b/src/test/resources/path.feature @@ -0,0 +1,29 @@ +Feature: 경로 조회 기능 + + Scenario: 두 역의 최단 경로 조회 +# 교대역 --- *2호선*(10) --- 강남역 +# | | +# *3호선*(2) *신분당선* (10) +# | | +# 남부터미널역 --- *3호선*(3) --- 양재 + Given 지하철역을 생성하고 + | 교대역 | + | 강남역 | + | 양재역 | + | 남부터미널역 | + And 지하철 노선을 생성하고 + | name | color | upStation | downStation | distance | duration | extraCharge | + | 2호선 | green | 교대역 | 강남역 | 10 | 10 | 300 | + | 신분당선 | red | 강남역 | 양재역 | 10 | 10 | 0 | + | 3호선 | orange | 교대역 | 남부터미널역 | 2 | 2 | 1000 | + And '3호선'에 구간을 생성한다 + | upStation | 남부터미널역 | + | downStation | 양재역 | + | distance | 3 | + | duration | 3 | + When '교대역'에서 '양재역'까지 최단 경로 조회 요청하면 + Then 최단 경로 응답와 총 거리, 소요 시간, 요금정보를 조회한다 + | stations | 교대역,남부터미널역,양재역 | + | distance | 5 | + | duration | 5 | + | fare | 1250 | From 01997f2881346f20c01b9d6ff61288b6cdb39828 Mon Sep 17 00:00:00 2001 From: yongju Date: Mon, 22 Jan 2024 09:41:43 +0900 Subject: [PATCH 3/6] =?UTF-8?q?test:=20=EA=B3=B5=EC=9C=A0=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/cucumber/AcceptanceContext.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/test/java/nextstep/subway/cucumber/AcceptanceContext.java diff --git a/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java b/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java new file mode 100644 index 000000000..6f192b46b --- /dev/null +++ b/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java @@ -0,0 +1,14 @@ +package nextstep.subway.cucumber; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +@Profile("test") +@Service +public class AcceptanceContext { + + private Map store = new HashMap<>(); + +} From 0888f04588a9c594ad5e574d966b44ec7121a58f Mon Sep 17 00:00:00 2001 From: yongju Date: Mon, 22 Jan 2024 09:42:21 +0900 Subject: [PATCH 4/6] =?UTF-8?q?test:=20before=20book=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/cucumber/BeforeDatabaseCleanup.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/nextstep/subway/cucumber/BeforeDatabaseCleanup.java diff --git a/src/test/java/nextstep/subway/cucumber/BeforeDatabaseCleanup.java b/src/test/java/nextstep/subway/cucumber/BeforeDatabaseCleanup.java new file mode 100644 index 000000000..44f05fda8 --- /dev/null +++ b/src/test/java/nextstep/subway/cucumber/BeforeDatabaseCleanup.java @@ -0,0 +1,17 @@ +package nextstep.subway.cucumber; + +import io.cucumber.java8.En; +import nextstep.subway.utils.DatabaseCleanup; +import org.springframework.beans.factory.annotation.Autowired; + +public class BeforeDatabaseCleanup implements En { + + @Autowired + private DatabaseCleanup databaseCleanup; + + public BeforeDatabaseCleanup() { + Before(() -> { + databaseCleanup.execute(); + }); + } +} From 07aae9d7cf80c71cbb342f0494c422211d5b1311 Mon Sep 17 00:00:00 2001 From: yongju Date: Mon, 22 Jan 2024 09:42:37 +0900 Subject: [PATCH 5/6] =?UTF-8?q?test:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/subway/acceptance/LineSteps.java | 4 + .../subway/cucumber/AcceptanceContext.java | 11 ++ .../subway/cucumber/steps/PathStepDef.java | 101 ++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java diff --git a/src/test/java/nextstep/subway/acceptance/LineSteps.java b/src/test/java/nextstep/subway/acceptance/LineSteps.java index 70aae7590..083e9f23e 100644 --- a/src/test/java/nextstep/subway/acceptance/LineSteps.java +++ b/src/test/java/nextstep/subway/acceptance/LineSteps.java @@ -81,6 +81,10 @@ public class LineSteps { assertThat(response.jsonPath().getList("stations.id", Long.class)).containsExactly(idsOfStations); } + public static void 경로_역_목록_조회됨(ExtractableResponse response, String[] stationNames) { + assertThat(response.jsonPath().getList("stations.name", String.class)).containsExactly(stationNames); + } + public static void 경로_전체_거리_조회됨(ExtractableResponse response, int distance) { assertThat(response.jsonPath().getInt("distance")).isEqualTo(distance); } diff --git a/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java b/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java index 6f192b46b..0e6617426 100644 --- a/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java +++ b/src/test/java/nextstep/subway/cucumber/AcceptanceContext.java @@ -11,4 +11,15 @@ public class AcceptanceContext { private Map store = new HashMap<>(); + public void put(final String name, final String id) { + store.putIfAbsent(name, id); + } + + public String get(final String name) { + return store.get(name); + } + + public Long getLongValue(final String name) { + return Long.valueOf(store.get(name)); + } } diff --git a/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java b/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java new file mode 100644 index 000000000..d0b9dd1a1 --- /dev/null +++ b/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java @@ -0,0 +1,101 @@ +package nextstep.subway.cucumber.steps; + +import static nextstep.subway.acceptance.LineSteps.경로_역_목록_조회됨; +import static nextstep.subway.acceptance.LineSteps.경로_전체_거리_조회됨; +import static nextstep.subway.acceptance.LineSteps.경로_전체_시간_조회됨; +import static nextstep.subway.acceptance.LineSteps.지하철_노선에_지하철_구간_생성_요청; +import static nextstep.subway.acceptance.PathAcceptanceSteps.경로_전체_요금_조회됨; +import static nextstep.subway.acceptance.PathAcceptanceSteps.두_역의_경로_조회를_요청; +import static nextstep.subway.acceptance.StationSteps.지하철역_생성_요청; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java8.En; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import nextstep.subway.acceptance.LineSteps; +import nextstep.subway.cucumber.AcceptanceContext; +import nextstep.subway.cucumber.CucumberTest; +import nextstep.subway.domain.PathType; +import org.springframework.beans.factory.annotation.Autowired; + +public class PathStepDef extends CucumberTest implements En { + + @Autowired + private AcceptanceContext context; + private ExtractableResponse response; + + public PathStepDef() { + Given("^지하철역을 생성하고$", (final DataTable dataTable) -> { + final List stationNames = dataTable.asList(); + for (final String stationName : stationNames) { + final Long id = 지하철역_생성_요청(stationName).jsonPath().getLong("id"); + + context.put(stationName, id + ""); + } + }); + + And("^지하철 노선을 생성하고$", (final DataTable dataTable) -> { + final List> lines = dataTable.asMaps(); + for (final Map line : lines) { + final Map lineParam = createLineParam(line); + final Long id = LineSteps.지하철_노선_생성_요청(lineParam).jsonPath().getLong("id"); + + context.put(line.get("name"), id + ""); + } + }); + + And("{string}에 구간을 생성한다", (final String lineName, final DataTable dataTable) -> { + final Long lineId = context.getLongValue(lineName); + final Map section = dataTable.asMap(); + final Map sectionParam = createSectionParam(section); + + 지하철_노선에_지하철_구간_생성_요청(lineId, sectionParam); + }); + + When("{string}에서 {string}까지 최단 경로 조회 요청하면", (final String source, final String target) -> { + final Long sourceId = context.getLongValue(source); + final Long targetId = context.getLongValue(target); + + response = 두_역의_경로_조회를_요청(given(), sourceId, targetId, PathType.DISTANCE); + }); + + Then("^최단 경로 응답와 총 거리, 소요 시간, 요금정보를 조회한다$", (final DataTable dataTable) -> { + final Map pathResponse = dataTable.asMap(); + + 경로_역_목록_조회됨(response, pathResponse.get("stations").split(",")); + 경로_전체_거리_조회됨(response, Integer.parseInt(pathResponse.get("distance"))); + 경로_전체_시간_조회됨(response, Integer.parseInt(pathResponse.get("duration"))); + 경로_전체_요금_조회됨(response, Integer.parseInt(pathResponse.get("fare"))); + }); + } + + private Map createLineParam(final Map line) { + final Map lineParam = new HashMap<>(); + lineParam.put("name", line.get("name")); + lineParam.put("color", line.get("color")); + lineParam.put("upStationId", context.get(line.get("upStation"))); + lineParam.put("downStationId", context.get(line.get("downStation"))); + lineParam.put("distance", line.get("distance")); + lineParam.put("duration", line.get("duration")); + lineParam.put("extraCharge", line.get("extraCharge")); + return lineParam; + } + + private Map createSectionParam(final Map section) { + final Map sectionParam = new HashMap<>(); + sectionParam.put("upStationId", context.get(section.get("upStation"))); + sectionParam.put("downStationId", context.get(section.get("downStation"))); + sectionParam.put("distance", section.get("distance")); + sectionParam.put("duration", section.get("duration")); + return sectionParam; + } + + private RequestSpecification given() { + return RestAssured.given().log().all(); + } +} From b1651d0d8da0f0c61d067dafafae8e4613f07235 Mon Sep 17 00:00:00 2001 From: yongju Date: Mon, 22 Jan 2024 10:39:16 +0900 Subject: [PATCH 6/6] test: apply DataTableType --- .../subway/cucumber/steps/PathStepDef.java | 20 ++++++--- .../cucumber/steps/dto/PathStepResponse.java | 44 +++++++++++++++++++ src/test/resources/path.feature | 6 +-- 3 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/test/java/nextstep/subway/cucumber/steps/dto/PathStepResponse.java diff --git a/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java b/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java index d0b9dd1a1..2fd7ec447 100644 --- a/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java +++ b/src/test/java/nextstep/subway/cucumber/steps/PathStepDef.java @@ -20,6 +20,7 @@ import nextstep.subway.acceptance.LineSteps; import nextstep.subway.cucumber.AcceptanceContext; import nextstep.subway.cucumber.CucumberTest; +import nextstep.subway.cucumber.steps.dto.PathStepResponse; import nextstep.subway.domain.PathType; import org.springframework.beans.factory.annotation.Autowired; @@ -64,13 +65,18 @@ public PathStepDef() { response = 두_역의_경로_조회를_요청(given(), sourceId, targetId, PathType.DISTANCE); }); - Then("^최단 경로 응답와 총 거리, 소요 시간, 요금정보를 조회한다$", (final DataTable dataTable) -> { - final Map pathResponse = dataTable.asMap(); - - 경로_역_목록_조회됨(response, pathResponse.get("stations").split(",")); - 경로_전체_거리_조회됨(response, Integer.parseInt(pathResponse.get("distance"))); - 경로_전체_시간_조회됨(response, Integer.parseInt(pathResponse.get("duration"))); - 경로_전체_요금_조회됨(response, Integer.parseInt(pathResponse.get("fare"))); + DataTableType((final Map pathStepResponse) -> new PathStepResponse( + pathStepResponse.get("stations").split(","), + Integer.parseInt(pathStepResponse.get("distance")), + Integer.parseInt(pathStepResponse.get("duration")), + Integer.parseInt(pathStepResponse.get("fare")) + )); + + Then("^최단 경로 응답와 총 거리, 소요 시간, 요금정보를 조회한다$", (final PathStepResponse pathStepResponse) -> { + 경로_역_목록_조회됨(response, pathStepResponse.getStations()); + 경로_전체_거리_조회됨(response, pathStepResponse.getDistance()); + 경로_전체_시간_조회됨(response, pathStepResponse.getDuration()); + 경로_전체_요금_조회됨(response, pathStepResponse.getFare()); }); } diff --git a/src/test/java/nextstep/subway/cucumber/steps/dto/PathStepResponse.java b/src/test/java/nextstep/subway/cucumber/steps/dto/PathStepResponse.java new file mode 100644 index 000000000..e876e9000 --- /dev/null +++ b/src/test/java/nextstep/subway/cucumber/steps/dto/PathStepResponse.java @@ -0,0 +1,44 @@ +package nextstep.subway.cucumber.steps.dto; + +import java.util.Arrays; + +public class PathStepResponse { + + private final String[] stations; + private final int distance; + private final int duration; + private final int fare; + + public PathStepResponse(final String[] stations, final int distance, final int duration, final int fare) { + this.stations = stations; + this.distance = distance; + this.duration = duration; + this.fare = fare; + } + + public String[] getStations() { + return stations; + } + + public int getDistance() { + return distance; + } + + public int getDuration() { + return duration; + } + + public int getFare() { + return fare; + } + + @Override + public String toString() { + return "PathStepResponse{" + + "stations=" + Arrays.toString(stations) + + ", distance=" + distance + + ", duration=" + duration + + ", fare=" + fare + + '}'; + } +} diff --git a/src/test/resources/path.feature b/src/test/resources/path.feature index cccae2a57..5d306e625 100644 --- a/src/test/resources/path.feature +++ b/src/test/resources/path.feature @@ -23,7 +23,5 @@ Feature: 경로 조회 기능 | duration | 3 | When '교대역'에서 '양재역'까지 최단 경로 조회 요청하면 Then 최단 경로 응답와 총 거리, 소요 시간, 요금정보를 조회한다 - | stations | 교대역,남부터미널역,양재역 | - | distance | 5 | - | duration | 5 | - | fare | 1250 | + | stations | distance | duration | fare | + | 교대역,남부터미널역,양재역 | 5 | 5 | 1250 |