ADR-11: 골든 스냅샷 통합 테스트
| Date | Author | Repos |
|---|---|---|
| 2025-12-23 | @KubrickCode | core |
Status: Accepted
Context
문제 상황
SpecVital Core 파서는 다양한 환경에서 정확한 테스트 탐지가 필요함:
- 다중 테스트 프레임워크 (Jest, Vitest, Playwright, JUnit, pytest 등)
- 다중 프로그래밍 언어 (JavaScript, TypeScript, Python, Go, Java, C# 등)
- 복잡한 저장소 구조 (모노레포, 혼합 프레임워크, 폴리글랏 프로젝트)
단위 테스트만으로는 부족한 이유
- 제한된 커버리지: 인공 테스트 픽스처는 모든 실제 코드 패턴을 예측할 수 없음
- 회귀 위험: 파서 변경이 단위 테스트에 포함되지 않은 프레임워크 탐지를 조용히 깨뜨릴 수 있음
- 엣지 케이스: 중첩 설정의 모노레포, 단일 저장소 내 다중 프레임워크, 비전통적 파일 구조
요구사항
- 높은 신뢰도: 실제 코드베이스에 대한 파서 동작 검증
- 회귀 탐지: 파서 출력 변경 자동 감지
- 빠른 피드백: 많은 저장소를 테스트하면서도 재실행이 빠름
- 결정론적 결과: 동일 입력은 동일 출력 생성
Decision
실제 GitHub 오픈소스 저장소와 골든 스냅샷 비교를 통합 테스트에 사용함.
통합 테스트 방식:
- GitHub에서 실제 저장소 클론
- 전체 파싱 파이프라인 실행
- 사전 기록된 골든 스냅샷과 결과 비교
- 차이 발견 시 실패 (의도적 업데이트 제외)
Options Considered
Option A: 실제 저장소 + 골든 스냅샷 (선택됨)
실제 오픈소스 프로젝트를 클론하고, 파싱 후 저장된 스냅샷과 비교함.
장점:
- 실제 환경 검증: 인공 픽스처가 아닌 실제 코드베이스로 테스트
- 자동 회귀 탐지: 출력에 영향을 주는 모든 파서 변경이 즉시 표시됨
- 엣지 케이스 커버리지: 실제 프로젝트는 개발자가 생각하지 못한 엣지 케이스를 자연스럽게 포함
- 프레임워크 다양성: 모든 지원 프레임워크를 해당 대표 프로젝트로 테스트
- 문서 가치: 저장소 목록이 프레임워크 지원 증명 역할
단점:
- 초기 실행 시간: 전체 테스트 스위트는 많은 저장소 클론이 필요
- 외부 의존성: GitHub 저장소 가용성에 테스트가 의존
- 유지보수 부담: 파서 동작이 의도적으로 변경될 때 스냅샷 업데이트 필요
- 네트워크 요구: 최초 실행은 인터넷 접근 필요
Option B: 인공 테스트 픽스처만 사용
알려진 패턴을 실행하는 인공 테스트 파일 생성.
장점:
- 빠른 실행, 네트워크 의존성 없음
- 테스트 케이스에 대한 완전한 통제
- 이해하기 쉬움
단점:
- 커버리지 갭: 모든 실제 패턴을 예측할 수 없음
- 거짓 신뢰: 테스트 통과하지만 실제 코드에서 파서 실패
- 유지보수 오버헤드: 각 프레임워크별로 픽스처 수동 생성 필요
- 엣지 케이스 누락: 실제 환경의 복잡성 포착 불가
Option C: 런타임 출력 검증만 수행
파서 실행 후 기본 불변식(비어있지 않은 출력, 에러 없음)만 확인, 스냅샷 비교 없음.
장점:
- 스냅샷 유지보수 없음
- 간단한 구현
단점:
- 조용한 회귀: 출력 변경이 감지되지 않음
- 낮은 신뢰도: 테스트 통과가 올바른 출력을 의미하지 않음
- 기준선 없음: 예상 동작 기록 없음
Implementation Details
저장소 설정
저장소는 repos.yaml에 정의됨:
yaml
repositories:
- name: vite
url: https://github.com/vitejs/vite
ref: v6.0.0
frameworks:
- vitest
- name: grafana
url: https://github.com/grafana/grafana
ref: v11.3.1
frameworks:
- cypress
- go-testing
- jest
- playwright
complex: true
nondeterministic: true필드:
| 필드 | 설명 |
|---|---|
name | 저장소 식별자 |
url | GitHub 저장소 URL |
ref | Git 태그 또는 브랜치 (재현성을 위해 고정) |
frameworks | 탐지될 것으로 예상되는 프레임워크 |
complex | 모노레포 또는 혼합 프레임워크 프로젝트 |
nondeterministic | 엄격한 스냅샷 비교 건너뜀 |
쉘로우 클론과 캐싱
tests/integration/testdata/
├── cache/ # 클론된 저장소
│ ├── vite-v6.0.0/
│ ├── grafana-v11.3.1/
│ └── .clone_complete # 마커 파일
└── golden/ # JSON 스냅샷
├── vite-v6.0.0.json
└── grafana-v11.3.1.json최적화 전략:
- 쉘로우 클론 (
--depth=1): 다운로드 크기와 시간 최소화 - 단일 브랜치 (
--single-branch): 지정된 ref만 가져옴 - 완료 마커:
.clone_complete파일이 부분 다운로드의 재클론 방지 - 캐시 유지: 클론된 저장소가 테스트 실행 간에 재사용됨
골든 스냅샷 구조
json
{
"repository": "gin",
"ref": "v1.10.0",
"expectedFrameworks": ["go-testing"],
"fileCount": 38,
"testCount": 483,
"frameworkCounts": {
"go-testing": 38
},
"sampleFiles": [
{
"path": "auth_test.go",
"framework": "go-testing",
"suiteCount": 0,
"testCount": 9
}
],
"stats": {
"filesMatched": 38,
"filesScanned": 38
}
}비교 포인트:
fileCount: 탐지된 총 테스트 파일 수testCount: 탐지된 총 테스트 케이스 수frameworkCounts: 프레임워크별 파일 분포sampleFiles: 처음 N개 파일 (결정론적을 위해 경로순 정렬)
비결정론적 저장소
일부 저장소는 다음 이유로 비결정론적 파싱 결과를 보임:
- 병렬 실행 경쟁 조건: 파일 발견 순서가 다름
- 동적 테스트 생성: 런타임에 따라 테스트 수 변동
- 빌드 아티팩트: 생성된 파일이 있을 수도 없을 수도 있음
이러한 저장소는 nondeterministic: true 설정으로:
- 전체 파싱 파이프라인 실행
- 프레임워크 탐지 검증
- 엄격한 스냅샷 비교 건너뜀
프레임워크 검증
다중 프레임워크 저장소는 모든 예상 프레임워크를 검증함:
go
func validateFrameworkMatch(t *testing.T, expected []string, actual map[string]int) {
// 양방향 검사: 누락된 예상 + 예상치 못한 탐지
// 보조 프레임워크가 조용히 탐지 안 되는 것을 방지
}이를 통해 파서 변경이 보조 프레임워크 탐지를 조용히 깨뜨리지 않도록 보장함.
테스트 워크플로우
통합 테스트 실행
bash
just test integration # 모든 통합 테스트 실행스냅샷 업데이트
언제: 의도적인 파서 변경 후 (새 기능, 버그 수정)
금지: 출력이 왜 바뀌었는지 이해 없이 실패한 테스트를 "수정"하기 위해
bash
just snapshot-update # 모든 스냅샷 업데이트
just snapshot-update repo=vite # 특정 저장소 업데이트CI/CD 통합
통합 테스트 실행 시점:
- Pull request (병합 전 회귀 탐지)
- main 브랜치 (릴리스 후보 검증)
- 타임아웃 설정 (~15분, 전체 스위트)
Consequences
Positive
높은 신뢰도 테스팅
- 인공 픽스처를 넘어선 실제 환경 검증
- 개발자가 예상하지 못한 엣지 케이스 커버리지
- 대표 프로젝트를 통한 프레임워크 지원 증명
자동 회귀 탐지
- 모든 출력 변경이 즉시 표시됨
- 차이 출력이 정확한 변경 내용 표시
- 조용한 파서 성능 저하 방지
빠른 재실행
- 캐시된 클론이 네트워크 오버헤드 제거
t.Parallel()을 통한 병렬 테스트 실행- 일반적인 재실행: 초 단위 (최초 클론의 분 단위와 비교)
자가 문서화
repos.yaml이 지원 프레임워크 문서 역할- 골든 스냅샷이 예상 파서 동작 표시
- 테스트 실패가 실행 가능한 차이 출력 제공
Negative
초기 실행 시간
- 최초 실행은 모든 저장소 클론 필요
- 완화: 쉘로우 클론, 병렬 실행, CI 캐싱
외부 의존성
- GitHub 접근 불가 시 테스트 실패
- 완화: 캐시된 클론, 오프라인 폴백 가능
스냅샷 유지보수
- 의도적 변경 시 스냅샷 업데이트 필요
- 완화: 명확한 업데이트 명령어, 문서화된 워크플로우
디스크 공간
- 캐시된 저장소가 저장소 공간 소비
- 완화: 쉘로우 클론이 크기 최소화,
.gitignore가 캐시 제외
Trade-off 요약
| 측면 | 골든 스냅샷 | 인공 픽스처 |
|---|---|---|
| 실제 환경 커버리지 | 우수 | 제한적 |
| 회귀 탐지 | 자동 | 수동 |
| 초기 설정 | 느림 (저장소 클론) | 빠름 |
| 재실행 속도 | 빠름 (캐시됨) | 빠름 |
| 유지보수 | 변경 시 업데이트 | 새 픽스처 추가 |
| 신뢰도 수준 | 높음 | 중간 |
References
- 테스트 인프라:
tests/integration/ - 저장소 정의:
tests/integration/repos.yaml - 골든 스냅샷:
tests/integration/testdata/golden/
