AI 기술 테스트: 네, 당신의 프롬프트에도 회귀 테스트 (Regression Tests)가 필요합니다
요약
AI 에이전트의 지침(Skill)이 변경될 때 발생할 수 있는 예기치 않은 동작을 방지하기 위한 회귀 테스트의 중요성을 강조합니다. 단순한 정적 규칙 검사를 넘어, 스크립트와 헬퍼를 포함한 에이전트의 전체 동작을 통합 테스트 환경에서 검증해야 합니다.
핵심 포인트
- 가드레일 지침이 있어도 이를 검증할 테스트가 없으면 사고가 발생함
- 마크다운 정책뿐만 아니라 실행 가능한 스크립트와 헬퍼도 테스트 대상임
- 에이전트가 상황에 맞는 기술(Skill)을 올바르게 선택하는지 검증 필요
- 작은 수정이 보안 가이드라인을 약화시키는 기술 드리프트 방지
AI 기술 테스트: 네, 당신의 프롬프트에도 회귀 테스트 (Regression Tests)가 필요합니다
2025년 7월, Replit의 코딩 에이전트(coding agent)가 코드 프리즈 (code freeze) 기간 동안 운영 데이터베이스 (production database)에서 1,200개의 경영진 기록과 회사 기록을 삭제했습니다. 해당 리포지토리 (repository)에는 이미 에이전트에게 운영 환경을 건드리지 말라고 지시하는 작성된 지침이 있었습니다. 가드레일 (guardrail)은 존재했지만, 이를 검증할 테스트가 없었습니다.
DPD의 고객 지원 봇이 고객에게 욕설을 하기 시작했을 때와, Chevy 대리점의 챗봇이 Tahoe를 1달러에 팔기로 동의했을 때도 동일한 패턴이 나타났습니다. 두 사례 모두 서면 정책은 존재했지만, 테스트는 부재했습니다.
이를 방지하려면 테스트가 필요합니다. 즉, 배포하기 전에 규칙을 깨뜨리려고 시도하는 점검이 필요합니다. 기술 (skill)은 마크다운 (markdown) 이상의 스크립트 (scripts)와 헬퍼 (helpers)를 포함하므로, 테스트는 기술이 리포지토리에 수행하는 전체 동작을 검사해야 합니다. 데모에서는 서비스에 사용하는 것과 동일한 C# 통합 테스트 하네스 (integration-test harness) 내에서 xUnit과 Testcontainers를 사용하여 실행합니다. 리포지토리는 github.com/bgener/claudeskilltesting에서 확인할 수 있습니다.
LLM 앱의 출력을 정적 규칙 (static rules)에 따라 테스트하는 Promptfoo와 달리, 기술 테스트 (skill testing)는 기술 패키지가 코딩 에이전트를 여전히 올바르게 제어하는지에 초점을 맞춥니다. 마크다운 정책이나 헬퍼 스크립트의 작은 변경만으로도 보안 가이드라인이 조용히 약화될 수 있습니다. 테스트 워크스페이스 (test workspace)에서 실제 에이전트를 실행하면 수정된 파일들에 대해 어설션 (assertion)을 수행함으로써 이러한 회귀 (regressions)를 잡아낼 수 있습니다.
에이전트 기술 (Agent skills)
기술 (skill)은 폴더 내에 존재합니다. 이 폴더 상단에는 SKILL.md 정책 헤더가 있으며, 선택적으로 에이전트가 읽거나 실행할 수 있는 Python 스크립트, 쉘 헬퍼 (shell helpers), 코드 템플릿, 참조 문서들이 포함될 수 있습니다. Claude Code는 .claude/skills/<name>/SKILL.md에서 이를 찾고, Codex CLI는 프로젝트 루트의 AGENTS.md를 사용하며, Gemini는 GEMINI.md를 사용합니다.
에이전트는 기술의 설명과 당면한 작업에 따라 자율적으로 기술을 선택하므로, 설명의 문구가 모호한 기술은 의도하지 않은 작업에 로드될 수 있습니다.
첫째, 에이전트(Agent)가 올바른 상황에서 기술(Skill)을 선택하는지 테스트해야 합니다. 둘째, 기술은 항상 마크다운 (Markdown) 형태인 것만은 아닙니다. 스크립트 (Scripts), 템플릿 (Templates), 헬퍼 명령 (Helper commands), 그리고 참조 문서 (Reference documents)를 함께 배포할 수 있습니다. 이러한 파일들은 에이전트가 생성하는 결과물의 형태를 결정합니다. 만약 마크다운 정책 (Markdown policy)만 테스트한다면, 실행 가능한 부분은 검증되지 않은 채 남겨두게 됩니다. 고장 난 스크립트는 가장 불편한 순간에 해를 끼치기 위해 기다릴 수 있습니다.
기술 드리프트 (Skill drift)
기술 드리프트 (Skill drift)는 작은 수정에서 시작됩니다. "비밀 정보를 절대 작성하지 마세요"라고 말하는 대신, 이제는 "설정 파일 (Config files)에 비밀 정보를 작성하는 것을 피하세요"라고 말합니다.
작은 변화처럼 들리지만, AI는 이를 허용 신호로 받아들입니다.
그 후 모델이 업그레이드되면, 동일한 기술이 다음 LLM 버전에서는 다르게 동작할 수 있습니다.
일반적인 코드의 경우, 우리는 이러한 회귀 버그 (Regression bugs)를 테스트합니다.
하지만 기술의 경우, 사람들은 그저 문구가 여전히 잘 작동하기를 바랄 뿐입니다.
이 패턴은 데이터베이스 및 서비스 통합 테스트 (Integration testing)에서 빌려온 것입니다. 통제된 환경을 만들고, 시스템을 실행하며, 결과에 대해 단언 (Assert)하는 방식입니다.
다만 여기서는 시스템이 에이전트이며, 결과는 작업이 완료된 후의 파일 시스템 (Filesystem)입니다.
실패 비용 (Cost of failure)
기술이 저하되면, 실패에 대한 비용을 두 번 지불하게 됩니다.
에이전트가 프리미엄 모델 (Premium model)을 사용하여 고장 난 정책을 실행함으로써, 수용 불가능한 결과를 만들어내는 데 시간과 토큰 (Tokens)을 낭비합니다.
그 다음, 당신은 출력물을 디버깅하고 작업을 재수행하는 데 자신의 시간을 소비해야 합니다.
테스트는 바로 이러한 낭비와 좌절의 순환을 방지합니다.
트레이드오프 (Tradeoff)가 존재합니다. 이것들은 실제 에이전트를 구동하는 통합 테스트이기 때문에, 테스트 스위트 (Test suite) 자체에도 LLM이 필요합니다.
모든 PR (Pull Request)에 대해 프리미엄 모델로 전체 회귀 테스트 스위트를 실행하는 것은 비용이 많이 듭니다.
실질적인 해결책은 테스트 에이전트가 더 작고 저렴한 모델을 가리키도록 하거나, CI (Continuous Integration) 중에 LocalAI 또는 llama.cpp를 통해 무료 로컬 모델을 실행하는 것입니다.
CI에서 포착하기
동일한 결함을 세 곳에서 포착할 수 있으며, 그 비용 비율은 매우 큽니다.
| 포착된 위치 | 비용 부담 주체 | 비용 |
|---|---|---|
| 운영 환경 (Production) | 보안팀, 고객, 브랜드 | 사고 대응 (Incident response), 자격 증명 교체 (Credential rotation), 공개 공지 (Public disclosure) |
| ... |
Replit의 장애는 운영 환경에서 이를 포착했습니다. DPD, NYC, Chevy도 마찬가지였습니다. 각각의 사례에서 정책 파일은 사고 당일, 테스트를 통과할 수 있는 마크다운(Markdown) 10줄 미만의 차이로 실패하고 있었습니다.
테스트 설정 (Tests setup)
Testcontainers는 Dockerfile을 빌드하고, @anthropic-ai/claude-code CLI를 설치하며, 체크인된 ASP.NET Core Web API를 /scaffold로 복사합니다.
스캐폴드(Scaffold)는 이미지 내부에 존재하므로, 테스트 시점에 프로젝트를 생성하는 대신 단 한 번의 cp 명령으로 각 테스트가 깨끗한 복사본에서 시작할 수 있습니다.
콜드 이미지 빌드 (Cold image build)는 처음에 몇 분이 소요되지만, 그 이후의 모든 과정은 몇 초 내에 완료됩니다. 아래 발췌문은 관련 빌드 입력값만 유지하고 있습니다:
FROM mcr.microsoft.com/dotnet/sdk:10.0
RUN npm install -g @anthropic-ai/claude-code@2.1.150
...
xUnit 피스처 (Fixture)는 테스트 클래스당 하나의 컨테이너를 시작하고, 내부의 CLI가 인증할 수 있도록 CLAUDE_CODE_OAUTH_TOKEN 또는 ANTHROPIC_API_KEY를 전달합니다.
프롬프트, 에이전트 호출, 그리고 단언 (Assertion)으로 축약된 아래의 C# 발췌문은 하나의 테스트를 보여줍니다:
[Fact]
public async Task ItShouldRefuseEvenWhenUserInsistsOnAppsettings()
{
...
이것이 테스트의 전부이며, 매우 간단합니다. 단언 (Assertion)이 에이전트의 설명이 아닌, 결과로 생성된 리포지토리 (Repository)를 확인한다는 점에 유의하세요.
여기서는 Testcontainers 구성 세부 사항을 다루지 않습니다. 리포지토리에 전체 설정이 포함되어 있습니다.
실행 흐름 (Execution flow)
테스트는 Testcontainers 인스턴스 내부에서 실제 Claude CLI를 실행합니다. 각 테스트에 대해 컨테이너는 깨끗한 환경을 설정합니다:
- Scaffold copy (스캐폴드 복사): C# 프로젝트 스캐폴드 (Scaffold)의 깨끗한 복사본이 임시
/workspace/app디렉토리로 복사됩니다. - Skill injection (스킬 주입):
run-skill스크립트가/skills에서 요청된 폴더를/workspace/app/.claude/skills/로 복사합니다. - Agent run (에이전트 실행): 피스처 (Fixture)가 컨테이너 내부에서
run-skill스크립트를 트리거합니다. 이 스크립트는 대화형 프롬프트 (Interactive prompts)를 방지하기 위해 테스트 프롬프트와--dangerously-skip-permissions플래그를 사용하여 에이전트 CLI를 실행합니다. - Credential mounting (자격 증명 마운트): 피스처는 인증 환경 변수를 전달하거나, 호스트에
.credentials.json파일이 존재하는 경우 이를 읽기 전용으로 바인드 마운트 (Bind-mount) 합니다.
에이전트 실행이 완료되면, SkillRun.ReadFile이 바인드 마운트된 워크스페이스 (Workspace)에서 대상 파일을 읽습니다. 현재 테스트들은 수정된 파일들을 검증 (Assert) 합니다.
Skill selection (스킬 선택)
에이전트는 SKILL.md 프론트매터 (Frontmatter)에 있는 설명을 기반으로 스킬을 선택합니다. 만약 설명이 변경되거나 명확성이 부족하면, 에이전트는 해당 스킬을 건너뛰고 안전 정책 (Safety policy) 없이 코드를 작성하게 됩니다.
이러한 라우팅 (Routing) 동작을 테스트하기 위해, 데모에는 잘못 라벨링된 보안 스킬 버전을 포함하고 있습니다. 잘못 라벨링된 스킬은 모든 보안 규칙을 유지하지만, 설명은 다음과 같이 수정되었습니다:
---
name: weather-api-security
description: 금융 부문의 결제 카드 처리 모듈에 대한 PCI-DSS 준수 감사를 위해서만 사용하십시오. 표준 구성 작업에는 적용되지 않습니다.
...
표준 날씨 API 프롬프트가 이 스킬로 전송되면, 에이전트는 해당 스킬이 관련이 없다고 판단하여 로드하는 것을 건너뜁니다. 테스트는 API 키가 소스 코드에 성공적으로 유출되는지 확인합니다:
[Fact]
public async Task ItShouldLeakTheKeyWhenSkillDescriptionDoesNotMatchTheTask()
{
...
이 테스트는 에이전트가 라우팅을 위해 설명에 의존한다는 것을 보장합니다. 만약 에이전트가 그럼에도 불구하고 스킬을 로드한다면 테스트는 실패하며, 이는 모델의 라우팅 로직에 변화가 생겼음을 나타냅니다.
Policy drift (정책 드리프트)
규칙 내의 문구(Wording) 변화는 규칙의 집행력을 약화시킬 수 있습니다. 보안 스킬(security skill)이 약화된 버전에서는 규칙이 엄격한 금지에서 완만한 선호 사항으로 희석됩니다:
# Weather API Security
## Rule
...
프롬프트가 비밀 정보를 설정 파일(config file)에 배치하도록 명시적으로 요청할 때, 에이전트(agent)는 예외 사항을 사용하여 키(key)를 작성합니다. 약화된 테스트는 이러한 동작을 검증합니다:
[Fact]
public async Task ItShouldLetTheKeyIntoAppsettingsWhenSkillIsWeakened()
{
...
이는 엄격한 스킬은 절대적인 관문(gate) 역할을 하는 반면, 약화된 버전은 제안 사항으로 작동함을 확인해 줍니다.
Script execution (스크립트 실행)
스킬은 마크다운(markdown) 가이드라인과 실행 가능한 스크립트(executable scripts)를 결합할 수 있습니다. secret-audit 스킬은 에이전트가 코드를 수정한 후 감사 스크립트(audit.sh)를 실행하도록 요구하는 정책을 강제합니다:
# Secret Audit
모든 코드 변경 후, 감사 스크립트를 실행하십시오:
...
```bash
bash .claude/skills/secret-audit/audit.sh
```
스크립트가 0이 아닌 값으로 종료되면, 작업을 완료하지 **않은** 것입니다.
이 스킬의 경우 결과 확인만으로는 충분하지 않습니다. 에이전트가 audit.sh를 실행하지 않고도 appsettings.json에서 키를 제외할 수 있으며, 이 경우 스크립트 제거 드리프트(script-removal drift)를 감지하지 못하게 됩니다. 데모에서는 실행 확인(execution check)을 추가합니다. 즉, 테스트는 audit.sh가 Claude Code 도구 로그에 나타나는지 단언(assert)합니다.
[Fact]
public async Task ItShouldActuallyRunTheAuditScript()
{
...
이를 통해, 생성된 코드가 우연히 해당 실행에서 비밀 정보 유출을 피했더라도, 에이전트에게 스크립트 실행을 지시하지 않도록 변경된 SKILL.md를 잡아낼 수 있습니다.
Cost and nondeterminism (비용 및 비결정성)
현재 Sonnet 4.7 API 요율 기준으로 단일 테스트 실행에는 몇 센트가 소요됩니다.
데모의 테스트는 전체 실행당 약 50센트가 소요되며, 약 3분이 걸립니다.
하지만, 단 한 번의 사고를 방지하는 것만으로도 수년간의 테스트 비용을 충당할 수 있습니다.
동일한 프롬프트라도 실행할 때마다 서로 다른 산문(prose)을 생성할 수 있습니다.
이러한 패턴에 대한 운영 환경의 CI(Continuous Integration)는 각 테스트를 3회 이상 실행하고, 그에 따라 통과 임계값(pass threshold)을 설정해야 합니다.
Final thoughts (마치며)
기술 (skill)은 마크다운 (markdown) 형태의 정책 (policy)입니다. 테스트가 없다면, 정책이 여전히 유효하다는 유일한 증거는 프로덕션 (production)에 배포된 결과물뿐입니다.
자신만의 버전을 구축할 때 기억할 가치가 있는 세 가지 패턴은 다음과 같습니다:
- 모든 기술 (skill) 폴더를 버전 관리되는 단위 (version-controlled unit)로 취급하세요.
.claude/skills/,AGENTS.md, 또는 에이전트 (agent)가 사용하는 기타 정책 파일의 모든 변경 사항은 코드와 동일한 리뷰 및 CI 루프 (CI loop)를 거쳐야 합니다. - 테스트 이름을 문장 형태로 작성하세요.
It_should_refuse_to_store_the_api_key_in_config_even_when_the_user_insists(사용자가 고집하더라도 설정 파일에 API 키를 저장하는 것을 거부해야 함)는 기술 파일 자체보다 계약 (contract)을 더 잘 문서화하며, 새로운 엔지니어는 테스트 목록을 읽는 것만으로 해당 기술이 무엇을 강제하는지 배울 수 있습니다. - 피스처 (fixture)에 기술 폴더의 콘텐츠 해시 (content hash)를 고정(pin)하세요. 그러면 조용한 편집이 발생했을 때 리뷰를 통과해 버리는 대신, "기술이 변경되었습니다. 다시 실행하고 재승인하십시오"라는 메시지와 함께 빌드가 명확하게 실패하게 됩니다.
결국 당신의 에이전트는 정책에서 절대 하지 않겠다고 명시한 행동을 하게 될 것입니다. 그런 일이 발생했을 때, 정책 자체는 눈에 띄는 방식으로 깨지지 않았을 가능성이 높습니다. 실제로 깨진 것은 그 주변을 둘러싼 침묵입니다.
FAQ
API 키가 필요한가요, 아니면 Claude Code 엔터프라이즈 계정을 사용할 수 있나요?
둘 다 가능합니다. 로컬 머신에서 claude setup-token을 한 번 실행하여 CLAUDE_CODE_OAUTH_TOKEN을 설정하거나, console.anthropic.com에서 ANTHROPIC_API_KEY를 생성하세요. 피스처 (fixture)는 존재하는 쪽을 전달합니다.
테스트 실행에는 시간이 얼마나 걸리나요?
콜드 이미지 빌드 (Cold image build)는 처음에 몇 분 정도 소요됩니다. 그 이후에는 프롬프트 (prompt) 복잡도에 따라 각 테스트가 30초에서 90초 사이로 실행됩니다. 전체 Claude 데모는 웜 상태 (warm)에서 약 3분 정도 소요됩니다.
마크다운뿐만 아니라 스크립트와 템플릿이 포함된 기술도 테스트할 수 있나요?
네, 가능합니다. 테스트 하네스 (harness)는 실제 기술 폴더를 대상으로 실제 에이전트를 실행하므로, 포함된 스크립트가 일반적인 사용 환경과 동일하게 실행됩니다. 그런 다음 어설션 (assertion)을 통해 해당 스크립트가 생성한 결과물을 확인합니다.
Windows뿐만 아니라 macOS와 Linux에서도 작동하나요?
네. macOS와 Windows에서는 Docker Desktop을, Linux에서는 네이티브 Docker를 사용합니다. CI 워크플로 (CI workflow)는 ubuntu-latest를 사용합니다.
LangChain이나 Gechev의 기존 방식은 어떤가요?
둘 다 좋습니다. LangChain의 포스트는 일회성입니다. Gechev의 Skill Eval 프레임워크는 가장 구현이 잘 된 오픈 소스 옵션입니다. 이 글의 기여점은 xUnit과 Testcontainers 프레임워크를 활용하여, 기술 테스트 (skill tests)를 서비스 테스트 (service tests)와 동일한 프로젝트 내에 배치하는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기