실제로 버그를 잡아내는 LLM 테스트 케이스를 생성하는 방법
요약
LLM 에이전트를 위한 테스트 케이스 생성 시 발생하는 노이즈 문제를 해결하고, 실제 버그를 잡아낼 수 있는 고품질 테스트 세트를 구축하는 전략을 다룹니다. 단순히 대량 생성하는 것에 그치지 않고, 엄격한 필터링과 페르소나, 시나리오, 적대적 설계를 통해 테스트의 유효성을 확보하는 방법을 제시합니다.
핵심 포인트
- 단순 생성보다 생성된 데이터의 90%를 버리는 엄격한 필터링 과정이 핵심임
- 페르소나, 시나리오, 적대적 버전을 결합하여 테스트 커버리지를 확보해야 함
- 의미 기반 중복 제거와 스타일 붕괴 확인을 통해 데이터의 다양성을 유지해야 함
- 합성 데이터가 실제 사용자 트래픽의 다양성을 반영하는지 반드시 검증해야 함
우리 에이전트(agent)를 위한 테스트 케이스를 직접 작성하는 것에 지쳐서, 저는 2026년답게 당연한 일을 했습니다. LLM을 우리 문서(docs)에 연결하고 테스트 케이스를 생성하도록 요청한 것입니다. 오후 한나절 만에 수천 개의 테스트 케이스를 얻었습니다. 엄청나게 생산적이라고 느꼈습니다.
그것들은 매 릴리스(release)마다 CI를 통과했습니다. 하지만 프로덕션(production)은 계속해서 망가졌습니다.
마침내 자세히 살펴보니, 생성된 테스트의 약 3분의 1은 잘못되었거나, 거의 중복되었거나, 실제 사용자가 결코 묻지 않을 내용들이었습니다. 테스트 세트가 너무 노이즈(noisy)가 많아서 실제 회귀(regression)를 잡아낼 수 없었고, 따라서 초록색 체크 표시(green check)는 아무런 의미가 없었습니다. 생성하는 것은 쉬운 부분이었습니다. 제가 건너뛰었던 부분, 즉 대부분을 버리는 과정이 바로 전체 작업의 핵심이었습니다.
다음은 실제로 무언가를 잡아내는 자동 생성 테스트를 만들며 배운 내용입니다. 제가 이 문장을 문신으로 새기고 싶을 정도입니다: 필요한 양의 10배를 생성하고, 엄격한 필터(filter)를 통과하여 살아남은 10번째 것만 남기세요.
AI 생성 테스트 세트가 보통 쓸모없는 이유
두 가지 문제가 발생하며, 저는 두 가지 모두를 겪었습니다.
첫 번째는 실제 필터가 없다는 것입니다. 수천 개를 생성하고,
- 페르소나 (Personas). 동일한 요청이라도 사람마다 다릅니다. 스마트폰을 사용하는 서두르는 사용자와 노트북을 사용하는 신중한 전문가는 동일한 질문을 완전히 다른 방식으로 표현하며, 여러분의 모델은 이 두 가지를 모두 처리할 수 있어야 합니다. 한 가지 주의할 점은, 페르소나를 모델의 상상력이 아닌 실제 사용자 데이터로부터 구축해야 한다는 것입니다. 그렇지 않으면 만화 캐릭터 같은 스테레오타입(예: "좌절한 사용자"가 전형적인 틀에 박힌 모습으로 변함)이 만들어집니다.
- 시나리오 (Scenarios). 한 줄짜리 질문이 아니라, 도구 호출 (tool calls), 막다른 길, 그리고 복구 과정을 포함하는 전체 대화여야 합니다. 이것만이 세 번째 턴에서 문맥 (context)을 놓치는 단계나 잘못된 형태 (shape)를 반환하는 도구를 잡아낼 수 있는 유일한 방법입니다.
- 적대적 버전 (Adversarial versions). 일반적인 케이스를 가져와 더 어렵게 만드세요. 단계를 추가하거나, 모호하게 만들거나, 규칙을 우회하려는 사람처럼 질문을 구성합니다. 실제 트래픽에서도 이런 사례가 나타나지만, 이를 기다리기에는 너무 느리게 발생하므로 직접 제조해야 합니다.
하나의 축만 실행하면 다른 축들이 무너집니다. 세 가지를 모두 실행해야만 실제로 지표로 제시할 수 있는 커버리지 (coverage)를 얻을 수 있습니다.
필터가 핵심입니다
이 부분은 모두가 건너뛰지만, 가장 중요한 부분입니다. 생성한 후, 저는 모든 데이터를 몇 가지 단계에 통과시키며 대부분을 버릴 것을 예상합니다.
- 텍스트가 아닌 의미에 따라 중복을 제거하세요. 두 질문의 표현이 다르더라도 여전히 동일한 테스트일 수 있습니다. 의미 기반 매칭은 정확한 텍스트 매칭 (exact-text matching)이 놓치는 의역된 문장들을 잡아냅니다.
- 모든 후보를 심사하고 실패하는 것을 거부하세요. 이것이 실제 사용자가 물어볼 법한 내용인가? 예상되는 답변을 실제로 복구할 수 있는가? 단순히 시드 (seed)를 말만 바꾼 복사본인가?
- 배포하기 전에 스타일 붕괴 (house-style collapse)가 있는지 확인하세요. 만약 합성된 데이터 세트가 실제 트래픽보다 훨씬 덜 다양하다면, 그것은 통과 신호가 아니라 다시 생성해야 한다는 신호입니다.
- 작은 샘플을 직접 확인하세요. 5% 정도는 직접 라벨링 (labeling) 하세요. 그 샘플의 품질이 나쁘다면 해당 배치 (batch) 전체를 버리십시오.
처음 이 과정에서 후보의 80%가 거절되는 것을 보았을 때, 저는 움찔했습니다. 하지만 그 후, 데이터 세트를 오염시키기 전에 전체의 3분의 1이 잘못된 배치를 잡아내는 것을 보고 나서, 저는 더 이상 움찔하지 않게 되었습니다.
생성 모델과 다른 계열의 모델을 판정자로 사용하세요
이것은 제가 얻은 가장 유용한 통찰이며, 따라서 별도의 섹션으로 다룹니다.
만약 동일한 모델이 테스트를 작성하고 채점까지 한다면, 그 모델은 자신의 나쁜 습관에 대해 눈이 멀게 됩니다. 모델은 자신만의 어휘적 특이점(vocabulary quirks), 정형화된 페르소나(stereotyped personas), 지름길 추론(shortcut reasoning)을 공유하므로, 자신이 생성한 쓰레기를 그대로 승인(rubber-stamps)해 버립니다. 다른 계열의 모델을 판정자로 사용하면 이러한 사각지대를 공유하지 않으며, 인간 검토자와 훨씬 더 자주 일치합니다.
따라서 저는 한 모델로 생성하고 다른 모델로 판정하거나, 각 배치(batch)를 채점하는 모델을 교체합니다. 이는 작은 변화이지만, 신뢰할 수 있는 필터와 자신의 실수를 조용히 승인하는 필터 사이의 결정적인 차이를 만듭니다.
의도적으로 공간을 커버하세요, 그렇지 않으면 쉬운 사례만 과하게 샘플링하게 됩니다
세 개의 축을 사용하더라도, 제약 없는 생성(unconstrained generation)은 쉽고 명백한 사례들에 쌓이게 됩니다. 그래서 저는 의도(intent) x 난이도(difficulty) x 분위기(mood) x 방언(dialect)과 같은 간단한 그리드(grid)를 구성하고, 각 셀(cell)마다 작은 할당량(quota)을 설정합니다.
이유는 단순하지만 중요합니다. 그리드에서 빈 셀은 눈에 보이기 때문에, 공백을 확인하고 채울 수 있습니다. 하지만 무작위로 쌓인 더미 속의 공백은 실제 운영 사고(production incident)로 나타나기 전까지는 보이지 않습니다. 저는 제 제품에 의미가 없는 셀은 버리고 나머지를 채웁니다.
합성 데이터 세트를 신뢰하기 전에 실제 데이터와 대조하세요
합성 테스트(Synthetic tests)는 현실과 닮았을 때만 가치가 있습니다. 따라서 세트를 신뢰하기 전에 세 가지 빠른 점검을 수행합니다.
- 합성 데이터 세트가 실제로 실제 트래픽(real traffic) 근처에 위치하는가, 아니면 공간의 자신만의 구석에 떨어져 있는가?
- 만약 모델이 라벨(labels)까지 작성했다면, 라벨이 생성기(generator)의 편향(bias)을 담고 있지 않은지 확인하기 위해 작은 샘플을 수동으로 다시 라벨링합니다.
- 만약 세트가 미세 조정(fine-tuning)을 위한 것이라면, 합성 데이터로 학습한 후 실제 데이터로 테스트합니다. 만약 합성 데이터에서만 결과가 좋다면, 생성기가 제 모델이 최적화(optimising)하게 될 가상의 세계를 만들어낸 것입니다.
작업마다 필요한 필터 강도가 다릅니다
동일한 루프는 세 가지 형태로 배포되며, 실제로 변하는 유일한 것은 필터링을 얼마나 엄격하게 하느냐입니다.
- **평가 세트 (An eval set)**는 가장 엄격한 필터를 적용받습니다. 평가 세트가 정확하지 않다면 당신의 CI 게이트는 보여주기식(theatre)에 불과합니다. 수천 개의 노이즈 섞인 케이스보다 수백 개의 훌륭한 케이스가 더 낫습니다.
- **미세 조정 세트 (A fine-tuning set)**는 더 많은 양과 약간 더 느슨한 필터를 수용할 수 있습니다. 학습 과정에서 노이즈가 평균화되기 때문입니다. 하지만 필터를 유지해야 합니다. 그렇지 않으면 모델이 생성기 (generator)의 말투를 영원히 물려받게 됩니다.
- **레드팀 세트 (A red-team set)**는 필터를 반대로 뒤집습니다. 이제 "너무 어려운 것"이 목표가 됩니다. 지저분한 케이스들은 유지하되, 현실적이지 않은 공격들만 거부합니다.
동일한 생성기, 동일한 판정자입니다. 당신은 그저 엄격함의 다이얼을 돌리고 있을 뿐입니다.
자동 생성된 테스트 세트를 조용히 망가뜨리는 실수들
- 하나의 모델이 테스트를 작성하고 채점까지 모두 하게 두는 것.
- 스타일 붕괴 (style collapse)를 전혀 확인하지 않아, 몇 달이 지나서야 문제가 나타나게 하는 것.
- 취약한 고위험 구간을 숨겨버리는 하나의 전체 점수만을 신뢰하는 것.
- 각 테스트가 어디에서 왔는지 추적하지 않아, 문서가 변경되었을 때 재검토할 수 없게 만드는 것.
- 생성을 인간의 판단을 대체하는 것으로 취급하는 것. 생성은 양(volume)의 문제이지 판단의 문제가 아닙니다. 리스크가 큰 케이스에는 여전히 인간이 필요합니다.
- 한 번만 실행하는 것. 실제 트래픽은 표류(drift)하며, 봄에 생성한 세트는 가을이 되면 구식이 됩니다.
제가 계속해서 강조하는 점은, 테스트를 생성하는 것은 결코 어려운 부분이 아니며, 모든 툴링(tooling)이 자랑하고 싶어 하는 부분이 바로 그 지점이라는 것입니다. 진짜 어려운 부분은 생성된 것의 대부분을 기꺼이 버릴 수 있는 용기를 갖는 것이며, 다른 모델이 결정권을 갖도록 하는 것입니다.
필터링 측면, 판정 루브릭 (judge rubric), 중복 제거 임계값 (dedupe cutoffs), 스타일 붕괴를 잡아내는 체크 항목 등에 대해 더 깊이 알고 싶다면, 이 글이 좋은 자료가 될 것입니다.
만약 이런 방식으로 테스트를 생성해 보셨다면, 여러분의 유지율 (keep rate)이 어느 정도인지 정말 알고 싶습니다. 제 경우는 10개 중 1개 정도로 수렴했는데, 다른 분들은 더 높은 수치를 얻는지, 아니면 모두가 조용히 생성된 것의 대부분을 버리고 있는지 진심으로 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기