-
Notifications
You must be signed in to change notification settings - Fork 40
[Step2] daniel.hoon 미션 제출합니다. #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kdaehun00
wants to merge
25
commits into
next-step:kdaehun00
Choose a base branch
from
kdaehun00:step-2
base: kdaehun00
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
7de8693
build(gradle): add rest-assured test dependency
kakao-daniel-hoon b54759a
test(acceptance): add base test class and configuration
kakao-daniel-hoon 3ba1ab1
test(category): add category acceptance test
kakao-daniel-hoon e38eb03
test(product): add product acceptance test
kakao-daniel-hoon 0ef8b08
test(gift): add gift acceptance test
kakao-daniel-hoon 48d8b5e
docs: add test strategy and AI usage documentation
kakao-daniel-hoon 1d81b47
chore(skills): add write-acceptance-test skill
kakao-daniel-hoon 5835b1c
docs(claude): add CLAUDE.md following best practices
kakao-daniel-hoon e7d84b9
docs(ai-usage): update with CLAUDE.md and skill usage details
kakao-daniel-hoon e10f543
docs(ai-usage): clarify CLAUDE.md analysis context
kakao-daniel-hoon 0900e91
fix(controller): add @RequestBody to Product/Category controllers
kakao-daniel-hoon 6f7fa99
test: separate ambiguous create/retrieve acceptance tests
kakao-daniel-hoon aced1c3
docs(skills): add test naming clarity guidelines
kakao-daniel-hoon 1074dfc
test: add response body verification to creation tests
kakao-daniel-hoon 163b30d
feat(error): add custom exception infrastructure
kakao-daniel-hoon e2a0f08
refactor: replace generic exceptions with BusinessException
kakao-daniel-hoon 8bcef60
test(gift): add failure case tests for invalid Member-Id
kakao-daniel-hoon 42c5ccf
refactor(error): split BusinessException into domain exceptions
kakao-daniel-hoon e8c3ba9
refactor(error): add invalid body test, remove unused fallback handler
kakao-daniel-hoon 48315bb
test: add error code verification to failure tests
kakao-daniel-hoon ef8602a
docs(skills): add skills for step-2 test infrastructure
kakao-daniel-hoon 3ebba44
test(cucumber): replace JUnit acceptance tests with Cucumber BDD
kakao-daniel-hoon 7522784
infra(docker): replace H2 with PostgreSQL via Docker Compose
kakao-daniel-hoon 9c2f08c
infra(docker): containerize Spring Boot app with Docker Compose
kakao-daniel-hoon 10d4cd3
Merge branch 'kdaehun00' into step-2
kdaehun00 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| --- | ||
| name: dockerize-app | ||
| description: Spring Boot 애플리케이션을 Docker 컨테이너로 실행하여 프로덕션과 동일한 환경에서 End-to-End 테스트를 수행한다. | ||
| disable-model-invocation: true | ||
| argument-hint: (인자 없음) | ||
| --- | ||
|
|
||
| Spring Boot 애플리케이션을 컨테이너화한다. 아래 단계를 순서대로 수행한다. | ||
|
|
||
| ## 전제 조건 | ||
|
|
||
| - 요구사항 2(PostgreSQL + Docker Compose)가 완료된 상태여야 한다 | ||
| - docker-compose.yml에 PostgreSQL 서비스가 구성되어 있어야 한다 | ||
|
|
||
| ## 1단계: .dockerignore 작성 | ||
|
|
||
| 프로젝트 루트에 `.dockerignore`를 생성하여 불필요한 파일을 제외한다. | ||
|
|
||
| ``` | ||
| .git | ||
| .gradle | ||
| .claude | ||
| .idea | ||
| build | ||
| *.md | ||
| ``` | ||
|
|
||
| ## 2단계: Dockerfile 작성 (Multi-stage build) | ||
|
|
||
| 프로젝트 루트에 `Dockerfile`을 생성한다. | ||
|
|
||
| ```dockerfile | ||
| # Builder stage: Gradle로 빌드 | ||
| FROM eclipse-temurin:21-jdk-alpine AS builder | ||
| WORKDIR /app | ||
| COPY gradle/ gradle/ | ||
| COPY gradlew build.gradle settings.gradle ./ | ||
| RUN ./gradlew dependencies --no-daemon || true | ||
| COPY src/ src/ | ||
| RUN ./gradlew bootJar --no-daemon -x test | ||
|
|
||
| # Runtime stage: 경량 JRE로 실행 | ||
| FROM eclipse-temurin:21-jre-alpine | ||
| WORKDIR /app | ||
| COPY --from=builder /app/build/libs/*.jar app.jar | ||
| ENTRYPOINT ["java", "-jar", "app.jar"] | ||
| ``` | ||
|
|
||
| Multi-stage build를 사용하는 이유: | ||
| - Builder stage에는 JDK, Gradle, 소스코드가 포함되지만 최종 이미지에는 JRE + JAR만 포함 | ||
| - 이미지 크기를 최소화하고 보안 표면을 줄인다 | ||
|
|
||
| ## 3단계: docker-compose.yml에 앱 서비스 추가 | ||
|
|
||
| 기존 docker-compose.yml에 애플리케이션 서비스를 추가한다. | ||
|
|
||
| ```yaml | ||
| services: | ||
| postgres: | ||
| # ... 기존 설정 유지 | ||
|
|
||
| app: | ||
| build: . | ||
| ports: | ||
| - "28080:8080" | ||
| environment: | ||
| SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/gift_test | ||
| SPRING_DATASOURCE_USERNAME: test | ||
| SPRING_DATASOURCE_PASSWORD: test | ||
| depends_on: | ||
| postgres: | ||
| condition: service_healthy | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "wget -q --spider http://localhost:8080/actuator/health || exit 1"] | ||
| interval: 5s | ||
| timeout: 5s | ||
| retries: 10 | ||
| start_period: 30s | ||
| ``` | ||
|
|
||
| 네트워크 구조: | ||
| - 컨테이너 내부: `app` → `postgres:5432` (Docker service name이 hostname) | ||
| - 호스트(테스트): `localhost:28080` → `app`, `localhost:5432` → `postgres` | ||
|
|
||
| ## 4단계: Gradle 태스크 업데이트 | ||
|
|
||
| ```groovy | ||
| 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' | ||
| } | ||
| ``` | ||
|
|
||
| ## 5단계: 테스트 설정 변경 | ||
|
|
||
| Docker 컨테이너의 앱에 HTTP 요청을 보내므로 embedded 서버가 필요 없다. | ||
|
|
||
| ```java | ||
| @CucumberContextConfiguration | ||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) | ||
| @ActiveProfiles("cucumber") | ||
| public class CucumberSpringConfiguration { | ||
|
|
||
| @Before | ||
| public void setUp() { | ||
| RestAssured.baseURI = "http://localhost"; | ||
| RestAssured.port = 28080; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - `webEnvironment = NONE`: 테스트 프로세스 내에서 Spring Boot 서버를 띄우지 않는다 | ||
| - 테스트는 호스트에서 실행되고, Docker 컨테이너의 앱(28080)에 HTTP 요청을 보낸다 | ||
|
|
||
| ## 6단계: DB 직접 접근 (cleanup 용도) | ||
|
|
||
| 테스트에서 DB 초기화를 위해 JdbcTemplate으로 PostgreSQL에 직접 접근한다. | ||
|
|
||
| application-cucumber.properties에서 DB 연결 설정을 유지한다: | ||
| ```properties | ||
| spring.datasource.url=jdbc:postgresql://localhost:5432/gift_test | ||
| ``` | ||
|
|
||
| - 테스트(호스트) → `localhost:5432` → PostgreSQL 컨테이너 | ||
| - 앱(컨테이너) → `postgres:5432` → PostgreSQL 컨테이너 | ||
|
|
||
| 같은 DB를 다른 hostname으로 접근하는 구조이다. | ||
|
|
||
| ## 7단계: Spring Boot Actuator 추가 (healthcheck 용) | ||
|
|
||
| healthcheck에 actuator health endpoint를 사용한다면 의존성을 추가한다. | ||
|
|
||
| ```groovy | ||
| implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
| ``` | ||
|
|
||
| 또는 healthcheck를 curl/wget 대신 TCP 체크로 대체할 수도 있다. | ||
|
|
||
| ## 8단계: 검증 | ||
|
|
||
| ```bash | ||
| ./gradlew dockerBuild | ||
| ./gradlew dockerUp | ||
| curl http://localhost:28080 # 앱 응답 확인 | ||
| ./gradlew cucumberTest # Docker 환경에서 테스트 | ||
| ./gradlew dockerDown | ||
| ``` | ||
|
|
||
| - `docker ps`로 컨테이너 상태를 확인한다 | ||
| - `docker logs <container>`로 앱/DB 로그를 확인한다 | ||
|
|
||
| ## 트러블슈팅 | ||
|
|
||
| | 문제 | 해결 | | ||
| |------|------| | ||
| | 앱이 DB 연결 실패 | `depends_on: condition: service_healthy` 확인 | | ||
| | 포트 충돌 | `docker ps`로 기존 컨테이너 확인, `docker compose down` 후 재시작 | | ||
| | 이미지 캐시 문제 | `docker compose build --no-cache` | | ||
| | 앱 시작 지연 | healthcheck의 `start_period` 조정 | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| --- | ||
| name: setup-cucumber | ||
| description: Cucumber BDD 인프라를 구성한다. 의존성 추가, 테스트 러너, Spring 통합, 기존 테스트를 Gherkin 시나리오로 전환한다. | ||
| disable-model-invocation: true | ||
| argument-hint: (인자 없음) | ||
| --- | ||
|
|
||
| Cucumber BDD 인프라를 구성한다. 아래 단계를 순서대로 수행한다. | ||
|
|
||
| ## 1단계: 의존성 추가 | ||
|
|
||
| build.gradle에 Cucumber 관련 의존성을 추가한다. | ||
|
|
||
| 필요한 의존성: | ||
| - `io.cucumber:cucumber-java` — Step Definitions 작성 | ||
| - `io.cucumber:cucumber-spring` — Spring Boot 통합 | ||
| - `io.cucumber:cucumber-junit-platform-engine` — JUnit Platform에서 실행 | ||
| - `org.junit.platform:junit-platform-suite` — Suite API로 Cucumber 실행 | ||
|
|
||
| 한글 Step Definitions(`조건`, `먼저`, `만약`, `그러면`)을 사용하려면 `io.cucumber.java.ko` 패키지를 import한다. | ||
|
|
||
| ## 2단계: Cucumber 테스트 러너 생성 | ||
|
|
||
| `src/test/java/gift/acceptance/` 에 CucumberTest 클래스를 생성한다. | ||
|
|
||
| ```java | ||
| @Suite | ||
| @IncludeEngines("cucumber") | ||
| @SelectPackages("gift.acceptance") | ||
| @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "gift.acceptance") | ||
| @ConfigurationParameter(key = FEATURES_PROPERTY_NAME, value = "src/test/resources/features") | ||
| @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") | ||
| class CucumberTest { | ||
| } | ||
| ``` | ||
|
|
||
| ## 3단계: Spring 통합 설정 | ||
|
|
||
| `src/test/java/gift/acceptance/` 에 CucumberSpringConfiguration 클래스를 생성한다. | ||
|
|
||
| ```java | ||
| @CucumberContextConfiguration | ||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) | ||
| public class CucumberSpringConfiguration { | ||
|
|
||
| @LocalServerPort | ||
| private int port; | ||
|
|
||
| @Before | ||
| public void setUp() { | ||
| RestAssured.port = port; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - `@CucumberContextConfiguration`으로 Spring 컨텍스트를 Cucumber에 연결한다 | ||
| - `@Before`는 `io.cucumber.java.Before`를 사용한다 (JUnit의 @BeforeEach가 아님) | ||
|
|
||
| ## 4단계: Feature 파일 디렉토리 생성 | ||
|
|
||
| `src/test/resources/features/` 디렉토리를 생성한다. | ||
|
|
||
| ## 5단계: 시나리오 간 상태 공유 Bean | ||
|
|
||
| Step Definitions 간 HTTP 응답을 공유할 Bean을 생성한다. | ||
|
|
||
| ```java | ||
| @Component | ||
| @ScenarioScope | ||
| public class SharedContext { | ||
| private Response response; | ||
|
|
||
| public Response getResponse() { return response; } | ||
| public void setResponse(Response response) { this.response = response; } | ||
| } | ||
| ``` | ||
|
|
||
| - `@ScenarioScope`로 시나리오마다 새 인스턴스를 생성하여 격리한다 | ||
| - Step Definition 클래스들이 이 Bean을 주입받아 사용한다 | ||
|
|
||
| ## 6단계: 데이터 격리 | ||
|
|
||
| Cucumber에서는 `@Sql` 어노테이션을 사용할 수 없다. 대신 `@Before` hook에서 데이터를 초기화한다. | ||
|
|
||
| ```java | ||
| @Before | ||
| public void cleanup() { | ||
| // JdbcTemplate 또는 Repository를 사용하여 테이블 초기화 | ||
| } | ||
| ``` | ||
|
|
||
| 또는 각 시나리오의 `배경` (Background) 블록에서 데이터 상태를 명시한다. | ||
|
|
||
| ## 7단계: 기존 테스트 전환 | ||
|
|
||
| 기존 RestAssured 기반 인수 테스트를 Feature 파일 + Step Definitions로 전환한다. | ||
|
|
||
| 전환 순서: | ||
| 1. 기존 테스트의 @DisplayName을 시나리오 제목으로 변환 | ||
| 2. 테스트 로직을 Given/When/Then으로 분리 | ||
| 3. Step Definition 메서드를 작성 | ||
| 4. 기존 테스트 클래스는 전환 완료 후 제거 | ||
|
|
||
| ## 8단계: 검증 | ||
|
|
||
| ```bash | ||
| ./gradlew test | ||
| ``` | ||
|
|
||
| - 모든 Cucumber 시나리오가 실행되고 통과하는지 확인한다 | ||
| - `pretty` 플러그인으로 한글 시나리오가 콘솔에 출력되는지 확인한다 | ||
|
|
||
| ## 주의사항 | ||
|
|
||
| - Feature 파일은 `.feature` 확장자를 사용한다 | ||
| - 한글 Gherkin 사용 시 파일 첫 줄에 `# language: ko`를 명시한다 | ||
| - Step Definition 파라미터 추출은 Cucumber Expressions 또는 정규식을 사용한다 | ||
| - 하나의 Step Definition 클래스가 너무 커지지 않도록 도메인별로 분리한다 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 skill의 목적은 테스트 수행인가요? 아니면 Dockerfile 작성 및 수행일까요??
claude code skill을 잘 인식하게 하기위해 description을 수정해주면 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 스킬의 핵심 목적은 Dockerfile 작성 및 Docker Compose를 통한 애플리케이션 컨테이너화였습니다!
현재는 테스트를 수행한다는 의미가 내포되어있어 혼란이 있을 것 같습니다. 이 부분 수정하도록 하겠습니다!