AI 코딩 에이전트가 거짓 PASS를 못 내게 하는 테스트 오라클 설계
AI 코딩 에이전트에게 “테스트해봐”라고 시키면, 저품질 산출물에도 태연히 PASS를 찍어 온다. 원인은 에이전트의 불성실이 아니다. 합격이 너무 싸기 때문이다 — 뭉뚱그린 지시는 거의 무엇이든 통과시킨다. 이 글은 공개 npm 패키지 taskery의 테스트 단계를 재설계하며, 테스트 지시문 자체가 거짓말을 물리적으로 막도록 만든 방법을 정리한다. taskery는 AI 코딩 에이전트용 태스크 파이프라인 도구이고, 그 성질상 가드레일이 코드가 아니라 지시문 품질로 작동한다. 그래서 애매한 표현 하나가 그대로 구멍이 된다.
문제 — PASS가 너무 싸다
통증은 반복됐다. 기획은 삐까번쩍하게 나오는데 구현이 저품질로 오고, 그런데도 테스트는 한 방에 통과돼 있다. 결국 사람이 다시 걸러야 하고, 한 태스크에서 “고쳐-다시 검사”만 열 번 넘게 돌기도 했다.
기획은 삐까번쩍하게 해놓고 구현은 저품질로 해서 나한테 가져와. 근데 또 task-test도 한 방에 통과가 되어 있어. 그래서 재순회만 10회 이상씩 하는 거야, 한 태스크에.
근본 진단은 하나다. 합격 기준이 관측 불가능한 소원(“정상 동작”, “잘 됨”)이면 에이전트가 통과를 자기 재량으로 선언할 수 있다.
오라클의 원리 — 자동차 검사 비유
이 문제를 자동차 검사에 빗대면 해법이 선명해진다. “차 굴러가는지 봐”는 99% 합격 가능이라 검사로서 무의미하다. 제대로 된 검사는 장면을 쪼갠다.
제대로 된 방식은 이런 거야. 시속 60까지 밟아서 계기판이 60 뜨는지 봐. 60에서 브레이크 밟아 30미터 안에 서는지 봐. 후진 넣고 뒤로 가는지 봐. 이제 브레이크가 고장났으면 검사원이 합격을 줄 수가 없어. 지시문 자체가 거짓말을 막는 거지.
핵심은 여기 있다. 관측 가능한 장면으로 쪼개는 순간, 산출물이 고장났을 때 검사원이 합격을 줄 방법 자체가 사라진다. 성실성에 기대지 않고, 지시문의 구조가 거짓 PASS를 물리적으로 어렵게 만든다.
테스트 명세 두 규칙
그 원리를 두 규칙으로 못박았다.
- 장면 쪼개기 강제: 한 덩어리 지시는 금지. “이게 고장나면 어떻게 걸리나”를 못 적는 장면은 테스트가 아니라 반려 대상이다. 거의 무조건 통과할 장면도 반려한다.
- 합격 기준은 눈에 보이는 결과로: “정상 동작 / 잘 됨 / 확인한다”는 합격 기준이 아니라 소원이다. 합격 기준은 화면 글자, 응답 코드, 파일 내용, 어서션 통과처럼 관측 가능한 것이어야 한다.
집행은 2겹으로
규칙은 두 겹으로 집행한다. 하나만으로는 약하기 때문이다.
| 겹 | 역할 | 성격 |
|---|---|---|
| 훅 | 금지어·빈칸만 기계적으로 검사해 다음 단계 전환 자체를 차단 | 단순 방어 — 싸지만 얕다 |
| 격리 테스트 세션의 문 앞 검사 | 검증 시작 전에 명세 형태부터 친다. 실행 명령과 구체적 기대값이 없으면 검증을 거부하고 반려 | 의미 판정 — 형태만 보므로 가볍다 |
판정을 세 갈래로 나누다
통과/실패의 이분법도 손봤다. 실패에는 결이 다른 두 종류가 섞여 있었기 때문이다.
- PASS → 테스트 통과로 확정.
- 코드 결함 → “고쳐”로 개발 단계에 되돌린다.
- 시험문제 결함 → 테스트 지시문 자체가 잘못된 경우. 코드와 상태는 보존한 채 지시문만 보수해 재검사한다.
특히 “판정 불가”를 뭉뚱그리지 않고 둘로 갈랐다. 사람의 주관이 필요한 것(시각적 미세 취향·UX 느낌)과 기대값을 구성할 수 없는 것(실행 경로는 있으나 무엇이 정답인지 못 적는 경우)은 전혀 다른 문제다. 후자는 사람이 아니라 지시문 보수로 돌아가야 한다.
시각 산출물은 스스로 캡처해 대조하게
UI 산출물에는 함정이 하나 더 있었다. 승인 시점에 이미 목업-캡처 대조가 끝났으니, 같은 대조를 테스트에서 반복하는 건 무의미하다. 진짜 요구는 “사람에게 가져오기 전에 구현이 기획대로 됐는지 스스로 시각 테스트를 거치고 오는 것”이다.
그래서 격리 테스트 세션이 헤드리스 Chrome으로 실제 화면을 캡처해 승인 목업과 자동 대조하도록 했다 — 요소 빠짐·배치·색·크기·깨진 레이아웃을 잡는다. 개발 단계에서 하면 병목이라 테스트 단계에 배치했다.
실제로 걸러지는지 재봤다
설계가 도는지는 별도 fixture 프로젝트에서 실측했다. 세 종류를 나란히 돌려 대조했다.
| fixture | 무엇을 심었나 | 기대 판정 |
|---|---|---|
| broken | 헤더색·필드 배치·저장 버튼 부재 | 캡처 대조로 시각 FAIL |
| wishy | 소원형(관측 불가) 명세 | 시험문제 결함으로 반려 |
| good | 정상 구현 | PASS |
셋 다 기대대로 갈렸다. 재밌는 곁가지도 있었다. 검증 명령이 최신 Node(v23)에서 디렉토리 경로를 모듈로 해석해 엉뚱하게 실패했는데, 이 실패가 “새 테스트 식별자가 grep에 하나도 안 잡히면 미실행으로 간주” 룰과 맞물려 검증 명령 게이트를 정확히 걸러냈다. 오라클 강제가 실제로 작동함을 우연히 방증한 셈이다.
재진술 — 신뢰의 급소는 반증 가능성이다
즉, AI에게 “테스트해”라고 시킬 때 신뢰의 급소는 에이전트의 성실성이 아니라 지시문의 반증 가능성이다. 고장이 어떻게 걸리는지 못 적는 명세는 테스트가 아니다.
- 합격 기준을 관측 가능한 것으로 강제하면, 통과를 재량으로 선언할 여지가 사라진다.
- 집행은 단순 방어와 의미 판정을 겹쳐야 한다. 하나로는 약하다.
- “판정 불가”는 주관 검수와 시험문제 결함으로 나눠야 재검사 루프가 올바른 곳으로 돌아간다.
한 가지는 정직하게 남긴다. 이 방법도 에이전트가 규칙 자체를 안 따르는 행동까지 막지는 못한다. 정확한 주장은 “거짓 PASS를 물리적으로 어렵게 만든다”까지이고, 완벽 차단으로 부풀리지 않는다.
참고 자료
- [1] @angar2/taskery (npm)↗