94%. 숫자만 보면 훌륭하다. 실제로 AI가 생성한 테스트 스위트가 이 커버리지를 찍었다. 20개 테스트, 몇 초 만에. 팀 리드 입장에서 보면 '이거 쓸 만한데?'라는 생각이 드는 순간이다.
그런데 코드를 딱 한 줄 바꿨더니, 그 테스트가 전혀 눈치채지 못했다.
커버리지는 거짓말을 하지 않는다—하지만 진실도 말하지 않는다
dev.to에 올라온 사례가 이 문제를 정확하게 짚는다. CSV 파싱 라이브러리에 AI로 테스트를 생성한 개발자는 coverage.py로 94% 라인 커버리지를 확인했다. 그리고 뮤테이션 테스트를 돌렸다.
뮤테이션 테스트는 단순하다. 코드를 살짝 망가뜨려서, 테스트가 그걸 잡는지 확인하는 것이다. 이 개발자는 row.get('Course') 조건을 row.get('Course') or True로 바꿨다. 이제 Course 키가 없는 행도 필터 없이 통과된다. 버그다. 그런데 테스트는 전부 통과했다. 커버리지 리포트? 여전히 94%.
왜 이런 일이 벌어졌냐. AI 모델은 소스 코드를 보고 테스트를 생성한다. row.get('Course')라는 코드를 봤을 때, 모델이 추론한 것은 "이 키는 보통 있겠지"였다. .get()이 KeyError 방지를 위한 방어 코드라는 의도를 읽지 못한 것이다. 결과적으로 생성된 모든 픽스처에는 Course 컬럼이 포함되어 있었다. 그 키가 아예 없는 케이스를 테스트하는 코드는 한 줄도 없었다.
모델은 코드가 힌트를 주는 방향대로만 테스트를 썼다. "이 키가 없으면 어떻게 되지?"라는 질문은 하지 않았다.
AI 에이전트가 '완료'라고 말할 때
이 문제는 테스트 생성에만 국한되지 않는다. AI 코딩 에이전트의 자기 검증 문제는 구조적으로 더 심각하다.
Octomind 개발자가 버전 0.44.2 릴리즈 노트에서 직접 고백한 내용이 있다. 에이전트가 5개 파일을 편집해야 하는 작업을 받았는데, 3개만 수정하고 "완료"를 반환했다. 더 나쁜 건, 검증 방식이었다. 에이전트는 자신이 방금 작성한 코드를 다시 읽으면서 "코드가 올바르게 보인다"고 판정했다. 자기가 쓴 코드를 자기가 채점한 것이다.
이게 왜 문제냐. 모델은 자신이 생성한 결과물에 대한 확증 편향을 피하기 어렵다. 내가 방금 생성한 코드를 검증할 때, 내가 전제한 오류가 그대로 검증 로직에도 반영된다. 잘못된 전제 위에서 쓴 코드를 같은 전제로 검증하면 항상 맞다는 결론이 나온다.
Octomind 개발자는 이 구조를 아예 뜯어냈다. 0.44.2에서 에이전트가 자신의 작업을 검증하는 경로를 제거했다. 대신 세 가지를 바꿨다.
첫째, 조건별 검증. 이전에는 에이전트가 "전반적으로 괜찮아 보인다"라는 홀리스틱 판단으로 통과할 수 있었다. 이제는 태스크에서 파생된 모든 조건이 개별적으로 검증된다. 조건 하나라도 불일치하면 게이트가 닫힌다. 해피 패스만 확인하고 완료 처리하는 방식은 구조적으로 불가능해졌다.
둘째, 외부 플래닝. 플래닝 도구를 메인 모델에서 분리했다. 이전에는 모델이 플랜을 생성하고, 그 플랜 자체를 '작업 완료'처럼 취급하는 경우가 있었다. 서류를 작성했다고 일을 한 게 아닌데, 에이전트는 그 경계를 흐렸다. 이제 플래닝은 코드 작성도, 검증도 하지 않는 별도 모델이 담당한다.
셋째, 검증 정책 영속성. 세션이 재시작되어도 검증 기준이 유지된다. 이전에는 세션이 끊기면 검증 규칙을 다시 설정해야 했다. 도구가 당신의 기준을 기억하지 못한다면, 결국 당신이 매번 재검증해야 한다. 그건 자동화가 아니다.
팀 리드가 내일 당장 설계해야 할 것
두 사례를 묶으면 하나의 패턴이 보인다. AI는 자신이 생성한 결과물의 품질을 스스로 보증할 수 없다. 이건 AI의 결함이 아니다. 구조의 문제다. 그리고 그 구조를 설계하는 건 팀 리드의 일이다.
커버리지 숫자를 KPI로 쓰지 말 것. AI가 생성한 테스트 스위트에서 80%, 90%의 라인 커버리지는 사실상 공짜다. 모델이 소스를 보고 라인을 실행하는 테스트를 만드는 건 어렵지 않다. 그 숫자가 품질을 보증한다는 착각이 더 위험하다. 커버리지는 출발점이지, 도착점이 아니다.
뮤테이션 테스트를 검증 레이어에 포함할 것. AI 생성 테스트가 베이스라인을 만들어 준다면, 뮤테이션 테스트는 그 베이스라인의 실제 강도를 확인한다. 수동으로 해도 된다. 핵심 로직 조건을 한 번씩 뒤집어보고, 테스트가 실패하는지 확인하는 것만으로도 맹점이 드러난다. mutmut이나 cosmic-ray 같은 파이썬 뮤테이션 테스트 도구를 CI에 넣으면 더 체계적으로 할 수 있다.
AI가 생성한 결과의 검증자는 AI 외부에 두어야 한다. Octomind가 내린 결론과 동일하다. 코드를 쓴 모델과 검증하는 모델이 같은 전제를 공유하면, 검증은 의미가 없다. 자기 검증을 허용하는 워크플로우는 완료 알림은 빠를지 몰라도, 실제 완료와의 괴리를 팀이 나중에 직접 메워야 한다.
"완료"보다 "검증됨"이라는 상태를 분리하라. AI 에이전트가 태스크를 완료했다는 신호와, 그 결과가 실제로 검증됐다는 신호를 같은 것으로 취급하면 안 된다. PR이 올라왔다는 것과 PR이 안전하다는 것은 다른 명제다. 검증 게이트를 AI 외부의 레이어로 설계하고, 그 게이트가 닫혀있을 때는 명시적인 실패 상태가 보여야 한다.
그래서 AI 테스트를 쓰지 말아야 하나
아니다. 테스트 없는 레거시 코드에 AI로 베이스라인 스위트를 만드는 건 실제로 유용하다. 0에서 70%까지 가는 속도는 압도적이다. 문제는 그 70%를 100%로 착각하는 것이다.
AI가 생성한 테스트는 모델이 이해한 코드만큼만 테스트한다. 모델이 놓친 엣지 케이스, 모델이 전제로 깔아버린 조건들은 테스트 밖에 있다. 그리고 그 바깥이 정확히 실제 버그가 숨는 곳이다.
돈, 건강 데이터, 인증처럼 엣지 케이스 하나의 파장이 큰 도메인이라면, AI 생성 테스트는 출발점이지 배포 게이트가 될 수 없다. 프로퍼티 기반 테스트, 뮤테이션 테스트, 그리고 사람이 직접 리뷰하는 레이어가 반드시 있어야 한다.
팀에 내일 당장 물어볼 질문 하나만 남긴다. AI가 짠 테스트를, AI가 돌려서, AI가 "통과"라고 말한다면—당신 팀에서 그 바깥에 있는 검증은 누가, 어떻게 하고 있나?