본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 6장: 회귀 테스트 자동화
2026년 3월 10일·AI / ML·

6장: 회귀 테스트 자동화

평가를 회귀 테스트로 졸업시키는 패턴, Golden Dataset 관리, 롤링 성공률 모니터링, 베이스라인 관리와 변경 영향 분석을 다룹니다.

16분1,241자10개 섹션
testingaievaluationquality-assurance
공유
agent-testing6 / 10
12345678910
이전5장: 비결정적 출력 평가다음7장: 에이전트 안정성 메트릭

학습 목표

  • 평가(Eval)를 회귀 테스트(Regression Test)로 졸업시키는 패턴을 이해합니다.
  • Golden Dataset의 설계, 관리, 버전 관리 전략을 학습합니다.
  • 롤링 7일/30일 성공률 모니터링과 10% 하락 플래그 기법을 익힙니다.
  • 베이스라인 관리와 변경 영향 분석 파이프라인을 구축합니다.

평가와 회귀 테스트의 차이

5장에서 다룬 **평가(Evaluation)**는 에이전트의 현재 품질을 측정하는 도구입니다. 반면 **회귀 테스트(Regression Test)**는 에이전트의 품질이 이전보다 나빠지지 않았는지 확인하는 방어 장치입니다.

구분평가 (Eval)회귀 테스트 (Regression)
목적현재 품질 측정품질 저하 방지
실행 시점개발 중, 탐색적매 변경(PR, 배포)마다
기준절대 임계값이전 베이스라인 대비
실패 의미"개선 필요""이 변경이 품질을 떨어뜨림"
엄격도유연엄격

평가에서 회귀 테스트로의 졸업

모든 평가가 회귀 테스트가 될 필요는 없습니다. 졸업 기준을 정의하고, 그 기준을 충족하는 평가만 회귀 테스트 스위트에 포함시킵니다.

졸업 기준

graduation_criteria.py
python
from dataclasses import dataclass
 
@dataclass
class GraduationCriteria:
    """평가를 회귀 테스트로 졸업시키기 위한 기준"""
 
    # 최소 연속 통과 횟수
    min_consecutive_passes: int = 5
 
    # 최소 평균 점수
    min_average_score: float = 0.85
 
    # 최대 허용 표준편차 (안정성)
    max_score_stdev: float = 0.10
 
    # 최소 실행 기간 (일)
    min_observation_days: int = 7
 
    # 최소 총 실행 횟수
    min_total_runs: int = 10
 
def check_graduation(
    eval_history: list[dict],
    criteria: GraduationCriteria,
) -> dict:
    """졸업 기준 충족 여부를 확인"""
    import statistics
    from datetime import datetime, timedelta
 
    scores = [h["score"] for h in eval_history]
    dates = [h["date"] for h in eval_history]
    passed = [h["passed"] for h in eval_history]
 
    # 기준 1: 최소 실행 횟수
    enough_runs = len(eval_history) >= criteria.min_total_runs
 
    # 기준 2: 최소 관찰 기간
    if dates:
        observation_days = (max(dates) - min(dates)).days
        enough_days = observation_days >= criteria.min_observation_days
    else:
        enough_days = False
 
    # 기준 3: 연속 통과
    consecutive = 0
    max_consecutive = 0
    for p in passed:
        if p:
            consecutive += 1
            max_consecutive = max(max_consecutive, consecutive)
        else:
            consecutive = 0
    enough_consecutive = max_consecutive >= criteria.min_consecutive_passes
 
    # 기준 4: 평균 점수
    avg_score = statistics.mean(scores) if scores else 0
    high_enough = avg_score >= criteria.min_average_score
 
    # 기준 5: 안정성
    stdev = statistics.stdev(scores) if len(scores) > 1 else 0
    stable_enough = stdev <= criteria.max_score_stdev
 
    ready = all([enough_runs, enough_days, enough_consecutive,
                 high_enough, stable_enough])
 
    return {
        "ready_to_graduate": ready,
        "criteria_met": {
            "min_total_runs": enough_runs,
            "min_observation_days": enough_days,
            "min_consecutive_passes": enough_consecutive,
            "min_average_score": high_enough,
            "max_score_stdev": stable_enough,
        },
        "stats": {
            "total_runs": len(eval_history),
            "average_score": avg_score,
            "stdev": stdev,
            "max_consecutive_passes": max_consecutive,
        },
    }
Tip

졸업 기준은 프로젝트 초기에는 느슨하게 설정하고, 시스템이 성숙해짐에 따라 강화합니다. 초기에 지나치게 엄격한 기준을 설정하면 회귀 테스트 스위트가 비어 있게 되어, 변경의 영향을 전혀 감지할 수 없습니다.

Golden Dataset 관리

Golden Dataset은 회귀 테스트의 기반이 되는 검증된 입력-기대출력 쌍의 집합입니다. 에이전트 테스트에서 Golden Dataset은 전통적인 소프트웨어의 "테스트 픽스처"에 해당합니다.

데이터셋 구조

golden_dataset.py
python
from dataclasses import dataclass, field
 
@dataclass
class GoldenTestCase:
    """Golden Dataset의 개별 테스트 케이스"""
    id: str
    category: str
    input_message: str
    expected_tools: list[str]           # 호출해야 할 도구 목록
    expected_tool_params: dict | None   # 핵심 파라미터
    quality_criteria: list[str]         # LLM-as-Judge 평가 기준
    min_score: float                    # 최소 통과 점수
    reference_response: str | None      # 참조 답변 (선택)
    tags: list[str] = field(default_factory=list)
    created_at: str = ""
    last_passed: str = ""
    pass_rate: float = 0.0
 
@dataclass
class GoldenDataset:
    """회귀 테스트용 Golden Dataset"""
    name: str
    version: str
    test_cases: list[GoldenTestCase]
    metadata: dict = field(default_factory=dict)
 
    def filter_by_category(self, category: str) -> list[GoldenTestCase]:
        return [tc for tc in self.test_cases if tc.category == category]
 
    def filter_by_tags(self, tags: list[str]) -> list[GoldenTestCase]:
        return [
            tc for tc in self.test_cases
            if any(tag in tc.tags for tag in tags)
        ]

데이터셋 YAML 예시

golden_dataset_v1.yaml
yaml
name: customer-support-agent-golden
version: "1.3.0"
metadata:
  agent: customer-support-v2
  created: "2026-03-01"
  last_updated: "2026-03-28"
 
test_cases:
  - id: "CS-001"
    category: "order_inquiry"
    input_message: "주문번호 ORD-12345의 배송 상태를 확인해 주세요"
    expected_tools:
      - "lookup_order"
      - "get_shipping_status"
    expected_tool_params:
      lookup_order:
        order_id: "ORD-12345"
    quality_criteria:
      - "주문번호를 정확히 조회했는가"
      - "배송 상태 정보가 포함되어 있는가"
      - "예상 배송일이 안내되었는가"
    min_score: 0.85
    tags: ["order", "shipping", "core"]
 
  - id: "CS-002"
    category: "refund"
    input_message: "지난주 구매한 상품을 환불하고 싶습니다"
    expected_tools:
      - "get_recent_orders"
      - "check_refund_eligibility"
    quality_criteria:
      - "최근 주문 목록을 조회했는가"
      - "환불 정책을 안내했는가"
      - "환불 절차를 단계별로 설명했는가"
    min_score: 0.80
    tags: ["refund", "core"]

데이터셋 버전 관리

Golden Dataset은 코드와 마찬가지로 버전 관리가 필요합니다.

dataset_versioning.py
python
import hashlib
import json
 
def compute_dataset_hash(dataset: GoldenDataset) -> str:
    """데이터셋의 해시를 계산하여 변경을 추적"""
    content = json.dumps(
        [vars(tc) for tc in dataset.test_cases],
        sort_keys=True,
        ensure_ascii=False,
    )
    return hashlib.sha256(content.encode()).hexdigest()[:12]
 
def track_dataset_change(old_dataset, new_dataset) -> dict:
    """두 데이터셋 버전 간의 차이를 분석"""
    old_ids = {tc.id for tc in old_dataset.test_cases}
    new_ids = {tc.id for tc in new_dataset.test_cases}
 
    return {
        "added": new_ids - old_ids,
        "removed": old_ids - new_ids,
        "modified": {
            tc.id for tc in new_dataset.test_cases
            if tc.id in old_ids and tc != get_by_id(old_dataset, tc.id)
        },
        "unchanged": old_ids & new_ids,
    }

롤링 성공률 모니터링

회귀 테스트의 핵심은 시간에 따른 성공률 추이를 모니터링하는 것입니다. 단일 실행의 성패보다 **추세(Trend)**가 더 중요합니다.

롤링 윈도우 계산

rolling_metrics.py
python
from datetime import datetime, timedelta
from collections import defaultdict
 
class RollingMetrics:
    """롤링 윈도우 기반 성공률 모니터링"""
 
    def __init__(self, results: list[dict]):
        self.results = sorted(results, key=lambda r: r["date"])
 
    def success_rate(self, window_days: int) -> float:
        """지정된 기간의 성공률 계산"""
        cutoff = datetime.now() - timedelta(days=window_days)
        recent = [r for r in self.results if r["date"] >= cutoff]
 
        if not recent:
            return 0.0
 
        passed = sum(1 for r in recent if r["passed"])
        return passed / len(recent)
 
    def by_category(self, window_days: int) -> dict:
        """카테고리별 성공률"""
        cutoff = datetime.now() - timedelta(days=window_days)
        recent = [r for r in self.results if r["date"] >= cutoff]
 
        categories = defaultdict(lambda: {"passed": 0, "total": 0})
        for r in recent:
            cat = r["category"]
            categories[cat]["total"] += 1
            if r["passed"]:
                categories[cat]["passed"] += 1
 
        return {
            cat: data["passed"] / data["total"]
            for cat, data in categories.items()
        }
 
    def detect_regression(
        self,
        baseline_window: int = 30,
        current_window: int = 7,
        threshold_drop: float = 0.10,
    ) -> dict:
        """현재 성공률이 베이스라인 대비 하락했는지 감지"""
        baseline_rate = self.success_rate(baseline_window)
        current_rate = self.success_rate(current_window)
 
        drop = baseline_rate - current_rate
        is_regression = drop >= threshold_drop
 
        return {
            "baseline_rate": baseline_rate,
            "current_rate": current_rate,
            "drop": drop,
            "is_regression": is_regression,
            "message": (
                f"성공률이 {drop:.1%} 하락했습니다 "
                f"({baseline_rate:.1%} -> {current_rate:.1%})"
                if is_regression
                else "정상 범위입니다"
            ),
        }
Warning

10% 하락 플래그는 절대적 규칙이 아니라 시작점입니다. 테스트 케이스 수가 적은 초기에는 단일 실패가 큰 비율 변동을 일으킬 수 있으므로, 최소 샘플 수 조건도 함께 적용해야 합니다.

베이스라인 관리

**베이스라인(Baseline)**은 현재 에이전트의 "정상 성능"을 나타내는 기준값입니다. 모든 회귀 판단은 이 베이스라인과의 비교로 이루어집니다.

베이스라인 갱신 전략

baseline_management.py
python
@dataclass
class Baseline:
    """에이전트 성능 베이스라인"""
    agent_version: str
    created_at: str
    overall_success_rate: float
    category_rates: dict
    average_scores: dict
    sample_size: int
 
    @classmethod
    def from_results(cls, agent_version: str, results: list[dict]):
        """실행 결과로부터 베이스라인 생성"""
        import statistics
 
        passed = sum(1 for r in results if r["passed"])
        scores = [r["score"] for r in results]
 
        # 카테고리별 성공률
        categories = defaultdict(list)
        for r in results:
            categories[r["category"]].append(r["passed"])
 
        return cls(
            agent_version=agent_version,
            created_at=datetime.now().isoformat(),
            overall_success_rate=passed / len(results),
            category_rates={
                cat: sum(vals) / len(vals)
                for cat, vals in categories.items()
            },
            average_scores={
                "mean": statistics.mean(scores),
                "stdev": statistics.stdev(scores),
                "p50": sorted(scores)[len(scores) // 2],
            },
            sample_size=len(results),
        )
 
def should_update_baseline(
    current_baseline: Baseline,
    new_results: list[dict],
    improvement_threshold: float = 0.05,
) -> bool:
    """베이스라인을 갱신해야 하는지 판단"""
    new_rate = sum(1 for r in new_results if r["passed"]) / len(new_results)
    improvement = new_rate - current_baseline.overall_success_rate
 
    # 유의미한 개선이 있을 때만 갱신
    return improvement >= improvement_threshold

변경 영향 분석

프롬프트 변경, 도구 추가, 모델 교체 등의 변경이 에이전트 품질에 미치는 영향을 분석합니다.

impact_analysis.py
python
async def analyze_change_impact(
    agent_before,
    agent_after,
    golden_dataset: GoldenDataset,
    num_runs: int = 3,
) -> dict:
    """변경 전후의 에이전트 성능을 비교 분석"""
 
    before_results = []
    after_results = []
 
    for test_case in golden_dataset.test_cases:
        # 변경 전 에이전트 실행
        for _ in range(num_runs):
            result = await run_test(agent_before, test_case)
            before_results.append({**result, "test_id": test_case.id})
 
        # 변경 후 에이전트 실행
        for _ in range(num_runs):
            result = await run_test(agent_after, test_case)
            after_results.append({**result, "test_id": test_case.id})
 
    # 테스트 케이스별 비교
    comparison = {}
    for tc in golden_dataset.test_cases:
        before = [r for r in before_results if r["test_id"] == tc.id]
        after = [r for r in after_results if r["test_id"] == tc.id]
 
        before_rate = sum(r["passed"] for r in before) / len(before)
        after_rate = sum(r["passed"] for r in after) / len(after)
 
        comparison[tc.id] = {
            "before": before_rate,
            "after": after_rate,
            "delta": after_rate - before_rate,
            "status": (
                "improved" if after_rate > before_rate
                else "degraded" if after_rate < before_rate
                else "unchanged"
            ),
        }
 
    # 전체 요약
    improved = sum(1 for c in comparison.values() if c["status"] == "improved")
    degraded = sum(1 for c in comparison.values() if c["status"] == "degraded")
    unchanged = sum(1 for c in comparison.values() if c["status"] == "unchanged")
 
    return {
        "summary": {
            "improved": improved,
            "degraded": degraded,
            "unchanged": unchanged,
            "total": len(comparison),
        },
        "details": comparison,
        "recommendation": (
            "APPROVE" if degraded == 0
            else "REVIEW" if degraded <= 2
            else "BLOCK"
        ),
    }

자동화 파이프라인

졸업, 실행, 모니터링, 알림을 하나의 자동화 파이프라인으로 통합합니다.

regression_pipeline.py
python
async def regression_pipeline(
    agent,
    golden_dataset: GoldenDataset,
    baseline: Baseline,
) -> dict:
    """회귀 테스트 자동화 파이프라인"""
 
    # 1단계: 회귀 테스트 실행
    results = []
    for tc in golden_dataset.test_cases:
        result = await run_regression_test(agent, tc)
        results.append(result)
 
    # 2단계: 롤링 메트릭 계산
    metrics = RollingMetrics(results)
    regression = metrics.detect_regression()
 
    # 3단계: 베이스라인 비교
    current_rate = sum(r["passed"] for r in results) / len(results)
    baseline_delta = current_rate - baseline.overall_success_rate
 
    # 4단계: 판정
    verdict = "PASS"
    if regression["is_regression"]:
        verdict = "FAIL"
    elif baseline_delta < -0.05:
        verdict = "WARNING"
 
    # 5단계: 리포트 생성
    report = {
        "verdict": verdict,
        "current_success_rate": current_rate,
        "baseline_success_rate": baseline.overall_success_rate,
        "delta": baseline_delta,
        "regression_detected": regression["is_regression"],
        "failed_tests": [r for r in results if not r["passed"]],
        "total_tests": len(results),
    }
 
    return report

정리

이번 장에서는 평가를 회귀 테스트로 전환하는 체계적인 방법론을 다루었습니다.

  • 졸업 패턴은 5회 이상 연속 통과, 높은 평균 점수, 낮은 표준편차를 기준으로 평가를 회귀 테스트로 승격합니다.
  • Golden Dataset은 검증된 테스트 케이스의 집합으로, 코드와 마찬가지로 버전 관리가 필요합니다.
  • 롤링 성공률(7일/30일)을 모니터링하여 10% 이상 하락 시 자동으로 플래그합니다.
  • 베이스라인은 "정상 성능"의 기준점이며, 유의미한 개선이 있을 때만 갱신합니다.
  • 변경 영향 분석은 변경 전후의 성능을 비교하여 승인/검토/차단을 자동 판정합니다.

다음 장 미리보기

7장에서는 회귀 테스트를 넘어 에이전트의 장기적 안정성을 추적하는 메트릭을 다룹니다. 태스크 성공률 추이, 행동 드리프트 감지, 응답 길이 변동, 신뢰도 변화, 지연시간 안정성 등을 측정하고 대시보드로 시각화하는 방법을 살펴봅니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#testing#ai#evaluation#quality-assurance

관련 글

AI / ML

7장: 에이전트 안정성 메트릭

태스크 성공률 추이, 행동 드리프트 감지, 응답 길이 변동, 지연시간 안정성, 비용 변동성 등 에이전트의 장기적 안정성을 추적하는 메트릭과 대시보드 설계를 다룹니다.

2026년 3월 12일·17분
AI / ML

5장: 비결정적 출력 평가

LLM-as-Judge 패턴으로 에이전트의 비결정적 출력을 평가하는 방법, 품질 차원별 점수 산출, 임계값 설정, pass@k 전략을 상세히 다룹니다.

2026년 3월 8일·17분
AI / ML

8장: 에이전트 시뮬레이션과 레드티밍

시뮬레이션 사용자 기반 적대적 테스트, 엣지 케이스 자동 생성, 스트레스 테스트, 안전성 가드레일 검증, 자동 레드티밍 기법을 다룹니다.

2026년 3월 14일·19분
이전 글5장: 비결정적 출력 평가
다음 글7장: 에이전트 안정성 메트릭

댓글

목차

약 16분 남음
  • 학습 목표
  • 평가와 회귀 테스트의 차이
  • 평가에서 회귀 테스트로의 졸업
    • 졸업 기준
  • Golden Dataset 관리
    • 데이터셋 구조
    • 데이터셋 YAML 예시
    • 데이터셋 버전 관리
  • 롤링 성공률 모니터링
    • 롤링 윈도우 계산
  • 베이스라인 관리
    • 베이스라인 갱신 전략
  • 변경 영향 분석
  • 자동화 파이프라인
  • 정리
  • 다음 장 미리보기