Trawling

AI가 생성한 테스트는 코딩 에이전트를 오히려 나쁘게 만들 수 있다 — 내 테스트를 점검하는 방법

AI 코딩 에이전트가 생성한 테스트가 수정 능력을 떨어뜨릴 수 있다는 분석입니다. SWE-bench Verified 실험에서 약한 테스트는 해결률을 61.2%에서 57.3%로 낮췄고, 더 좋은 테스트는 65.3%로 높였습니다. 잘못된 테스트는 결함을 놓치는 수준을 넘어 다음 수정에 잘못된 목표를 제시할 수 있다고 지적합니다.

본문

버그 수정이 새로 추가된 테스트를 모두 통과하면서도 새로운 리그레션을 만들 수 있다는 문제를, LLM 없이 로컬에서 검증 가능한 Python 예제로 보여주는 글입니다. 주문 필터 함수에는 세 가지 요구사항이 있습니다. 필터를 생략하거나 None을 넘기면 모든 주문을 반환하고, 빈 리스트를 넘기면 주문을 반환하지 않으며, 상태 리스트를 넘기면 일치하는 주문만 반환해야 합니다. 보고된 버그는 필터를 생략하면 아무것도 반환하지 않는다는 것입니다.

falsy 함정 — 그럴듯한 패치가 숨긴 리그레션

if not statuses: return list(orders) 형태로 수정하면 생략된 필터는 전체 주문을, ['paid']는 유료 주문만 반환하므로 두 검사를 통과하고 if의 양쪽 분기도 모두 실행됩니다. 그러나 filter_orders(ORDERS, []) == [] 검사를 추가하면 실패합니다. Python은 None과 []를 모두 falsy로 취급하지만 요구사항상 둘의 의미는 다르며, 이 패치는 그 구분을 지워버립니다. 원래 구현인 statuses = statuses or []에서 '수정 전 실패, 수정 후 통과'라는 익숙한 검사는 원래 버그는 잡아내지만 새 버그는 잡아내지 못합니다. None을 명시적으로 처리하는 완전한 수정(if statuses is None)만이 세 검사를 모두 통과하며, 세 구현을 나란히 비교한 표가 본문에 제시됩니다.

ExecCritic — 약한 테스트는 해결률을 깎는다

9월 8일 공개된 Leitian Tao 등의 ExecCritic 프리프린트는 이 문제를 정량화합니다. Qwen-3.5-35B-A3B Repair 에이전트를 고정하고 SWE-bench Verified에서 측정한 결과, 생성된 테스트 피드백 전 초기 수정은 61.2%를 해결했습니다. 그런데 기본 Qwen Test 에이전트가 만든 테스트를 피드백으로 주면 57.3%로 3.9%p 하락했고, GPT-5.6-sol이 만든 테스트를 주면 65.3%로 상승했습니다. 수치는 세 번의 수정 실행 평균이며 생성된 테스트는 재사용됩니다. 실패한 테스트 자격 검증은 초기 패치를 전체 점수에 유지하고, 기준선은 저장소 테스트를 금지하지 않으며, 해결 여부는 별도 공식 평가기가 판정합니다. 테스트 생성과 수정 작업이 추가되므로 컴퓨팅 예산은 대등하지 않고, 저자들의 결과이지 이 글을 위한 재현이 아니라는 점도 명시됩니다.

잘못된 테스트는 다음 수정의 표적을 비튼다

나쁜 테스트는 결함을 놓치는 것 이상의 해를 끼칩니다. filter_orders(ORDERS, []) == ORDERS처럼 빈 리스트 요구사항과 모순되는 단언을 추가하면 깨진 패치는 통과하고 올바른 구현은 실패합니다. 이 실패를 자동 수정 루프에 넣으면 루프는 올바른 동작을 망가뜨릴 이유를 얻게 됩니다. 그래서 저자가 제시하는 리뷰 질문은 '이 테스트가 거부할 수 있는 그럴듯한 잘못된 구현은 무엇인가'입니다. 필터 예제에서는 필터를 아예 무시하는 구현, 모든 누락 필터를 빈 것으로 취급하는 구현, 모든 빈 필터를 누락으로 취급하는 구현이 각기 다른 계약 오해에 해당하며, 이들을 구분하는 테스트가 유료 주문 예제를 더 늘리는 것보다 많은 정보를 줍니다. 뮤테이션 테스팅도 도움이 됩니다. statuses is None을 not statuses로 바꾸는 것처럼 구현에 작은 변이를 주고 스위트가 잡아내는지 확인하며, 살아남은 변이를 검토해 무엇을 바꾸는지 이해하라고 권합니다.

에이전트 워크플로우를 위한 네 가지 변경

첫째, 패치를 리뷰하기 전에 기대 동작을 적습니다. 통상 사례, 보고된 실패, 그리고 가장 혼동하기 쉬운 인접 사례를 포함하며 None과 []는 별도 행에 둡니다. 이슈가 그 구분을 명시하지 않으면 어느 해석이든 테스트로 만들기 전에 제품 결정을 받아야 합니다. 둘째, 기댓값을 프로덕션 코드만큼 신중히 리뷰합니다. 단언은 제품에 대한 주장이므로 요구사항, 기존 호환성 약속, 독립적으로 확인된 예제 중 하나로 추적해야 하며, 현재 출력을 기댓값으로 복사하면 바로 그 의심하려던 동작을 보존하게 됩니다. 셋째, 승인된 리그레션 검사를 수정 중에 안정적으로 유지합니다. 에이전트는 구현만 바꾸고 별도 러너가 리뷰된 테스트로 평가하며, 테스트 명령과 설정도 보호합니다. 테스트 파일이 그대로여도 패치가 실행을 건너뛸 수 있기 때문입니다. 테스트 자체가 틀렸다면 명시적으로 수정·리뷰한 뒤 후보를 다시 평가합니다. 넷째, 에이전트에 고치라고 하기 전에 실패를 검사합니다. 잘못 반환된 주문을 보여주는 단언은 실행 가능한 행동 증거이지만, 의존성 누락, 임포트 실패, 테스트 0개 선택 같은 실패는 다른 대응이 필요합니다. 무엇이 실행됐고 왜 실패했는지 기록해야 합니다.

마무리 — 초록불이 같으면 아직 답하지 못한 질문이 있다

ExecCritic은 테스트 생성과 수정을 분리하고, 원본 저장소에서 테스트 자격을 검증하며, 수정 중 테스트를 변경하지 않게 유지해 수정자가 목표를 바꾸는 능력을 제한합니다. 다만 컨텍스트와 권한을 분리해도 두 에이전트가 이슈를 올바로 이해했는지는 보장할 수 없습니다. 다음 AI 보조 수정에서는 원본 코드, 제안된 패치, 새 테스트를 함께 보관하고 요구사항을 위반하는 그럴듯한 대안 구현 하나를 골라 테스트를 돌려보라고 조언합니다. 두 구현이 같은 초록불을 받으면 스위트가 아직 답하지 못하는 구체적 질문을 찾은 것이므로, 둘을 구분하는 검사를 추가하고 그 기대 결과를 리뷰한 뒤 다음 수정을 신뢰해야 합니다.

원문: dev.to / 번역·요약: Trawling

참고 자료

오류 제보