Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 91 additions & 73 deletions .claude/commands/acceptance-test-writer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
당신은 카카오 선물하기 서비스의 시니어 테스트 엔지니어다.

사용자가 테스트 시나리오를 설명하면, 당신은 다음 작업을 순서대로 수행한다:
사용자가 테스트 시나리오를 설명하면, 다음 작업을 순서대로 수행한다.

--------------------------------------------------
[1단계: 프로젝트 코드 탐색]
Expand All @@ -20,97 +19,116 @@
- Entity의 테이블명, 컬럼명, 연관관계를 정확히 파악한다.

--------------------------------------------------
[2단계: 테스트 데이터 SQL 파일 생성]

RANDOM_PORT 환경에서는 @Transactional 롤백이 동작하지 않는다.
따라서 테스트 메서드마다 독립적인 SQL 파일을 생성하여 데이터 충돌을 방지한다.

파일 구조:
- 위치: src/test/resources/data/
- 파일명 규칙: {테스트클래스명}/{테스트메서드명}.sql
예시: gift-acceptance/선물하기_성공.sql, gift-acceptance/재고부족_실패.sql

각 SQL 파일 작성 규칙:
- 파일 상단에 반드시 관련 테이블을 TRUNCATE 또는 DELETE 한다.
(외래키 제약조건을 고려하여 자식 테이블부터 역순으로 삭제)
- 삭제 후 해당 테스트에 필요한 데이터만 INSERT 한다.
- Entity의 실제 테이블명과 컬럼명을 사용한다 (JPA 네이밍 전략 반영).
- NOT NULL 제약조건을 만족하는 완전한 INSERT 문을 작성한다.
- 외래키 순서를 지켜 삽입한다 (부모 테이블 먼저).
- 고정 ID를 사용한다. 랜덤 생성 금지.
- 각 INSERT 문 위에 주석으로 데이터의 용도를 명시한다.

예시 구조:
src/test/resources/data/
├── gift-acceptance/
│ ├── 선물하기_성공_및_재고차감.sql
│ └── 존재하지_않는_옵션_선물하기_실패.sql
└── wish-acceptance/
├── 위시리스트_추가_성공.sql
└── 중복_위시리스트_추가_실패.sql
[2단계: Feature 파일 작성]

위치: src/test/resources/features/{도메인}.feature

한글 Gherkin 키워드를 사용한다:
- 기능 (Feature), 시나리오 (Scenario), 배경 (Background)
- 조건 (Given), 만일 (When), 그러면 (Then), 그리고 (And), 하지만 (But)
- 시나리오 개요 (Scenario Outline), 예 (Examples)

파일 첫 줄에 반드시 `# language: ko`를 선언한다.

작성 규칙:
- 비개발자가 읽어도 이해할 수 있는 자연스러운 한국어로 작성한다.
- 스텝 문장은 재사용 가능하도록 파라미터를 활용한다. 예: "이름이 {string}인 카테고리를 생성하면"
- 반드시 최소 1개의 실패 시나리오를 포함한다 (재고 부족, 중복 요청, 비즈니스 예외 등).
- 배경(Background)을 활용하여 공통 전제 조건을 묶는다.
- 테스트 데이터는 조건(Given) 스텝에서 API 호출을 통해 생성한다. SQL 시드 파일을 사용하지 않는다.

예시:
```gherkin
# language: ko
기능: 카테고리 관리
카테고리를 생성하고 조회할 수 있다.

시나리오: 카테고리 생성 성공
만일 이름이 "전자기기"인 카테고리를 생성하면
그러면 응답 상태 코드는 201이다
그리고 응답 본문의 "name"은 "전자기기"이다

시나리오: 카테고리 생성 후 목록에서 조회된다
조건 이름이 "전자기기"인 카테고리가 등록되어 있다
만일 카테고리 목록을 조회하면
그러면 응답 상태 코드는 200이다
그리고 응답 목록에 "전자기기"가 포함되어 있다
```

--------------------------------------------------
[3단계: 테스트 코드 작성]

기술 스택:
- SpringBootTest (RANDOM_PORT)
- RestAssured
- JUnit5
- @Sql seed 데이터 사용 (메서드 레벨)
- BDD 도구 사용 금지 (Cucumber, Karate 등 금지)

테스트 철학:

1. API 경계 기반 Behavior Driven
- Given / When / Then 을 주석으로 표현한다.
- RestAssured 체이닝을 사용한다.

2. 상태 변화 검증 필수
- 반드시 최소 1개의 실패 시나리오를 포함한다.
- 재고 감소, 중복 요청, 비즈니스 예외 등 상태 변화를 증명한다.
- 실패 시나리오는 실제 코드의 예외 처리 로직을 기반으로 작성한다.
[3단계: Step Definitions 작성]

3. 데이터 관리 - 메서드 레벨 @Sql
- 클래스 레벨 @Sql을 사용하지 않는다.
- 각 테스트 메서드에 개별 @Sql을 적용한다.
- 형식: @Sql(scripts = "/data/{테스트클래스명}/{테스트메서드명}.sql", executionPhase = BEFORE_TEST_METHOD)
- 이렇게 하면 각 테스트가 자신만의 깨끗한 데이터로 시작한다.
위치: src/test/java/gift/cucumber/steps/{도메인}Steps.java

4. 멱등성 보장
- 각 SQL 파일이 TRUNCATE로 시작하므로 실행 순서와 무관하게 동일한 결과를 보장한다.
- 각 테스트는 독립적으로 실행 가능해야 한다.
기술 스택:
- io.cucumber.java.ko 패키지의 한글 어노테이션 (@조건, @만일, @그러면, @그리고)
- RestAssured로 HTTP 요청 수행
- TestContext (@ScenarioScope Bean)로 시나리오 내 상태 공유

TestContext 패턴:
```java
@Component
@ScenarioScope
public class TestContext {
private Response response;
private final Map<String, Long> createdIds = new HashMap<>();

public void setResponse(Response response) { this.response = response; }
public Response getResponse() { return response; }
public void saveId(String key, Long id) { createdIds.put(key, id); }
public Long getId(String key) { return createdIds.get(key); }
}
```

Step Definition 작성 규칙:
- 생성자 주입으로 TestContext를 받는다.
- 조건(Given) 스텝에서는 API를 호출하여 데이터를 생성하고, 생성된 ID를 TestContext에 저장한다.
- 만일(When) 스텝에서는 테스트 대상 API를 호출하고, 응답을 TestContext에 저장한다.
- 그러면(Then) 스텝에서는 TestContext의 응답에서 statusCode, body 등을 검증한다.
- 스텝 문장의 파라미터 추출에 Cucumber Expression({string}, {int}, {long})을 사용한다.

공통 검증 스텝 (CommonSteps에 배치):
```java
@그러면("응답 상태 코드는 {int}이다")
public void 응답_상태_코드_검증(int statusCode) {
context.getResponse().then().statusCode(statusCode);
}

@그리고("응답 본문의 {string}은 {string}이다")
public void 응답_본문_검증(String field, String value) {
context.getResponse().then().body(field, equalTo(value));
}
```

5. 검증 기준
- statusCode, 응답 body, 필요 시 header를 검증한다.
--------------------------------------------------
[4단계: 검증]

6. 코드 품질
- @DisplayName은 한국어로 작성한다.
- 가독성을 최우선으로 한다.
- Acceptance 테스트에서는 불필요한 mocking을 하지 않는다.
작성한 Feature 파일과 Step Definitions가 다음 조건을 만족하는지 확인한다:
- `./gradlew test` 실행 시 Cucumber 시나리오가 모두 통과한다.
- 기존 JUnit5 테스트(CategoryAcceptanceTest 등)도 함께 통과한다.
- 시나리오 간 데이터가 격리된다 (Cucumber @Before 훅에서 DB TRUNCATE).

--------------------------------------------------
[출력 요구사항]

반드시 다음을 생성한다:

1. SQL 파일 (테스트 메서드 수만큼)
- 위치: src/test/resources/data/{테스트클래스명}/{테스트메서드명}.sql
- 각 파일은 TRUNCATE + INSERT로 구성
- 해당 테스트에 필요한 데이터만 포함
1. Feature 파일 (src/test/resources/features/{도메인}.feature)
- 한글 Gherkin으로 작성된 시나리오
- 최소 1개 성공 + 1개 실패 시나리오

2. 테스트 클래스 (src/test/java/ 하위 적절한 패키지)
2. Step Definitions (src/test/java/gift/cucumber/steps/{도메인}Steps.java)
- 컴파일 가능한 완전한 클래스
- 필요한 import 구문 포함
- @SpringBootTest 설정 포함
- 각 메서드에 개별 @Sql 적용
- 최소 1개 성공 + 1개 실패 시나리오
- TestContext를 통한 상태 공유

3. (필요 시) CucumberSpringConfiguration, TestContext 등 인프라 코드 수정

절대 하지 말 것:
- 코드를 탐색하지 않고 추측으로 작성하지 말 것
- Entity에 없는 필드를 임의로 만들지 말 것
- 실제 코드에 없는 API endpoint를 사용하지 말 것
- 외부 BDD 도구를 사용하지 말 것
- 클래스 레벨 @Sql을 사용하지 말 것
- SQL 시드 파일로 테스트 데이터를 관리하지 말 것 (API 호출로 생성)

--------------------------------------------------

Expand Down
Loading