에이전트 기반 검증 - AI가 쓴 코드를 스스로 검사하게 만들기
요약
AI 코딩의 병목 지점이 코드 작성에서 정확성 검증으로 이동함에 따라, 에이전트가 스스로 변경하고 검사하는 반복적인 과정을 통해 품질을 높일 수 있습니다. E2E 테스트는 실제 사용자 흐름을 기준으로 완료 조건을 정하며, 속성 기반 테스트는 반드시 지켜야 할 규칙을 정의하여 반례를 탐색합니다.
핵심 포인트
- AI 코딩의 병목은 작성보다 검증 단계로 이동함.
- 에이전트가 자체적으로 변경-검사-수정 루프를 돌릴 수 있게 함.
- E2E 테스트는 실제 사용자 흐름을 확인하는 데 가장 적합함.
- 속성 기반 테스트는 핵심 규칙(Invariant)을 정의하여 안정성을 확보함.
- AI 코딩의 병목이
코드 작성에서 정확성 검증으로 이동함. 에이전트가 스스로 변경하고 검사하고 수정하는 반복 과정을 갖추면, 사람이 매번 오류를 찾아 돌려주는 방식보다 품질을 크게 높일 수 있음
E2E 테스트는 실제 사용자 흐름을 기준으로 완료 조건을 정함. 내부 구현이나 모의 객체 기반 테스트가 놓치는 문제를 잡되, 느리고 불안정할 수 있어 가치가 높은 소수의 흐름에 집중해야 함
속성 기반 테스트는 “정렬해도 항목이 사라지지 않는다”처럼 반드시 지켜야 할 규칙을 정하고, 수백~수천 개 입력을 생성해 반례를 탐색 - 재작성과 마이그레이션에서는
차등 테스트로 기존 시스템과 새 시스템에 같은 입력을 넣어 결과를 비교. 명세에 없는 실제 동작까지 확인하고, 의도적인 차이는 별도로 기록 - 검사는
빠르고 같은 조건에서 같은 결과를 내야 에이전트가 자주 실행하고 실패 원인을 좁힐 수 있음. 코드를 요청하기 전에 성공 기준과 검증 방법부터 마련
코드 작성보다 검증이 병목인 이유
-
에이전트에 독립적으로 작업을 검증할 수단을 주면
출력 품질을 크게 개선할 수 있음. 대규모 코드 작성에서의 품질 향상은 검증과 연결되며, 에이전트는 검사 비용을 낮춤 -
좋은 코드를 만드는 데 필요하지만 비용이 많이 들었던
테스트, 벤치마크, 명세, 실제 E2E 실행을 이제 에이전트에 요청할 수 있으며, 검사 자체도 개선할 수 있음 -
사람이 변경분을 읽고, 테스트를 실행하고, 앱에서 버그를 찾아 다시 전달하는 과정은
가장 느리고 비용이 큰 피드백 루프이며, 한 사람의 처리 능력을 넘어 확장하기 어려움 -
자체 검증이 가능하면 에이전트는 사람이 결과를 보기 전에
변경 → 검사 → 결과 확인 → 재수정을 반복할 수 있음 -
조금 더 나은 모델보다 훨씬 나은 피드백 루프를 우선할 가치가 있음
-
테스트는 버그 탐지와 안전한 리팩터링 도구인 동시에, 에이전트가 상황을 파악하고 완료 여부를 판단하는 수단임
-
사람의 역할은 모든 줄을 검토하는 것보다
에이전트가 직접 실행할 검증 루프를 만드는 데 가까워짐 -
잘못된 결과는 공유 설정, 훅, 테스트에 기록해 같은 실수를 반복하지 않도록 하고, 품질을 판단하는 안목을 축적해야 함
-
출시 결과에 대한 책임은 여전히 사람에게 있으며, 코드 변경분뿐 아니라 검증 증거를 검토해야 함
E2E 테스트로 실제 사용자 흐름 검증하기
-
테스트를 하나만 고른다면
E2E 테스트를 우선함. 회원가입, 장바구니에 상품 추가, 결제, 확인 화면처럼 실제 사용자가 수행하는 흐름을 검증함 -
내부 코드 구조가 아니라
사용자가 볼 수 있는 결과를 확인하므로 에이전트의 판정 기준으로 적합함 -
모의 객체 기반 단위 테스트는 에이전트와 같은 오해를 공유해, 기능이 깨져도 통과할 수 있음
-
E2E 테스트는 전체 흐름이 실제로 동작하는지 확인하므로 이런 방식으로 검사를 통과하기 어려움
-
에이전트에 유용한
E2E 테스트의 조건은 다음과 같음 -
모든 경로가 아니라 중요하고 의미 있는 사용자 흐름을 다룸
-
스크린샷, 실행 추적, 실패 단계 설명으로 원인을 알려줌. 단순한 “30초 후 시간 초과”만으로는 충분하지 않음
-
에이전트의 로컬 환경과 배포 환경 모두에서 실행됨
-
막연한 “기능 구현” 대신 “이 E2E 테스트 통과”라는 명확한 완료 조건을 제공함
-
E2E 테스트는 기존 단위 테스트보다
느리고 불안정할 수 있음. 없애기보다는 가치가 높은 소수의 테스트를 유지해야 함 -
실제 실행 중인 시스템을 조작하고 관찰할 수 있도록
유지 관리되는 스킬/CLI와 기능 지도(feature map) 도 유용함 -
CDP, 시뮬레이터, API 하네스 등을 통해 앱을 실행하고 조작하고 관찰할 수 있어야 함
-
기능 지도에는 사용자 관점에서 기능 목록, 접근 경로, 키보드 단축키/선택자, 기대 결과, 주의점을 담음
속성 기반 테스트로 깨져서는 안 되는 규칙 정의하기
-
예제 테스트는 특정 입력의 기대 출력을 확인하지만, 사람이 모든 실패 입력을 떠올릴 수는 없음.
속성 기반 테스트는 지켜야 할 규칙을 정의하고 도구가 수백~수천 개 입력으로 반례를 찾게 함 -
JS/TS의
fast-check, Python의 Hypothesis, Rust의 proptest 같은 도구를 사용할 수 있음 -
실패하면 입력을 문제를 재현하는 최소 사례로 축소하므로, 거대한 무작위 데이터 대신 이해 가능한 반례를 얻음
-
여러 프로젝트에 적용할 수 있는
대표 속성은 다음과 같음
왕복 변환: 인코딩 후 디코딩하거나 파싱 후 직렬화해도 원래 값을 유지함
불변 조건: 정렬해도 원소 수와 내용이 바뀌지 않고, 잔액은 음수가 되지 않으며, 유입 금액과 유출 금액이 일치함
멱등성: 마이그레이션, 정규화, 재시도를 두 번 수행해도 한 번 수행한 것과 효과가 같음
동등성: 최적화한 함수가 단순하고 올바른 함수와 같은 결과를 냄 -
에이전트는 자신의 내부 논리에 맞는 성공 예제를 만들 수 있지만,
생성된 입력에 대한 속성 검증은 자신이 고르지 않은 입력까지 처리하도록 강제함 -
속성은
요구사항 명세로도 유용함. “장바구니 총액은 항상 각 항목 금액의 합과 같아야 한다”는 규칙으로 에이전트가 코드를 만들고, 생성기가 불일치를 찾을 수 있음 -
복잡한 상태 머신, 동시성, 데이터 흐름에는
Lean, TLA+를 이용한 모델링도 활용할 수 있음 -
Claude Code의 Boris Cherny는 짧은 프롬프트로 모델을 만들고 반례를 찾아 재현한 뒤 수정하는 방식으로 경쟁 상태와 미묘한 버그를 탐색해 왔음
속성 테스트를 작게 시작하기
- 파서, 포매터, 가격 계산처럼
작고 사실상 순수한 함수부터 시작하고, 코드로 구현하기 전에 자연어로 속성을 작성함 - 테스트 라이브러리가 찾은
최소 실패 사례를 저장해 회귀 테스트에 사용함 - 새 코드를 만들 때 에이전트에 테스트 예제 대신
속성을 요청함. 명시적으로 요청하면 에이전트가 이를 잘 수행할 수 있음 - 기존 시스템을 교체할 때는 신구 시스템의 차이를 찾는 사례를 생성하고, 재작성이나 마이그레이션에서는
기존 시스템을 판정 기준으로 삼음 - 새 테스트를 모두 통과해도 기존 시스템의 문서화되지 않은 작은 동작들과 달라질 수 있음
- 사용자는 공식 명세에 적힌 동작이 아니라 실제로 작동하던 동작에 의존함
차등 테스트로 기존 시스템과 비교하기
차등 테스트는 같은 입력을 기존 시스템과 새 시스템에 넣어 출력을 비교함. 불일치는 새 시스템의 버그이거나, 호환성을 깨는 변경으로 문서화해야 할 의도적인 변화임
-
사람이 고른 사례만으로는 드문 경계 사례를 놓칠 수 있으므로
무작위 입력 생성과 실제 트래픽 표본 재생 중 하나 또는 둘 다 사용함 -
이는 “새 시스템의 출력이 기존 시스템과 같아야 한다”를 속성으로 삼는 속성 기반 테스트와 같음
-
에이전트 보조 대규모 마이그레이션에서는
수정 → 차등 테스트 → 불일치 검토 → 수정을 반복할 수 있음 -
기존 시스템이 판정 기준이므로 모든 경계 사례를 에이전트에 일일이 설명할 필요가 없음
-
이 방식을 사용한 팀들은 하나를 고치다 다른 것을 깨뜨리는 대신, 비교 결과를 따라 체계적으로 진행할 수 있었음
-
비교 전에 의미 없는 차이와 의미에 영향을 주지 않는 순서를
정규화해 오탐성 차이가 끝없이 쌓이지 않도록 해야 함 -
의도적인 변화는 이유와 함께
승인된 변경 목록으로 관리해, 단순히 “다르다”는 상태가 허용되지 않도록 함 -
차등 검증 하네스는 마이그레이션을 끝까지 마치고
기존 시스템을 제거할 확신을 제공함 -
두 시스템을 계속 운영하면 이해해야 할 작업량이 두 배가 되고, 마이그레이션이 실패할 경로도 두 가지가 됨
검증 루프를 빠르고 결정적으로 만들기
-
느리거나 불안정한 테스트는 에이전트에
잘못된 행동 유인을 줌. 실패한 검사가 재실행만으로 가끔 통과하면 수정보다 재시도를 선호하게 됨 -
테스트에
15분이 걸리면 실행 빈도가 줄고, 검사 사이에 더 많은 변경을 쌓아 어느 변경이 실패를 일으켰는지 특정하기 어려워짐 -
사람은 불안정한 테스트를 알아보고 CI를 재실행하거나 20분짜리 빌드를 기다릴 수 있지만, 에이전트는 반복 과정의 유인에 따라 행동함
-
이런 실패 양상은 뚜렷한 실패 신호 없이 사람이 검토할 결과물의 품질을 낮춤
-
핵심 검증은
매 반복마다 실행할 만큼 빨라야 하며, 1초 미만이면 좋음 -
에이전트가 쉽게 찾고 실행할 명령을 마련하고, 적용 가능한 출력 축소 옵션을 사용함
-
먼저 빠른 검증을 돌리고, 변경 통합 전에 무거운 E2E 테스트와 차등 테스트를 실행함
결정적 결과를 위해 시드가 고정된 난수, 고정된 시계, 통제된 네트워크, 격리된 상태를 사용함
-
테스트 실패가 시스템 결함을 뜻하도록 해야 함
-
불안정한 테스트는 주 테스트 모음에서 즉시 분리하고 제거해야 함. 주 검증에 남아 있으면 다른 결과에 대한 신뢰까지 무너뜨림
-
출력은 짧고
실패 원인을 구체적으로 알려줘야 함. 통과 로그를 길게 나열하는 것보다 무엇이 실패했는지 알려주는 편이 유용하며, 불필요한 출력은 매번 에이전트의 주의력을 소모함 -
테스트 인프라는
에이전트가 의존하는 구성 요소로 관리해야 함. 이를 개선한 효과는 모든 작업에 누적됨
코드보다 먼저 성공 기준과 검사를 작성하기
-
코딩 전에 에이전트가
성공 기준을 다시 말하도록 하고, 새로운 컨텍스트나 별도 검토 에이전트로 검증하는 습관이 유용함 -
생성된 테스트 자체도
검토 대상임. 올바른 구현과 그럴듯하지만 잘못된 구현을 실제로 구분하는지 확인해야 함 -
구현을 요청하기 전에 E2E 흐름, 특정 속성, 기존 시스템과의 비교 중 무엇으로
성공을 판정할지 정해야 함 -
검사가 빠르고 결정적이면 에이전트가 해결책을 반복해서 개선할 수 있음
-
사람의 판단은 모든 출력 줄을 읽는 것보다
성공의 정의에 집중할 때 더 큰 가치를 내며, 코드 변경분을 직접 읽는 방식보다 확장하기 쉬움 -
에이전트 검증에는
일어나야 할 일을 보여주는 예제와 일어나서는 안 되는 일을 규정하는 속성이 모두 필요하며, 검사 과정은 빠르고 정확해야 함
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기