Skip to content

ADR-11: 골든 스냅샷 통합 테스트

🇺🇸 English Version

DateAuthorRepos
2025-12-23@KubrickCodecore

Status: Accepted

Context

문제 상황

SpecVital Core 파서는 다양한 환경에서 정확한 테스트 탐지가 필요함:

  • 다중 테스트 프레임워크 (Jest, Vitest, Playwright, JUnit, pytest 등)
  • 다중 프로그래밍 언어 (JavaScript, TypeScript, Python, Go, Java, C# 등)
  • 복잡한 저장소 구조 (모노레포, 혼합 프레임워크, 폴리글랏 프로젝트)

단위 테스트만으로는 부족한 이유

  1. 제한된 커버리지: 인공 테스트 픽스처는 모든 실제 코드 패턴을 예측할 수 없음
  2. 회귀 위험: 파서 변경이 단위 테스트에 포함되지 않은 프레임워크 탐지를 조용히 깨뜨릴 수 있음
  3. 엣지 케이스: 중첩 설정의 모노레포, 단일 저장소 내 다중 프레임워크, 비전통적 파일 구조

요구사항

  1. 높은 신뢰도: 실제 코드베이스에 대한 파서 동작 검증
  2. 회귀 탐지: 파서 출력 변경 자동 감지
  3. 빠른 피드백: 많은 저장소를 테스트하면서도 재실행이 빠름
  4. 결정론적 결과: 동일 입력은 동일 출력 생성

Decision

실제 GitHub 오픈소스 저장소와 골든 스냅샷 비교를 통합 테스트에 사용함.

통합 테스트 방식:

  1. GitHub에서 실제 저장소 클론
  2. 전체 파싱 파이프라인 실행
  3. 사전 기록된 골든 스냅샷과 결과 비교
  4. 차이 발견 시 실패 (의도적 업데이트 제외)

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저장소 식별자
urlGitHub 저장소 URL
refGit 태그 또는 브랜치 (재현성을 위해 고정)
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

  1. 높은 신뢰도 테스팅

    • 인공 픽스처를 넘어선 실제 환경 검증
    • 개발자가 예상하지 못한 엣지 케이스 커버리지
    • 대표 프로젝트를 통한 프레임워크 지원 증명
  2. 자동 회귀 탐지

    • 모든 출력 변경이 즉시 표시됨
    • 차이 출력이 정확한 변경 내용 표시
    • 조용한 파서 성능 저하 방지
  3. 빠른 재실행

    • 캐시된 클론이 네트워크 오버헤드 제거
    • t.Parallel()을 통한 병렬 테스트 실행
    • 일반적인 재실행: 초 단위 (최초 클론의 분 단위와 비교)
  4. 자가 문서화

    • repos.yaml이 지원 프레임워크 문서 역할
    • 골든 스냅샷이 예상 파서 동작 표시
    • 테스트 실패가 실행 가능한 차이 출력 제공

Negative

  1. 초기 실행 시간

    • 최초 실행은 모든 저장소 클론 필요
    • 완화: 쉘로우 클론, 병렬 실행, CI 캐싱
  2. 외부 의존성

    • GitHub 접근 불가 시 테스트 실패
    • 완화: 캐시된 클론, 오프라인 폴백 가능
  3. 스냅샷 유지보수

    • 의도적 변경 시 스냅샷 업데이트 필요
    • 완화: 명확한 업데이트 명령어, 문서화된 워크플로우
  4. 디스크 공간

    • 캐시된 저장소가 저장소 공간 소비
    • 완화: 쉘로우 클론이 크기 최소화, .gitignore가 캐시 제외

Trade-off 요약

측면골든 스냅샷인공 픽스처
실제 환경 커버리지우수제한적
회귀 탐지자동수동
초기 설정느림 (저장소 클론)빠름
재실행 속도빠름 (캐시됨)빠름
유지보수변경 시 업데이트새 픽스처 추가
신뢰도 수준높음중간

References

  • 테스트 인프라: tests/integration/
  • 저장소 정의: tests/integration/repos.yaml
  • 골든 스냅샷: tests/integration/testdata/golden/

Open-source test coverage insights