TDD가 AI 코드를 신뢰할 수 있게 만들 것이라 생각했다. 하지만 우리의 테스트에는 구멍이 있었다.
요약
AI 코딩 에이전트의 견고성을 테스트 주도 개발(TDD)로 확보하려 했으나, AI가 작성한 테스트는 여전히 심각한 구멍을 가지고 있음을 발견했습니다. 높은 커버리지와 녹색 상태만으로는 모든 경계 조건이나 잘못된 가정을 잡아내기 어렵습니다. 따라서 단순히 TDD를 따르는 것보다, 강력한 의도(intent) 제공과 계약서 기반의 검증 등 프로세스 전반의 조정이 필요합니다.
핵심 포인트
- AI 에이전트가 작성하는 테스트는 형편없거나 구멍이 많을 수 있습니다.
- 높은 커버리지와 녹색 상태만으로는 요구사항의 완전성을 보장할 수 없습니다.
- 단순 TDD보다 강력한 의도(intent) 제공과 검증 프로세스 조정이 중요합니다.
- 테스트가 코드보다 먼저 작성되었는지 확인하는 연대기적 접근이 필요합니다.
우리는 AI 코딩 에이전트로부터 일관되게 견고한 소프트웨어를 얻는 방법을 찾았다고 생각했습니다. 바로 테스트 주도 개발(Test-Driven Development, TDD)을 사용하게 하는 것이었습니다.
에이전트가 테스트까지 작성한다면, 90% 이상의 커버리지를 달성하는 것은 거의 일상적인 것처럼 보일 수 있습니다.
그것은 우리가 기다려온 안전망처럼 들립니다.
하지만 저는 그것이 그렇게 간단하지 않다는 것을 깨달았습니다.
많은 경우에 AI 에이전트는 형편없는 테스트를 작성하고, 이를 빨간색(red)에서 녹색(green)으로 만들 수는 있지만, 여전히 우리가 원하는 동작을 증명하는 데 실패할 수 있습니다.
테스트 스위트가 녹색이고 커버리지가 높더라도, 아무도 설명하지 않은 경계 조건에서 코드가 잘못될 수 있습니다.
안전망은 존재하지만, 큰 구멍들이 있습니다.
이것이 테스트를 쓸모없게 만든다는 의미는 아닙니다.
이는 같은 에이전트가 요구사항을 해석하고, 구현을 작성하며, 오라클(oracle)을 작성하고, 모든 것이 왜 올바른지 설명할 수 있게 되었을 때 프로세스를 조정해야 한다는 것을 의미합니다.
우리는 코드 이전에 더 강력한 의도(intent)를 제공하고, 실제 위험에 도달하는 증거를 선택하며, 나중에 완성된 묶음(lot)을 계약서와 비교하여 검증해야 합니다.
TDD는 여전히 시작점을 제공한다
Martin Fowler는 TDD를 테스트가 다음 기능 조각을 표현하고, 코드가 그것을 통과시키며, 개발자가 진화하는 케이스 목록을 유지하면서 리팩토링하는 주기라고 설명합니다.
이 빨간색-녹색-리팩토링 루프는 더 많은 코드가 쌓이기 전에 기대를 실행 가능한 피드백으로 전환하기 때문에 여전히 유용합니다.
Fowler의 TDD 설명은 또한 케이스 목록을 중심에 두는데, 이는 그 목록의 품질이 스위트가 감지할 수 있는 것을 결정하기 때문에 중요합니다.
우리의 이전 워크플로우는 TDD에서 영감을 받은 지침을 사용했지만, 우리의 감사(audit)는 모든 테스트가 해당 코드보다 먼저 작성되었음을 확립하지 못했습니다.
에이전트는 종종 코드와 테스트를 하나의 검토된 묶음으로 함께 제공했습니다.
이를 엄격한 TDD라고 부르는 것은 우리가 관찰하지 못한 연대기를 발명하는 것입니다.
더 깊은 문제는 시간 순서와 무관합니다.
한 추론 과정이 구현과 테스트를 모두 작성할 때, 둘 다 동일한 잘못된 가정을 유지할 수 있습니다.
테스트는 모든 분기(branch)를 실행할 수 있지만 여전히 잘못된 결과를 단언(assert)할 수 있습니다.
커버리지(Coverage)는 어떤 코드가 실행되었는지 알려줄 뿐, 요구사항이 정확했는지, 완전한지, 또는 의미 있게 단언되었는지는 알려주지 못합니다.
우리의 파일럿 사례보다는 예시적인 예를 들어보겠습니다.
테스트가 처음에는 누락된 심볼을 참조하거나 유효하지 않은 데이터를 구성하여 실패할 수 있습니다.
에이전트(agent)는 그 설정을 복구하고, 테스트를 녹색(green)으로 만들고, 의도된 동작은 테스트되지 않은 채로 남겨둘 수 있습니다.
색상이 실제로 기술적인 이유로 변한 것이지만, 빨간색(red)이 제품의 동작이 부재함을 증명하지 못했고, 녹색(green) 역시 구현되었음을 증명하지 못했습니다.
벤치마크는 주장을 구체화해야 한다
Kun Chen은 Sonnet 5.5를 사용하여 DeepSWE에서 진행한 10월 8일 실험을 보고했는데, 새로 작성된 테스트를 금지하는 것이 약간 더 높은 성공률을 보였지만, 그 차이는 통계적으로 유의미하지 않았습니다.
그는 시간과 토큰 사용량에 있어 통계적으로 유의미한 감소를 보고했습니다.
기준선(baseline) 분할은 단위 테스트(unit tests) 65%와 통합 테스트(integration tests) 35%였습니다.
기존 테스트가 비활성화된 44개 작업 하위 집합에서, 그는 성공률에 측정 가능한 차이가 없다고 보고했습니다.
종단 간(end-to-end) 테스트는 3,000개가 넘는 단위 및 통합 테스트 옆에 단 17개만 나타났기 때문에, 이 실험은 종단 간 질문을 해결하지 못했습니다.
그의 설명은 에이전트가 명시적인 인간의 지침 없이 자발적으로 추가하는 테스트와 관련되었습니다.
이는 Chen이 자신의 보관된 진술에서 보고한 결과이며, 독립적인 재현이나 원본 프로토콜에 대한 검토가 아닙니다.
별도의 2026년 연구에서는 여섯 개 모델의 궤적과 네 개 모델에 걸친 프롬프트 개입을 검토했습니다.
이 연구는 해당 설정에서 그러한 개입으로 인한 통계적으로 유의미한 결과 변화를 발견하지 못했으며, 이는 동등성을 확립하거나 회귀 보호의 장기적인 가치를 제거하는 것은 아닙니다.
[에이전트가 생성한 테스트] (https://arxiv.org/abs/2602.07900)는 광범위한 약속들에 반대하는 유용한 증거일 뿐, 테스트가 결코 도움이 되지 않는다는 증거는 아닙니다.
녹색 스위트가 두 개의 경계를 놓치다
한 검토 그룹은 내부 대시보드에 응답 추적 기능을 추가했습니다.
에이전트는 다섯 개의 새 테스트와 함께 코드를 전달했고, 목표로 한 스위트는 47개의 테스트를 통과했습니다.
그 테스트들은 장식용이 아니었습니다.
그것들은 폐기된 초안이 명시적으로 처리되는지, 외부 전송 영수증이 여전히 반복 가능(idempotent)한지, 일관성 없는 ID가 거부되는지, 정규화된 메시지 참조가 답장을 첨부할 수 있는지, 그리고 모호한 참조가 거부되는지를 확인했습니다.
독립적인 diff 검토에서도 여전히 두 개의 차단 결함이 발견되었습니다.
첫째, 한 계정의 스레드 식별자가 다른 계정에 들어오는 메시지에 연결될 수 있었습니다.
둘째, UTC ISO 타임스탬프가 datetime-local 필드를 초기화하고 로컬 시간으로 해석되어 기록된 순간을 시간대에 따라 1~2시간 이동시켰습니다.
수정 후 두 개의 추가 목표 테스트가 해당 경계를 다루었습니다.
그 결과 스위트는 총 일곱 개의 새 테스트와 함께 49개의 테스트를 통과했습니다.
원래의 테스트들은 영수증, 반복 가능성, ID 확인 및 모호성에 대한 유용한 증거로 남아 있었습니다.
단지 전달에 중요한 두 개의 파티션만 생략했을 뿐입니다.
통합 경계 역시 여전히 불완전했습니다.
스레드 테스트는 이미 정규화된 In-Reply-To와 References 값을 데이터베이스 변경에 직접 주입했습니다.
그것들은 변경의 동작을 증명했지만, 실제 어댑터(adapter)에서 워커(worker)로 이어지는 체인을 우회했습니다.
동일하게 모킹된 경계에서 더 많은 테스트를 추가한다고 해서 실제로 그 체인이 헤더를 올바르게 보존한다는 것을 스스로 증명할 수는 없습니다.
어떤 유한한 테스트 스위트도 완벽할 수 없으므로, '테스트를 더 많이 작성하라'는 것이 전부는 아닙니다. 우리는 배포 결정을 뒤집을 수 있는 누락된 파티션, 실패 지점, 표현 변경, 그리고 통합 경계를 목표로 해야 합니다. 이는 계정 간, 시간대별, 권한별, 재시도(retries)별, 부분적 실패(partial failures)별, 프로토콜 어댑터(protocol adapters)별, 외부 효과별로 무엇이 발생하는지 질문하는 것을 의미합니다.
더 강력한 기법에는 신뢰할 수 있는 오라클이 필요하다
블랙박스(Black-box) 및 엔드투엔드(end-to-end) 테스트는 공개 인터페이스를 실행하고 실제 효과를 관찰할 수 있게 합니다. 내부 목업(mock)이 데이터의 형태가 바뀌거나 원격 시스템이 다르게 동작하는 경계를 지워버릴 때, 이들은 매우 유용합니다. 저희의 Safari 테스트 사후 분석은 그 인터페이스 뒤에 있는 환경이 왜 중요한지 보여줍니다. 엔드투엔드 단언(assertion) 역시 단위 테스트와 동일한 잘못된 기대를 인코딩할 수 있기 때문에, 이들이 마법 같은 진실의 원천은 아닙니다.
또한, 테스트를 설계하는 데 사용되는 컨텍스트와 변경 사항을 구현하는 데 사용되는 컨텍스트를 분리할 수도 있습니다. 두 컨텍스트 모두 고정된 인간 승인 계약(human-approved contract), 대표 예제(representative examples), 그리고 반례(counterexamples)를 참조해야 합니다. 다른 모델이나 새로운 컨텍스트를 사용하는 것이 상관관계가 있는 구현 세부 사항을 줄일 수는 있지만, 둘 다 동일한 모호한 요구사항을 받는다면 독립성을 창출하지는 못합니다. 우리는 더 강력한 모델이 이 결과를 변경하는지 테스트하지 않았습니다.
속성 테스트(Property tests)는 아이디엠포턴스(idempotence), 순서(ordering), 격리(isolation), 또는 보존(conservation)과 같은 불변량(invariants) 주변의 광범위한 입력 공간을 탐색할 수 있습니다. 변이 테스트(Mutation testing)는 테스트 스위트가 구현에 대한 통제된 변경 사항을 감지하는지 보여줄 수 있습니다. 둘 다 민감도(sensitivity)를 향상시키지만, 변화에 대한 민감도는 의도된 동작과의 일치성(agreement)과는 다릅니다. 잘못된 오라클은 여전히 틀린 규칙을 방어하면서 많은 변이를 거부할 수 있습니다.
우리는 계약 기반의 하이브리드 프로세스가 필요하다
코드 이전에, 우리는 인간의 의도(human intent) 출처를 수락된 계약서에 기록해야 합니다.
이 계약서에는 대표 사례(representative cases), 반례(counterexamples), 불변 조건(invariants), 그리고 실패의 결과가 포함되어야 합니다.
우리는 모호성을 해결해야 하지만, 그것이 구현이나 출시 결정에 실질적인 변화를 줄 수 있을 때만 그래야 합니다.
유용한 반례는 단순히 “오류 처리”라고 말하는 것보다 실패 상황을 명시합니다.
예를 들어 이메일 리더의 경우, 타사 클라이언트가 성공적으로 연결된 후 메일박스 잠금 획득(mailbox lock acquisition)을 거부할 수 있습니다.
필요한 불변 조건은 여전히 연결이 닫혀야 한다고 명시할 수 있습니다.
이러한 사례는 잠금 획득 후에만 정리(cleanup)를 보호하는 구현과, 연결 후 획득된 모든 리소스를 보호하는 구현을 구별합니다.
개발 중에는 에이전트가 위험도에 맞는 단위 테스트(unit tests)와 통합 테스트(integration tests)를 선택해야 합니다.
검토 대상 경계(boundary under examination)를 모의(mock away)해서는 안 됩니다.
재현 가능한 버그의 경우, 새로운 테스트는 예상되는 이유로 빨간색(red)이어야 하며, 동일한 시나리오가 이제 올바르게 동작하기 때문에 초록색(green)이 되어야 합니다.
단언(assertion)을 변경하는 것이 가장 빠른 녹색 경로일지라도 기존 게이트(gates)는 활성화 상태를 유지해야 합니다.
테스트가 실패할 때, 우리는 의도 출처로 돌아가 코드가 잘못되었는지 아니면 오라클(oracle)이 잘못되었는지 결정해야 합니다.
녹색 압력(Green pressure)은 제품 결정이 아닙니다.
만약 출처가 제한 범위가 1과 100 사이에 있어야 한다고 말한다면, 추측성 테스트(speculative test)가 거부를 예상했더라도 클램핑(clamping)이 계약을 만족시킬 수 있습니다.
그 테스트는 수정되거나 폐기되어야 하며 그 이유가 기록되어야 합니다.
이후, 감독 에이전트는 각 물질 동작을 증거에 매핑하고 누락된 위험 요소를 식별해야 합니다.
일반적인 diff 검토는 기본값으로 유지됩니다. 독립적인 검토는 계정 격리(account isolation), 인증(authentication), 데이터 손실(data loss), 동시성(concurrency), 마이그레이션(migrations) 또는 유료 외부 효과와 같이 배포 결정에 영향을 줄 수 있는 위험이 있을 때만 나타나야 합니다.
제한된 배치(bounded lot)의 경우, 이 단계는 하나의 수정 패스로 제한됩니다. 만약 차단되는 결함(blocking defect)이 남아 있다면, 성공을 선언하거나 다른 검토 루프를 시작하기보다는 이를 보고하고 중단해야 합니다.
일부 테스트는 검사 과정에서 판별 경계(discriminating boundary)가 드러난 후에 구현된 후 작성될 수 있습니다. 이는 전체 테스트가 나중에 작성되는 경우 엄격한 TDD는 아닙니다.
이 프로세스는 하이브리드입니다: 인간의 의도와 결정적인 예시가 먼저 오고, 유용한 레드-그린 사이클(red-green cycles)이 개발을 안내하며, 최종 증거 검토는 완성된 배치가 여전히 입증하지 못하는 부분을 탐색합니다.
그린 스위트(Green suite), 90% 커버리지, 잘못된 경계. 에이전트는 자신의 숙제를 채점하고 자신에게 A를 주었습니다.
개발 전에 동작을 정의하고, 배포하기 전에 증거에 도전하세요.
이 실용적인 워크북은 다음 AI 지원 변경 사항에서 그 질문에 답하는 데 도움을 줍니다. 코딩 전에 예상되는 동작을 설정하고, 개발 후에 테스트에 도전하며, 설명할 수 있는 릴리스 결정을 내리는 방법을 배울 것입니다.
저희 파일럿 테스트에서 하나의 정리 결함이 발견되었습니다
10월 9일, 우리는 기준선이 57개의 테스트와 151개의 단언(assertions)으로 녹색이었던 작은 이메일 읽기 라이브러리에 이 프로세스를 적용해 보았습니다.
위임된 검사(delegated inspection)를 하기 전에, 우리는 현재 워터마크에서의 활성화, 순서가 지정된 커서 진행 상황, UID 유효성 변경, 헤더 보존, 경계가 있는 읽기(bounded reads), 그리고 메시지를 읽음으로 표시하지 않는 리소스 정리 등 6가지 요구사항을 수정했습니다.
검토는 실행과 소스 검사를 혼합하여 9가지 시나리오를 고려했습니다. 이들은 9개의 새로운 테스트는 아니었습니다. 기존 테스트들이 이미 워터마크 동작, UID 순서, UID 유효성 재설정, MIME 파싱, 그리고 자르기(truncation)에 대한 유용한 증거들을 제공하고 있었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기