Skip to content

ADR-04: Early-Return 프레임워크 탐지

🇺🇸 English Version

날짜작성자리포지토리
2025-12-23@KubrickCodecore

상태: 승인됨

배경

문제 상황

SpecVital Core는 다양한 언어와 프레임워크에 걸쳐 테스트 파일이 어떤 프레임워크에 속하는지 탐지함. 이 탐지는 다음을 처리해야 함:

  1. 패턴을 공유하는 유사 프레임워크: Jest와 Vitest 모두 describe/it 구문 사용
  2. 파일당 복수의 유효한 신호: import 문, 설정 파일 존재 여부, 콘텐츠 패턴
  3. 결정론적 요구사항: 동일 입력은 항상 동일 결과를 생성해야 함
  4. 모노레포 복잡성: 서로 다른 스코프를 가진 중첩된 설정 파일

탐지 신호

테스트 파일은 여러 신호로 식별 가능함:

신호예시신뢰도
Importimport { test } from 'vitest'최고
Config 스코프jest.config.js 스코프 내 파일높음
콘텐츠jest.fn(), vi.mock() 패턴중간
파일명*.test.ts, *_test.go낮음

전략적 질문

탐지기가 여러 신호를 어떻게 조합하여 단일 프레임워크 결과를 생성해야 하는가?

결정

우선순위 기반 early-return 사용: 신호 신뢰도에 따라 첫 매칭이 승리함.

탐지는 엄격한 우선순위 순서를 따름:

  1. Import → 프레임워크별 import가 발견되면 즉시 반환
  2. Config 스코프 → 파일이 설정 스코프 내에 있으면 즉시 반환
  3. 콘텐츠 패턴 → 프레임워크별 패턴이 발견되면 즉시 반환
  4. Unknown → 매칭된 신호가 없으면 반환

어떤 우선순위 레벨에서든 첫 번째 성공적인 매칭이 발생하면 하위 우선순위를 확인하지 않고 즉시 반환함.

탐지 소스 추적

각 결과는 프레임워크가 어떻게 탐지되었는지를 포함함:

go
type DetectionSource string

const (
    SourceImport         DetectionSource = "import"
    SourceConfigScope    DetectionSource = "config-scope"
    SourceContentPattern DetectionSource = "content-pattern"
    SourceUnknown        DetectionSource = "unknown"
)

검토한 옵션

옵션 A: 우선순위 기반 Early-Return (선택됨)

신호 신뢰도 계층에 따라 첫 매칭이 승리함.

장점:

  • 빠른 실행: 첫 매칭에서 중단, 불필요한 처리 없음
  • 예측 가능한 동작: 동일 입력은 항상 동일 결과 생성
  • 디버깅 용이: "어떤 신호가 매칭되었는지" 추적이 명확함
  • 단순한 구현: 복잡한 점수 계산 로직 없음

단점:

  • 하위 우선순위 신호 무시: 더 강해도 후순위 신호는 평가되지 않음
  • Import 추출 민감성: 잘못된 import 파싱은 잘못된 결과로 이어짐

옵션 B: 점수 축적

각 신호에 신뢰도 점수를 부여하고 합산하여 최고 점수 프레임워크를 반환함.

점수 예시:

신호점수
스코프80
Import60
콘텐츠40
파일명20

장점:

  • 여러 신호가 서로를 강화 가능
  • 더 강한 전체 신호가 약한 초기 매칭을 덮어쓸 수 있음

단점:

  • 디버깅 어려움: "왜 이 프레임워크가 이겼는가?"는 모든 점수 분석 필요
  • 튜닝 복잡성: 점수 값은 임의적이고 보정하기 어려움
  • 비결정론 위험: 점수 동점 시 추가 tie-breaking 규칙 필요
  • 성능 오버헤드: 결정 전 모든 신호 평가 필요

옵션 C: 하이브리드 접근법

점수 축적에 early-exit 임계값 적용 (예: 점수가 100을 초과하면 즉시 반환).

장점:

  • 속도와 신호 조합의 균형

단점:

  • 점수 계산의 복잡성 상속
  • 임계값은 임의적임
  • 여전히 전체 점수 계산 로직 필요

구현 세부사항

Config 스코프 해석

여러 설정 파일이 파일에 적용될 수 있을 때, 탐지기는 가장 구체적인 것을 선택함:

  1. 깊이 기반 선택: 더 깊은 설정 경로가 우선 (더 구체적)
  2. Tie-breaker 1: 더 긴 설정 경로 (더 구체적인 경로)
  3. Tie-breaker 2: 사전순 정렬 (결정론적)
project/
├── jest.config.js          # 깊이 0
├── packages/
│   └── web/
│       └── jest.config.js  # 깊이 2 (packages/web/ 내 파일에 대해 승리)

언어별 처리

Go 테스트 파일은 import 탐지 대신 네이밍 컨벤션 (*_test.go)을 사용하며, 일반 탐지 흐름 전에 특별 케이스로 처리함.

네거티브 매칭

프레임워크는 "확실히 이 프레임워크가 아님" 신호를 선언 가능함:

go
type MatchResult struct {
    Confidence int
    Evidence   []string
    Negative   bool  // true이면 이 프레임워크 제외
}

유사한 프레임워크가 패턴을 공유할 때 false positive를 방지함.

결과

긍정적

  1. 성능

    • 빠른 실행: 첫 매칭에서 종료
    • 하위 우선순위 신호에 대한 불필요한 계산 없음
    • 최선 O(1), 최악 O(프레임워크 × 신호)
  2. 유지보수성

    • 단순한 제어 흐름 (순차적 우선순위 검사)
    • 탐지 단계별 명확한 책임
    • 새 프레임워크나 신호 추가 용이
  3. 디버깅 용이성

    • 결과에 탐지 소스 포함
    • "왜 이 프레임워크인가" 추적 용이
    • 감사할 복잡한 점수 계산 없음
  4. 결정론

    • 동일 파일은 항상 동일 결과 생성
    • Config 스코프 tie-breaking이 완전히 결정론적
    • 무작위 또는 타이밍 의존적 동작 없음

부정적

  1. 신호 계층이 고정됨

    • 프로젝트별 우선순위 순서 조정 불가
    • 완화: 우선순위 순서는 실제 신뢰도 분석 기반
  2. Import 파싱 민감성

    • 잘못된 import 추출은 잘못된 결과 유발
    • 완화: 포괄적인 테스트 커버리지를 갖춘 언어별 추출기
  3. 후순위 신호 무시

    • import가 매칭되면 콘텐츠 패턴은 검사되지 않음
    • 완화: Import가 가장 신뢰할 수 있음. 후순위 신호 무시는 의도적임.

설계 원칙

  • 명시적 개발자 의도가 승리: Import 문은 의식적인 프레임워크 선택을 나타냄
  • 프로젝트 설정이 권위적: 설정 파일은 프로젝트 레벨 결정을 정의함
  • 콘텐츠 패턴은 폴백: 명시적 신호가 없을 때만 사용함
  • 파일명 패턴은 신뢰할 수 없음: 탐지에서 제거됨 (false positive가 너무 많음)

관련 ADR

참조

  • pkg/parser/detection/detector.go - 핵심 탐지 구현
  • pkg/parser/detection/result.go - DetectionSource를 포함한 결과 타입
  • pkg/parser/framework/scope.go - Config 스코프 해석 로직

Open-source test coverage insights