# QA 에이전트 설계 가이드 빌드 하네스에 QA 에이전트를 포함할 때 참고하는 가이드. 실제 프로젝트(SatangSlide)에서 발견된 버그 패턴과 그 근본 원인 분석을 바탕으로, QA가 놓치기 쉬운 결함을 체계적으로 잡는 검증 방법론을 제공한다. --- ## 목차 1. QA 에이전트가 놓치는 결함의 패턴 2. 통합 정합성 검증 (Integration Coherence Verification) 3. QA 에이전트 설계 원칙 4. 검증 체크리스트 템플릿 5. QA 에이전트 정의 템플릿 --- ## 1. QA 에이전트가 놓치는 결함의 패턴 ### 1-1. 경계면 불일치 (Boundary Mismatch) 가장 빈번한 결함. 두 컴포넌트가 각각 "올바르게" 구현되어 있지만, 연결 지점에서 계약이 어긋남. | 경계면 | 불일치 예시 | 놓치는 이유 | |--------|-----------|-----------| | API 응답 → 프론트 훅 | API가 `{ projects: [...] }` 반환, 훅이 `SlideProject[]` 기대 | 각각 개별 검증하면 정상, 교차 비교 안 함 | | API 응답 필드명 → 타입 정의 | API가 `thumbnailUrl`(camelCase), 타입이 `thumbnail_url`(snake_case) | TypeScript 제네릭으로 캐스팅하면 컴파일러가 못 잡음 | | 파일 경로 → 링크 href | 페이지가 `/dashboard/create`에 있는데 링크가 `/create`로 지정 | 파일 구조와 href를 교차 비교하지 않음 | | 상태 전이 맵 → 실제 status 업데이트 | 맵에 `generating_template → template_approved` 정의, 코드에서 전환 누락 | 맵 존재 확인만 하고, 모든 업데이트 코드를 추적하지 않음 | | API 엔드포인트 → 프론트 훅 | API 존재하지만 대응 훅 없음 (호출 안 됨) | API 목록과 훅 목록을 1:1 매핑하지 않음 | | 즉시 응답 → 비동기 결과 | API가 즉시 `{ status }` 반환, 프론트가 `data.failedIndices` 접근 | 동기/비동기 응답 구분 없이 타입만 확인 | ### 1-2. 왜 정적 코드 리뷰로 못 잡나 - **TypeScript 제네릭의 한계**: `fetchJson()` — 런타임 응답이 `{ projects: [...] }`여도 컴파일 통과 - **`npm run build` 통과 ≠ 정상 동작**: 타입 캐스팅, `any`, 제네릭이 사용되면 빌드는 성공하지만 런타임에 실패 - **존재 검증 vs 연결 검증의 차이**: "API가 있는가?"와 "API의 응답이 호출측의 기대와 일치하는가?"는 전혀 다른 검증 --- ## 2. 통합 정합성 검증 (Integration Coherence Verification) QA 에이전트에 반드시 포함해야 하는 **교차 비교 검증** 영역. ### 2-1. API 응답 ↔ 프론트 훅 타입 교차 검증 **방법**: 각 API route의 `NextResponse.json()` 호출부와 대응 훅의 `fetchJson` 타입 파라미터를 비교. ``` 검증 단계: 1. API route에서 NextResponse.json()에 전달하는 객체의 shape 추출 2. 대응 훅에서 fetchJson의 T 타입 확인 3. shape과 T가 일치하는지 비교 4. 래핑 여부 확인 (API가 { data: [...] }를 반환하면 훅이 .data를 꺼내는지) ``` **특히 주의할 패턴:** - 페이지네이션 API: `{ items: [], total, page }` vs 프론트가 배열 기대 - snake_case DB 필드 → camelCase API 응답 → 프론트 타입 정의 간 불일치 - 즉시 응답 (202 Accepted) vs 최종 결과의 shape 차이 ### 2-2. 파일 경로 ↔ 링크/라우터 경로 매핑 **방법**: `src/app/` 하위 page 파일의 URL 경로를 추출하고, 코드 내 모든 `href`, `router.push()`, `redirect()` 값과 대조. ``` 검증 단계: 1. src/app/ 하위 page.tsx 파일 경로에서 URL 패턴 추출 - (group) → URL에서 제거 - [param] → 동적 세그먼트 2. 코드 내 모든 href=, router.push(, redirect( 값 수집 3. 각 링크가 실제 존재하는 page 경로와 매칭되는지 확인 4. route group 내부 페이지의 URL 접두사 주의 (예: dashboard/ 하위) ``` ### 2-3. 상태 전이 완전성 추적 **방법**: 코드에서 모든 `status:` 업데이트를 추출하여 상태 전이 맵과 대조. ``` 검증 단계: 1. 상태 전이 맵(STATE_TRANSITIONS)에서 허용된 전이 목록 추출 2. 모든 API route에서 .update({ status: "..." }) 패턴 검색 3. 각 전이가 맵에 정의되어 있는지 확인 4. 맵에 정의된 전이 중 코드에서 실행되지 않는 것 식별 (죽은 전이) 5. 특히: 중간 상태(예: generating_template)에서 최종 상태(template_approved)로의 전환이 누락되지 않았는지 ``` ### 2-4. API 엔드포인트 ↔ 프론트 훅 1:1 매핑 **방법**: 모든 API route와 프론트 훅을 나열하여 짝이 맞는지 확인. ``` 검증 단계: 1. src/app/api/ 하위 route.ts에서 HTTP 메서드별 엔드포인트 목록 추출 2. src/hooks/ 하위 use*.ts에서 fetch 호출 URL 목록 추출 3. API 엔드포인트 중 훅에서 호출하지 않는 것 식별 → "사용 안 됨" 플래그 4. "사용 안 됨"이 의도적인지 (관리 API 등) 아닌지 (호출 누락) 판단 ``` --- ## 3. QA 에이전트 설계 원칙 ### 3-1. Explore 타입이 아닌 general-purpose 타입을 사용하라 QA 에이전트가 `Explore` 타입이면 읽기만 가능하다. 하지만 효과적인 QA는: - Grep으로 패턴 검색 (모든 `NextResponse.json()` 추출) - 스크립트 실행으로 자동 대조 (API shape vs 훅 타입) - 필요 시 수정까지 가능 **권장**: `general-purpose` 타입으로 설정하되, 에이전트 정의에서 "검증 → 리포트 → 수정 요청" 프로토콜을 명시. ### 3-2. 체크리스트는 "존재 확인"보다 "교차 비교"를 우선하라 | 약한 체크리스트 | 강한 체크리스트 | |---------------|---------------| | API 엔드포인트가 존재하는가? | API 엔드포인트의 응답 shape과 대응 훅의 타입이 일치하는가? | | 상태 전이 맵이 정의되어 있는가? | 모든 status 업데이트 코드가 맵의 전이와 일치하는가? | | 페이지 파일이 존재하는가? | 코드 내 모든 링크가 실제 존재하는 페이지를 가리키는가? | | TypeScript strict mode인가? | 제네릭 캐스팅으로 우회된 타입 안전성이 없는가? | ### 3-3. "양쪽을 동시에 읽어라" 원칙 QA가 경계면 버그를 잡으려면, 한쪽만 읽어선 안 된다. 반드시: - API route **와** 대응 훅을 **같이** 읽고 - 상태 전이 맵 **와** 실제 업데이트 코드를 **같이** 읽고 - 파일 구조 **와** 링크 경로를 **같이** 읽어야 한다 에이전트 정의에 이 원칙을 명시적으로 기재하라. ### 3-4. QA는 빌드 후가 아니라, 각 모듈 완성 직후에 실행하라 오케스트레이터에서 QA를 "Phase 4: 전체 완성 후"에만 배치하면: - 버그가 누적되어 수정 비용이 높아짐 - 초기 경계면 불일치가 후속 모듈에 전파됨 **권장 패턴**: 각 백엔드 API 완성 시 즉시 해당 API + 대응 훅의 교차 검증 수행 (incremental QA). --- ## 4. 검증 체크리스트 템플릿 QA 에이전트 정의에 포함할 웹 애플리케이션용 통합 정합성 체크리스트. ```markdown ### 통합 정합성 검증 (웹 앱) #### API ↔ 프론트엔드 연결 - [ ] 모든 API route의 응답 shape과 대응 훅의 제네릭 타입이 일치 - [ ] 래핑된 응답({ items: [...] })은 훅에서 unwrap하는지 확인 - [ ] snake_case ↔ camelCase 변환이 일관되게 적용 - [ ] 즉시 응답(202)과 최종 결과의 shape이 프론트에서 구분되는지 확인 - [ ] 모든 API 엔드포인트에 대응하는 프론트 훅이 존재하고 실제로 호출됨 #### 라우팅 정합성 - [ ] 코드 내 모든 href/router.push 값이 실제 page 파일 경로와 매칭 - [ ] route group ((group))이 URL에서 제거되는 것을 고려한 경로 검증 - [ ] 동적 세그먼트([id])가 올바른 파라미터로 채워지는지 확인 #### 상태 머신 정합성 - [ ] 정의된 모든 상태 전이가 코드에서 실행됨 (죽은 전이 없음) - [ ] 코드의 모든 status 업데이트가 전이 맵에 정의됨 (무단 전이 없음) - [ ] 중간 상태에서 최종 상태로의 전환이 누락되지 않음 - [ ] 프론트에서 상태 기반 분기(if status === "X")의 X가 실제 도달 가능 #### 데이터 흐름 정합성 - [ ] DB 스키마 필드명과 API 응답 필드명의 매핑이 일관됨 - [ ] 프론트 타입 정의와 API 응답의 필드명이 일치 - [ ] 옵셔널 필드에 대한 null/undefined 처리가 양쪽에서 일관됨 ``` --- ## 5. QA 에이전트 정의 템플릿 빌드 하네스의 QA 에이전트에 포함할 핵심 섹션. ```markdown --- name: qa-inspector description: "QA 검증 전문가. 스펙 준수, 통합 정합성, 디자인 품질을 검증." --- # QA Inspector ## 핵심 역할 스펙 대비 구현 품질과 **모듈 간 통합 정합성**을 검증한다. ## 검증 우선순위 1. **통합 정합성** (가장 높음) — 경계면 불일치가 런타임 에러의 주요 원인 2. **기능 스펙 준수** — API/상태머신/데이터모델 3. **디자인 품질** — 색상/타이포/반응형 4. **코드 품질** — 미사용 코드, 명명 규칙 ## 검증 방법: "양쪽 동시 읽기" 경계면 검증은 반드시 **양쪽 코드를 동시에 열어** 비교한다: | 검증 대상 | 왼쪽 (생산자) | 오른쪽 (소비자) | |----------|-------------|---------------| | API 응답 shape | route.ts의 NextResponse.json() | hooks/의 fetchJson | | 라우팅 | src/app/ page 파일 경로 | href, router.push 값 | | 상태 전이 | STATE_TRANSITIONS 맵 | .update({ status }) 코드 | | DB → API → UI | 테이블 컬럼명 | API 응답 필드 → 타입 정의 | ## 팀 통신 프로토콜 - 발견 즉시 해당 에이전트에게 구체적 수정 요청 (파일:라인 + 수정 방법) - 경계면 이슈는 양쪽 에이전트 **모두**에게 알림 - 리더에게: 검증 리포트 (통과/실패/미검증 항목 구분) ``` --- ## 실제 사례: SatangSlide에서 발견된 버그 이 가이드의 모든 내용은 아래 실제 버그에서 추출한 교훈이다: | 버그 | 경계면 | 원인 | |------|--------|------| | `projects?.filter is not a function` | API→훅 | API가 `{projects:[]}` 반환, 훅이 배열 기대 | | 대시보드 모든 링크 404 | 파일경로→href | `/dashboard/` 접두사 누락 | | 테마 이미지 안 보임 | API→컴포넌트 | `thumbnailUrl` vs `thumbnail_url` | | 테마 선택 저장 안 됨 | API→훅 | select-theme API 존재, 훅 없음 | | 생성 페이지 영원히 대기 | 상태전이→코드 | `template_approved` 전이 코드 누락 | | `data.failedIndices` 크래시 | 즉시응답→프론트 | 백그라운드 결과를 즉시 응답에서 접근 | | 완료 후 슬라이드 보기 404 | 파일경로→href | `/projects/` → `/dashboard/projects/` |