diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..5e1ac9ec --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.gradle/ +build/ +.git/ +.claude/ +*.md +src/test/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c23ee9fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM gradle:8-jdk21 AS build +WORKDIR /app +COPY . . +RUN gradle bootJar --no-daemon + +FROM eclipse-temurin:21-jre +WORKDIR /app +COPY --from=build /app/build/libs/*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/README.md b/README.md index 8318f676..3094dfed 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# spring-gift-test \ No newline at end of file +# spring-gift-test + +인수 테스트 중심으로 기능을 검증하며, PostgreSQL + Docker Compose 기반 실행을 지원합니다. + +## Acceptance Test Docs + +- 테스트 전략 문서: [TEST_STRATEGY.md](./TEST_STRATEGY.md) +- 테스트 실행 가이드: [TEST_RUN_GUIDE.md](./TEST_RUN_GUIDE.md) +- AI 활용 기록: [USING_AI.md](./USING_AI.md) + +## 빠른 실행 + +```bash +./gradlew test # Cucumber BDD 테스트 실행(H2) +./gradlew cucumberTest # Docker(App+PostgreSQL)로 Cucumber 실행 +./gradlew step1Test # 기존 RestAssured 인수 테스트 실행 +``` + +## 요구사항 3 실행(컨테이너) + +```bash +./gradlew dockerBuild +./gradlew dockerUp +curl http://localhost:28080/api/categories +./gradlew cucumberTest +./gradlew dockerDown +``` diff --git a/TEST_RUN_GUIDE.md b/TEST_RUN_GUIDE.md new file mode 100644 index 00000000..f25c54fc --- /dev/null +++ b/TEST_RUN_GUIDE.md @@ -0,0 +1,91 @@ +# Test 실행 가이드 + +## 개요 +현재 Gradle 설정 기준으로 테스트 태스크는 아래처럼 분리되어 있습니다. + +- `./gradlew test`: Cucumber BDD 테스트만 실행 +- `./gradlew cucumberTest`: Docker(App + PostgreSQL) 기반 Cucumber 테스트 실행 +- `./gradlew step1Test`: 기존 RestAssured 인수 테스트(1단계)만 실행 +- `./gradlew dockerBuild/dockerUp/dockerDown`: 애플리케이션 컨테이너 실행/종료 + +## 1) Cucumber 테스트 실행 +명령어: + +```bash +./gradlew test +``` + +실행 대상: + +- `gift.cucumber.CucumberTest` +- `src/test/resources/features/*.feature` 시나리오 +- step definitions: `src/test/java/gift/cucumber/steps/*.java` + +설정 근거: + +- `build.gradle`의 `test` 태스크 필터가 `gift.cucumber.CucumberTest`로 제한되어 있음 + +## 2) Docker(App + PostgreSQL) 기반 Cucumber 실행 +명령어: + +```bash +./gradlew cucumberTest +``` + +실행 흐름: + +- `dockerComposeUp` 실행 (`docker compose up -d --wait`) +- `gift.cucumber.CucumberTest` 실행 (프로파일: `test`) +- 완료 후 `dockerComposeDown` 실행 (`docker compose down -v`) + +설정 근거: + +- `build.gradle`의 `cucumberTest` 태스크 (`dependsOn dockerComposeUp`, `finalizedBy dockerComposeDown`) +- `src/test/resources/application-test.properties`의 PostgreSQL 설정 + +## 3) 컨테이너 실행(요구사항 3) +명령어: + +```bash +./gradlew dockerBuild +./gradlew dockerUp +curl http://localhost:28080/api/categories +./gradlew cucumberTest +./gradlew dockerDown +``` + +검증 포인트: + +- `docker compose ps`에서 `app`, `postgres`가 `healthy` +- `curl http://localhost:28080/api/categories` 응답 확인 +- `curl http://localhost:28080`가 404인 것은 정상(루트 매핑 없음) + +## 4) 1단계 인수 테스트 실행 +명령어: + +```bash +./gradlew step1Test +``` + +실행 대상: + +- `gift.CategoryAcceptanceTest` +- `gift.ProductAcceptanceTest` +- `gift.GiftAcceptanceTest` + +설정 근거: + +- `build.gradle`의 `step1Test` 태스크 `includeTestsMatching` 필터 + +## 5) 개별 테스트 클래스만 실행 +특정 클래스만 실행할 때: + +```bash +./gradlew test --tests gift.cucumber.CucumberTest +./gradlew step1Test --tests gift.GiftAcceptanceTest +``` + +## 주의사항 +- `./gradlew test`는 기본 실행이며 Docker 없이 실행됩니다. +- `./gradlew cucumberTest`는 `dockerUp` 후 테스트를 실행하고 종료 시 `dockerDown`을 수행합니다. +- Cucumber 시나리오는 `DatabaseCleanUp` 훅으로 시나리오마다 DB를 초기화합니다. diff --git a/build.gradle b/build.gradle index 7e021bef..536e9ba2 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,68 @@ dependencies { runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured' + + // cucumber 관련 의존성 + testImplementation 'io.cucumber:cucumber-java:7.22.0' + testImplementation 'io.cucumber:cucumber-spring:7.22.0' + testImplementation 'io.cucumber:cucumber-junit-platform-engine:7.22.0' + testImplementation 'org.junit.platform:junit-platform-suite' + + // postgresql 의존성 + runtimeOnly 'org.postgresql:postgresql' +} + +tasks.withType(Test).configureEach { + testLogging { + events 'passed', 'failed', 'skipped', 'standardOut', 'standardError' + showStandardStreams = true + exceptionFormat = 'full' + } } -tasks.named('test') { +// 1) ./gradlew test => cucumber 실행 +tasks.named('test', Test) { useJUnitPlatform() + description = 'Runs Cucumber BDD tests' + group = 'verification' + filter { + includeTestsMatching 'gift.cucumber.CucumberTest' + } } + +// 2) ./gradlew cucumberTest => Docker (App + PostgreSQL)로 Cucumber 실행 +tasks.register('cucumberTest', Test) { + description = 'Runs Cucumber tests with Docker' + group = 'verification' + useJUnitPlatform() + filter { + includeTestsMatching 'gift.cucumber.CucumberTest' + } + systemProperty 'spring.profiles.active', 'test' + dependsOn 'dockerUp' + finalizedBy 'dockerDown' +} + +// 3) ./gradlew step1Test => 기존 인수테스트 실행 +tasks.register('step1Test', Test) { + useJUnitPlatform() + description = 'Runs step1 acceptance tests' + group = 'verification' + filter { + includeTestsMatching 'gift.CategoryAcceptanceTest' + includeTestsMatching 'gift.ProductAcceptanceTest' + includeTestsMatching 'gift.GiftAcceptanceTest' + } +} + +tasks.register('dockerBuild', Exec) { + commandLine 'docker', 'compose', 'build' +} + +tasks.register('dockerUp', Exec) { + commandLine 'docker', 'compose', 'up', '-d', '--wait' +} + +tasks.register('dockerDown', Exec) { + commandLine 'docker', 'compose', 'down', '-v' +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..35f19063 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: gift_test + POSTGRES_USER: gift + POSTGRES_PASSWORD: gift + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U gift -d gift_test"] + interval: 3s + timeout: 5s + retries: 10 + + app: + build: . + ports: + - "28080:8080" + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/gift_test + SPRING_DATASOURCE_USERNAME: gift + SPRING_DATASOURCE_PASSWORD: gift + SPRING_JPA_HIBERNATE_DDL_AUTO: create + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:8080/api/categories || exit 1"] + interval: 5s + timeout: 5s + retries: 15 diff --git a/src/test/java/gift/CategoryAcceptanceTest.java b/src/test/java/gift/CategoryAcceptanceTest.java index 067600ea..96df65dc 100644 --- a/src/test/java/gift/CategoryAcceptanceTest.java +++ b/src/test/java/gift/CategoryAcceptanceTest.java @@ -55,7 +55,10 @@ void setUp() { * C2: 카테고리 목록을 조회한다. */ @Test - @Sql(scripts = "classpath:test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql( + scripts = {"classpath:cleanup.sql", "classpath:test-data.sql"}, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) void 카테고리_목록을_조회한다() { // when diff --git a/src/test/java/gift/ProductAcceptanceTest.java b/src/test/java/gift/ProductAcceptanceTest.java index d7dd51e1..a19428d2 100644 --- a/src/test/java/gift/ProductAcceptanceTest.java +++ b/src/test/java/gift/ProductAcceptanceTest.java @@ -65,7 +65,10 @@ void setUp() { * P2: 상품 목록을 조회한다. */ @Test - @Sql(scripts = "classpath:test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql( + scripts = {"classpath:cleanup.sql", "classpath:test-data.sql"}, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) void 상품_목록을_조회한다() { // when ExtractableResponse response = 상품_목록을_조회_요청한다(); @@ -111,4 +114,4 @@ void setUp() { .then().log().all() .extract(); } -} \ No newline at end of file +} diff --git a/src/test/java/gift/cucumber/CucumberSpringConfig.java b/src/test/java/gift/cucumber/CucumberSpringConfig.java new file mode 100644 index 00000000..07fc2feb --- /dev/null +++ b/src/test/java/gift/cucumber/CucumberSpringConfig.java @@ -0,0 +1,20 @@ +package gift.cucumber; + +import io.cucumber.spring.CucumberContextConfiguration; +import io.restassured.RestAssured; +import jakarta.annotation.PostConstruct; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; + +@CucumberContextConfiguration +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class CucumberSpringConfig { + + @LocalServerPort + int port; + + @PostConstruct + void setUp() { + RestAssured.port = port; + } +} diff --git a/src/test/java/gift/cucumber/CucumberTest.java b/src/test/java/gift/cucumber/CucumberTest.java new file mode 100644 index 00000000..f261eec7 --- /dev/null +++ b/src/test/java/gift/cucumber/CucumberTest.java @@ -0,0 +1,17 @@ +package gift.cucumber; + +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 static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; +import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME; + +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("features") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "gift.cucumber") +@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") +public class CucumberTest { +} \ No newline at end of file diff --git a/src/test/java/gift/cucumber/DatabaseCleanUp.java b/src/test/java/gift/cucumber/DatabaseCleanUp.java new file mode 100644 index 00000000..55285d1e --- /dev/null +++ b/src/test/java/gift/cucumber/DatabaseCleanUp.java @@ -0,0 +1,21 @@ +package gift.cucumber; + +import io.cucumber.java.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; + +public class DatabaseCleanUp { + + @Autowired + JdbcTemplate jdbcTemplate; + + @Before(order = 0) + public void cleanUp() { + jdbcTemplate.execute("TRUNCATE TABLE wish, option, product, member, category CASCADE"); + jdbcTemplate.execute("ALTER SEQUENCE category_id_seq RESTART WITH 1"); + jdbcTemplate.execute("ALTER SEQUENCE product_id_seq RESTART WITH 1"); + jdbcTemplate.execute("ALTER SEQUENCE option_id_seq RESTART WITH 1"); + jdbcTemplate.execute("ALTER SEQUENCE member_id_seq RESTART WITH 1"); + jdbcTemplate.execute("ALTER SEQUENCE wish_id_seq RESTART WITH 1"); + } +} diff --git a/src/test/java/gift/cucumber/steps/CategoryStep.java b/src/test/java/gift/cucumber/steps/CategoryStep.java new file mode 100644 index 00000000..47ca767b --- /dev/null +++ b/src/test/java/gift/cucumber/steps/CategoryStep.java @@ -0,0 +1,76 @@ +package gift.cucumber.steps; + +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CategoryStep { + + @Autowired + private SharedContext context; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Given("빈 데이터 상태다") + public void 빈_데이터_상태다() { + // DatabaseCleanUp @Before 훅에서 이미 처리됨 + } + + @Given("기본 테스트 데이터가 준비되어 있다") + public void 기본_테스트_데이터가_준비되어_있다() { + jdbcTemplate.execute("INSERT INTO category (id, name) VALUES (1, '간식')"); + jdbcTemplate.execute("INSERT INTO category (id, name) VALUES (2, '음료')"); + jdbcTemplate.execute("INSERT INTO product (id, name, price, image_url, category_id) VALUES (1, '초콜릿', 5000, 'http://img.com/choco.png', 1)"); + jdbcTemplate.execute("INSERT INTO product (id, name, price, image_url, category_id) VALUES (2, '커피', 3000, 'http://img.com/coffee.png', 2)"); + jdbcTemplate.execute("INSERT INTO option (id, name, quantity, product_id) VALUES (1, '초콜릿 기본', 10, 1)"); + jdbcTemplate.execute("INSERT INTO option (id, name, quantity, product_id) VALUES (2, '커피 기본', 1, 2)"); + jdbcTemplate.execute("INSERT INTO member (id, name, email) VALUES (1, '보내는사람', 'sender@test.com')"); + jdbcTemplate.execute("INSERT INTO member (id, name, email) VALUES (2, '받는사람', 'receiver@test.com')"); + } + + @When("카테고리 {string}를 생성한다") + public void 카테고리를_생성한다(String name) { + var response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(Map.of("name", name)) + .when().post("/api/categories") + .then().log().all().extract(); + context.setResponse(response); + } + + @When("카테고리 목록을 조회한다") + public void 카테고리_목록을_조회한다() { + var response = RestAssured.given().log().all() + .when().get("/api/categories") + .then().log().all().extract(); + context.setResponse(response); + } + + @Then("응답 코드는 {int}이다") + public void 응답_코드는(int statusCode) { + assertThat(context.getResponse().statusCode()).isEqualTo(statusCode); + } + + @And("응답 본문의 이름 목록에는 {string}가 포함된다") + public void 응답_본문의_이름_목록에는_포함된다(String name) { + List names = context.getResponse().jsonPath().getList("name", String.class); + assertThat(names).contains(name); + } + + @And("응답 본문의 이름 목록은 {string}, {string}이다") + public void 응답_본문의_이름_목록은(String name1, String name2) { + List names = context.getResponse().jsonPath().getList("name", String.class); + assertThat(names).contains(name1, name2); + } +} diff --git a/src/test/java/gift/cucumber/steps/GiftStep.java b/src/test/java/gift/cucumber/steps/GiftStep.java new file mode 100644 index 00000000..17b03318 --- /dev/null +++ b/src/test/java/gift/cucumber/steps/GiftStep.java @@ -0,0 +1,77 @@ +package gift.cucumber.steps; + +import io.cucumber.java.en.When; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Map; + +public class GiftStep { + + @Autowired + private SharedContext context; + + @When("보내는 회원이 받는 회원에게 초콜릿 옵션을 {int}개 선물한다") + public void 초콜릿_옵션_선물(int quantity) { + var response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header("Member-Id", 1L) + .body(Map.of( + "optionId", 1L, + "quantity", quantity, + "receiverId", 2L, + "message", "선물합니다" + )) + .when().post("/api/gifts") + .then().log().all().extract(); + context.setResponse(response); + } + + @When("보내는 회원이 받는 회원에게 커피 옵션을 {int}개 선물한다") + public void 커피_옵션_선물(int quantity) { + var response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header("Member-Id", 1L) + .body(Map.of( + "optionId", 2L, + "quantity", quantity, + "receiverId", 2L, + "message", "선물합니다" + )) + .when().post("/api/gifts") + .then().log().all().extract(); + context.setResponse(response); + } + + @When("보내는 회원이 받는 회원에게 존재하지 않는 옵션 {long}으로 {int}개 선물한다") + public void 존재하지_않는_옵션_선물(long optionId, int quantity) { + var response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header("Member-Id", 1L) + .body(Map.of( + "optionId", optionId, + "quantity", quantity, + "receiverId", 2L, + "message", "선물합니다" + )) + .when().post("/api/gifts") + .then().log().all().extract(); + context.setResponse(response); + } + + @When("Member-Id 헤더 없이 선물한다") + public void 헤더_없이_선물한다() { + var response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(Map.of( + "optionId", 1L, + "quantity", 1, + "receiverId", 2L, + "message", "선물합니다" + )) + .when().post("/api/gifts") + .then().log().all().extract(); + context.setResponse(response); + } +} diff --git a/src/test/java/gift/cucumber/steps/ProductStep.java b/src/test/java/gift/cucumber/steps/ProductStep.java new file mode 100644 index 00000000..90f31c11 --- /dev/null +++ b/src/test/java/gift/cucumber/steps/ProductStep.java @@ -0,0 +1,69 @@ +package gift.cucumber.steps; + +import io.cucumber.java.en.And; +import io.cucumber.java.en.When; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProductStep { + + @Autowired + private SharedContext context; + + @When("카테고리 {string}를 생성한 뒤 상품 {string}, 가격 {int}, 이미지 {string}을 생성한다") + public void 카테고리_생성_뒤_상품_생성(String categoryName, String productName, int price, String imageUrl) { + var categoryResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(Map.of("name", categoryName)) + .when().post("/api/categories") + .then().log().all().extract(); + long categoryId = categoryResponse.jsonPath().getLong("id"); + + var response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(Map.of( + "name", productName, + "price", price, + "imageUrl", imageUrl, + "categoryId", categoryId + )) + .when().post("/api/products") + .then().log().all().extract(); + context.setResponse(response); + } + + @When("상품 목록을 조회한다") + public void 상품_목록을_조회한다() { + var response = RestAssured.given().log().all() + .when().get("/api/products") + .then().log().all().extract(); + context.setResponse(response); + } + + @And("응답 본문 상품 이름은 {string}이다") + public void 응답_본문_상품_이름(String name) { + assertThat(context.getResponse().jsonPath().getString("name")).isEqualTo(name); + } + + @And("응답 본문 상품 가격은 {int}이다") + public void 응답_본문_상품_가격(int price) { + assertThat(context.getResponse().jsonPath().getInt("price")).isEqualTo(price); + } + + @And("응답 본문 상품 카테고리 이름은 {string}이다") + public void 응답_본문_상품_카테고리_이름(String categoryName) { + assertThat(context.getResponse().jsonPath().getString("category.name")).isEqualTo(categoryName); + } + + @And("응답 본문의 카테고리 이름 목록은 {string}, {string}이다") + public void 응답_본문의_카테고리_이름_목록은(String name1, String name2) { + List names = context.getResponse().jsonPath().getList("category.name", String.class); + assertThat(names).contains(name1, name2); + } +} diff --git a/src/test/java/gift/cucumber/steps/SharedContext.java b/src/test/java/gift/cucumber/steps/SharedContext.java new file mode 100644 index 00000000..1d8fb793 --- /dev/null +++ b/src/test/java/gift/cucumber/steps/SharedContext.java @@ -0,0 +1,30 @@ +package gift.cucumber.steps; + +import io.cucumber.spring.ScenarioScope; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.stereotype.Component; + +@Component +@ScenarioScope +public class SharedContext { + + private ExtractableResponse response; + private long savedCategoryId; + + public ExtractableResponse getResponse() { + return response; + } + + public void setResponse(ExtractableResponse response) { + this.response = response; + } + + public long getSavedCategoryId() { + return savedCategoryId; + } + + public void setSavedCategoryId(long savedCategoryId) { + this.savedCategoryId = savedCategoryId; + } +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 00000000..0d206bf5 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,10 @@ +spring.datasource.url=jdbc:postgresql://localhost:5432/gift_test +spring.datasource.username=gift +spring.datasource.password=gift +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.hibernate.ddl-auto=none +spring.jpa.open-in-view=false +kakao.message.token=ACCESS_TOKEN +kakao.message.url=https://kapi.kakao.com/v1/api/talk +kakao.social.token=ACCESS_TOKEN +kakao.social.url=https://kapi.kakao.com/v1/api/talk diff --git a/src/test/resources/features/category.feature b/src/test/resources/features/category.feature new file mode 100644 index 00000000..cd79a3d7 --- /dev/null +++ b/src/test/resources/features/category.feature @@ -0,0 +1,16 @@ +# language: ko +기능: 카테고리 관리 + + 시나리오: 카테고리를 생성하고 목록에서 확인한다 + Given 빈 데이터 상태다 + When 카테고리 "디저트"를 생성한다 + Then 응답 코드는 200이다 + When 카테고리 목록을 조회한다 + Then 응답 코드는 200이다 + And 응답 본문의 이름 목록에는 "디저트"가 포함된다 + + 시나리오: 카테고리 목록을 조회한다 + Given 기본 테스트 데이터가 준비되어 있다 + When 카테고리 목록을 조회한다 + Then 응답 코드는 200이다 + And 응답 본문의 이름 목록은 "간식", "음료"이다 diff --git a/src/test/resources/features/gift.feature b/src/test/resources/features/gift.feature new file mode 100644 index 00000000..2d717981 --- /dev/null +++ b/src/test/resources/features/gift.feature @@ -0,0 +1,29 @@ +# language: ko +기능: 선물하기 + + 시나리오: 재고가 충분할 때 선물하기에 성공한다 + Given 기본 테스트 데이터가 준비되어 있다 + When 보내는 회원이 받는 회원에게 초콜릿 옵션을 1개 선물한다 + Then 응답 코드는 200이다 + + 시나리오: 선물을 보내면 재고가 감소한다 + Given 기본 테스트 데이터가 준비되어 있다 + When 보내는 회원이 받는 회원에게 초콜릿 옵션을 10개 선물한다 + Then 응답 코드는 200이다 + When 보내는 회원이 받는 회원에게 초콜릿 옵션을 1개 선물한다 + Then 응답 코드는 500이다 + + 시나리오: 재고보다 많은 수량을 선물하면 실패한다 + Given 기본 테스트 데이터가 준비되어 있다 + When 보내는 회원이 받는 회원에게 커피 옵션을 2개 선물한다 + Then 응답 코드는 500이다 + + 시나리오: 존재하지 않는 옵션으로 선물하면 실패한다 + Given 기본 테스트 데이터가 준비되어 있다 + When 보내는 회원이 받는 회원에게 존재하지 않는 옵션 9999으로 1개 선물한다 + Then 응답 코드는 500이다 + + 시나리오: Member-Id 헤더 없이 선물하면 실패한다 + Given 기본 테스트 데이터가 준비되어 있다 + When Member-Id 헤더 없이 선물한다 + Then 응답 코드는 400이다 diff --git a/src/test/resources/features/product.feature b/src/test/resources/features/product.feature new file mode 100644 index 00000000..e4a6ff28 --- /dev/null +++ b/src/test/resources/features/product.feature @@ -0,0 +1,20 @@ +# language: ko +기능: 상품 관리 + + 시나리오: 상품을 생성하고 목록에서 확인한다 + Given 빈 데이터 상태다 + When 카테고리 "간식"를 생성한 뒤 상품 "초콜릿", 가격 5000, 이미지 "http://img.com/choco.png"을 생성한다 + Then 응답 코드는 200이다 + And 응답 본문 상품 이름은 "초콜릿"이다 + And 응답 본문 상품 가격은 5000이다 + And 응답 본문 상품 카테고리 이름은 "간식"이다 + When 상품 목록을 조회한다 + Then 응답 코드는 200이다 + And 응답 본문의 이름 목록에는 "초콜릿"가 포함된다 + + 시나리오: 상품 목록을 조회한다 + Given 기본 테스트 데이터가 준비되어 있다 + When 상품 목록을 조회한다 + Then 응답 코드는 200이다 + And 응답 본문의 이름 목록은 "초콜릿", "커피"이다 + And 응답 본문의 카테고리 이름 목록은 "간식", "음료"이다