AI 코드 이해도 검사 vs 테스트: 언제 무엇이 이길까
테스트가 놓치는 AI 코드의 진짜 위험은 코드를 이해하지 못한 채 머지하는 것입니다. 이해도 검사와 자동화 테스트, 각각이 유리한 상황을 정리했습니다.

여기 점점 흔해지는 상황이 있습니다. 코딩 에이전트가 파일 네 개에 걸쳐 600줄을 작성하고, 테스트는 통과하고, 린터도 문제없다고 하니 머지합니다. 2주 뒤 프로덕션에서 장애가 나고, 여러분은 남이 짠 것처럼 diff를 노려보게 됩니다. 기능적으로는 실제로 남이 짠 코드니까요. 실패의 원인은 코드 자체가 아니라 코드와 여러분의 머릿속 모델 사이의 간극이었습니다. AI 코드 이해도 검사, 즉 배포 전에 diff에 대해 퀴즈를 내는 게이트가 바로 이 간극을 메우려는 시도입니다. 이번 주 화제가 된 오픈소스 Git 훅 Sphinx가 정확히 이런 일을 합니다. push가 통과하기 전에 diff를 읽고, 일이 잘못됐을 때 코드가 어떻게 동작하는지 질문합니다.
ML 엔지니어 출신인 제 첫 반응은 회의적이었습니다. 또 하나의 게이트? 또 하나의 의식? 그런데 에이전트가 만든 코드에서 실제로 보는 장애 패턴을 곱씹어 보니, 이 아이디어는 테스트가 구조적으로 잡을 수 없는 무언가를 겨냥하고 있다는 생각이 듭니다. 그래서 솔직하게 비교해 보려고 합니다. 이해도 검사와 이미 쓰고 있는 자동화 안전망, 즉 테스트, 린터, 타입 체커, CI를 놓고 각각이 언제 실제로 이기는지 따져 보겠습니다.
테스트와 린터가 실제로 검증하는 것
자동화 검사는 질문 하나에 답합니다. 코드가 검사가 기대하는 대로 동작하는가? 이것은 기계 안에서만 닫히는 루프입니다. 테스트는 누군가가 정상 동작에 대해 가진 믿음을 코드로 옮긴 것이고, 코드는 그 믿음을 만족하거나 만족하지 않습니다. 머지 시점에 사람의 이해는 필요하지 않습니다. 이것은 장점입니다. 수백 명의 엔지니어가 서로의 코드를 전부 읽지 않고도 같은 저장소에 배포할 수 있게 해 주니까요.
문제는 이 모델에 깔린 전제입니다. 머지하는 사람이 코드가 무엇을 해야 하는지에 대한 정확한 머릿속 모델을 갖고 있고, 테스트 스위트가 그것을 반영하고 있다는 전제입니다. 직접 짠 코드라면 이 전제는 대체로 맞습니다. 직접 씨름하며 만들었으니 어디가 위험한지 알고 있으니까요. 하지만 에이전트가 만든 코드에서는 이 전제가 조용히 무너집니다. 목표를 설명하면 에이전트가 구현을 내놓고, 테스트도 에이전트가 같이 썼기 때문에 테스트가 통과합니다. 이제 초록색 체크 표시는 에이전트의 코드가 에이전트의 테스트와 일치한다는 것만 보증하고 있습니다. 여러분의 이해는 애초에 루프에 들어가 있지 않았습니다.
이는 제가 AI 벤치마크가 실제 업무의 대부분을 놓치는 이유에서 다룬 것과 같은 함정입니다. 지표는 측정하는 것만 보증할 수 있습니다. 테스트 스위트는 누군가가 떠올려 코드로 옮긴 케이스에 대한 동작 적합성만 측정합니다. 재시도 로직이 Stripe 타임아웃 때 고객에게 이중 청구를 하는지, 에이전트가 아예 잘못된 엔드포인트를 골랐는지, 혹은 '수정'이 예외를 삼켜서 경쟁 상태를 덮어버렸는지는 말해 주지 않습니다. 이런 것들이 바로 이해도 실패이고, CI를 그냥 통과해 버립니다.
이해도 검사가 테스트가 놓치는 것을 잡는 방식
이해도 검사는 검증의 방향을 뒤집습니다. '코드가 동작하는가?'를 묻는 대신 '이걸 배포하는 사람이 일이 잘못됐을 때 코드가 무엇을 하는지 아는가?'를 묻습니다. Sphinx의 예시가 좋습니다. 웹훅 핸들러가 이제 charge()를 호출하기 전에 Stripe 이벤트 ID를 저장합니다. 타임아웃 후 재시도하면 어떻게 될까요? '고객이 두 번 청구됩니다'라고 답하면 틀린 겁니다. 저장된 ID가 재시도를 단락시키기 때문입니다. 하지만 후속 질문이 있습니다. charge() 자체가 실패하면 다음 재시도는 어떻게 될까요? 충전을 건너뜁니다. 고객은 한 번도 청구되지 않고, 어떤 테스트도 실패하지 않습니다. 겉보기에 올바른 멱등성 패턴이 잘못된 순서로 적용된 것이기 때문입니다.
이런 버그의 성격을 봅시다. 문법 오류도, 타입 오류도, 실패하는 단언문도 아닙니다. 특정 실패 순서에서만 드러나는 설계 결함입니다. 이를 프로덕션 전에 값싸게 잡을 유일한 방법은 사람이 실패 경로를 추론해 보는 것입니다. 테스트로도 잡을 수 있습니다. '이벤트 ID가 저장된 뒤 charge가 실패하는 경우'를 테스트로 작성하면 되니까요. 하지만 그 테스트를 쓸 사람은 순서 위험을 이해하고 있던 사람이고, 그런 사람이 있었다면 우리는 애초에 여기 있지 않았을 겁니다. 이해도 검사는 빠진 테스트를 쓰게 만들었을 바로 그 이해를 탐색합니다.
두 번째 장점은 버그를 잡는 것보다 느리게 새어 나가는 기술을 막는 쪽에 가깝습니다. 많은 개발자가 같은 경험을 이야기합니다. 에이전트를 작동하는 결과까지 몰아붙이는 데 실제 에너지가 소모되지만, 남는 학습은 없습니다. 더 많이 배포하지만 덜 이해하게 되고, 그 격차는 쌓입니다. push 시점의 퀴즈는 최소한 diff 로직을 얕게나마 다시 재구성하게 만들며, 그것만으로도 '승인한 코드'를 '내가 책임지는 코드'로 되돌리기에 충분한 경우가 많습니다. 제가 전에 AI 도구가 개발자의 사고방식을 은밀하게 바꾸고 있다고 주장한 적이 있는데, 이런 게이트는 그 반대 방향으로 밀어주는 첫 도구 중 하나입니다.
자동화 테스트가 이기는 경우
사람이 지키는 게이트를 미화하지는 맙시다. 테스트가 압도적으로 이기는 영역이 여럿 있고, 그걸 무시하면 퀴즈만 유일한 안전망이 되어 버립니다.
- 대규모 회귀 탐지. 3만 줄짜리 리팩터링에서 살아남는 머릿속 모델은 없습니다. 테스트는 커밋마다 수천 개의 동작을 푼돈으로 다시 검증합니다. 이해도 검사는 diff 하나에 대해 질문 서너 개 정도를 다룰 수 있을 뿐입니다.
- 기계적 정확성. off-by-one 오류, 잘못된 포맷 문자열, 잘못 처리된 시간대 같은 것은 시간 압박 속에서 질문에 답하는 사람보다 기계가 전수 검사하는 데 훨씬 낫습니다.
- 반복 가능성. 테스트는 영원히 똑같이 실행됩니다. 퀴즈 질문의 품질은 그것을 생성한 모델과, 금요일 오후 5시에 여러분이 얼마나 깨어 있는지에 따라 달라집니다.
- 리팩터링에 대한 확신. 동작이 전혀 바뀌지 않아야 할 때는 테스트만이 의미 있는 검사입니다. 코드를 이해하는 것은 그 코드를 바꾸지 않았다는 것을 검증하는 데 도움이 되지 않습니다.
보일러플레이트 생성 코드, 기계적 마이그레이션, 의존성 업데이트, 설정 변경처럼 이해도 검사가 가치 없이 마찰만 더하는 코드 영역도 있습니다. Prettier 포맷 변경이 무엇을 하는지 제게 퀴즈를 내는 것은 게이트가 아니라 세금입니다. 이 영역의 도구는 위험이 낮은 diff에 대한 좋은 답이 있어야 합니다. 그렇지 않으면 사람들이 우회하게 됩니다. 흥미롭게도 Sphinx는 이를 예상하고 sphinx yolo라는 탈출구를 두었고, 모든 건너뛰기를 기록합니다. 이 로깅은 생각보다 중요합니다. 모두가 조용히 우회하는 게이트는 아예 없는 것보다 나쁩니다.

이해도 검사가 이기는 경우
이해도 검사는 사람이 가장 약한 고리가 되는 곳에서 정확히 이깁니다. 에이전트 기반 개발에서는 우리가 인정하고 싶은 것보다 더 자주 그렇습니다.
- 실패 경로 추론. 재시도, 타임아웃, 부분 쓰기, 분산 트랜잭션처럼 다른 무언가가 이미 깨졌을 때만 실행되는 코드 경로입니다. 에이전트는 happy path에는 꽤 능숙하지만 sad path는 대충 넘깁니다.
- 영향 범위가 큰 diff. 결제, 인증, 데이터 마이그레이션, 그리고 재앙에 저항해야 하는 시스템에 닿는 모든 것. 여기서 답을 잘못 내는 비용은 2분짜리 퀴즈의 번거로움을 훨씬 넘어섭니다.
- 내가 쓰지 않았고 거의 리뷰하지 않은 코드. 개발자가 아닌 동료가 보낸 1,500줄짜리 PR, 에이전트가 밤새 자율적으로 만든 브랜치. 한 실무자는 바로 이런 PR을 구조화된 리뷰에 집중하기 위해 '이 PR에 대해 나에게 퀴즈를 내줘'라는 프롬프트를 쓴다고 보고했습니다.
- 온보딩과 기술 유지. 팀이 아무도 도메인을 배우는 속도보다 빠르게 에이전트 코드를 배포하고 있다면, 이해도 게이트는 사용할 수 있는 가장 저렴한 강제 장치입니다.
공통점은 이것입니다. 이해도 검사는 diff가 중대하고 사람의 머릿속 모델이 의심스러울 때 본전을 뽑습니다. 이는 보편적인 해법이 아니라 특정한 틈새입니다.
게이트 자체의 실패 모드
회의론자들에게는 실제 근거가 있으니, 이것이 잘못되는 방식들에 대한 솔직한 목록 없이 넘어간다면 독자에게 손해일 겁니다.
첫째, 확인 피로입니다. 우리는 이미 이 상황을 겪어 봤습니다. 쿠키 배너, '정말 계속하시겠습니까?' 대화창, 모두가 그냥 클릭해서 넘기는 보안 질문들. 모든 push에서 발동하는 게이트는 사람들이 생각하기보다 빨리 답하도록 훈련시키고, 한 달 안에 퀴즈는 의식적인 연극이 됩니다. 완화책은 범위를 좁히는 것입니다. 모든 기능 브랜치 커밋이 아니라 보호된 브랜치와 고위험 경로에서만 실행하세요.
둘째, 질문의 품질입니다. 퀴즈는 diff를 읽은 LLM이 생성하므로 LLM의 실패 모드를 물려받습니다. 얕은 질문, 틀린 '정답', 눈에 보이는 변경에 집착하면서 미묘한 변경은 놓치는 문제가 있습니다. 한 리뷰어는 자기 버전이 리뷰 구조를 잡는 데는 도움이 됐지만, 잘못된 엔드포인트 호출 같은 나쁜 결정은 잡지 못했다고 언급했습니다. 나쁜 질문은 아예 질문이 없는 것보다 나쁩니다. 검증하지 않은 이해를 인증해 버리기 때문입니다. 이는 실제 한계이며 완전히 해결될 수 있다고 생각하지 않습니다. 이 도구들 대부분에서 모델 선택보다 에이전트 하네스 설계가 더 중요한 이유와 같은 맥락입니다.
셋째, 이 부분은 실제로 그렇기 때문에 웃음이 났는데, 결국 누군가는 에이전트를 게이트에 붙여서 질문에 대신 답하게 할 겁니다. 도구 설계는 이를 인지하고 있습니다. 여러분의 답변은 코딩 에이전트에 전달되지 않고 비밀 정보는 닫혀 있습니다. 하지만 결심한 개발자가 질문을 채팅창에 붙여 넣는 것을 막을 방법은 없습니다. 그 시점에서 도구는 부정행위를 하려는 의지를 측정하고 있는 셈이며, 어떤 훅도 고칠 수 없는 문화의 문제입니다.
이해도 게이트는 코드를 검증하지 않습니다. 맥락과 책임을 가진 사람이 실제로 코드와 씨름했는지를 검증합니다. 이것은 에이전트 워크플로가 조용히 없애 버린 바로 그것입니다.
실제 워크플로에 게이트 넣기
시도해 보고 싶다면 실용적인 선택지가 두 가지 있습니다. 마찰이 낮은 방법은 기존 에이전트에서 프롬프트 기반 스킬을 쓰는 것입니다. 'PR을 열기 전에 현재 브랜치에 대해 퀴즈를 내줘.' 여러 사람이 정확히 이렇게 만들어 보고 리뷰 구조화에 잘 작동한다고 보고했습니다. 다만 이 방식은 여러분이 직접 실행하기로 선택해야 하며, 가장 바쁠 때 정확히 실패한다는 점을 기억하세요.
강제하는 방법은 Git 훅입니다. Sphinx는 1분이면 설치됩니다. npm install -g sphinx-understand를 실행한 뒤 sphinx install로 Git과 코딩 에이전트를 연결하고, 어떤 모델이 리뷰할지 묻습니다. Claude Code, Codex, Cursor 등 여러 도구와 일반 Git에서 동작하며, OpenAI 호환 엔드포인트라면 어디서든 실행할 수 있습니다. 직접 만들고 싶다면 뼈대는 pre-push 훅입니다:
#!/usr/bin/env bash
# .git/hooks/pre-push — quiz gate for protected branches
protected_branch="main"
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" != "$protected_branch" ]; then
exit 0 # only gate the branches that matter
fi
diff=$(git diff origin/main...HEAD)
if [ -z "$diff" ]; then
exit 0
fi
# Send the diff to your model of choice, get a scenario question,
# check the answer. Pseudocode for the core loop:
question=$(quiz_generate "$diff")
echo "$question"
read -r answer
if ! quiz_verify "$diff" "$question" "$answer"; then
echo "Not quite — here's why. Push blocked; try again."
exit 1
fi
코드보다 중요한 설계 결정이 두 가지 있습니다. push가 아니라 브랜치에 게이트를 거세요. main과 릴리스 브랜치로의 push에서 발동시키고, 가능하면 '리뷰 요청' 시점에도 발동시키세요. 슬롭을 팀원의 주의력이 소모되기 전에 잡는 것이 아마 가장 가치 있는 위치이기 때문입니다. 그리고 모든 우회를 기록하세요. 탈출구는 필요하지만, 감사 로그가 그것을 정직하게 유지시킵니다.
또한 이것이 사람의 리뷰와 어떤 관계인지도 생각해 보세요. 저는 리뷰 계층이 하나 늘 때마다 팀이 느려지는 이유를 쓴 적이 있고, 그 주장은 지금도 유효합니다. 그러니 이것을 모든 것 위에 더하지 마세요. 가장 얕은 첫 번째 리뷰 단계, 즉 '작성자가 이게 무엇을 하는지는 알고 있나?'를 확인하는 단계를 대체하는 데 쓰세요. 이해도 검사가 실제로 자동화할 수 있는 것이 바로 그 단계입니다.
권장 사항
테스트와 이해도 검사는 경쟁자가 아닙니다. 검증하는 대상이 다릅니다. 테스트는 코드를 검증하고, 이해도 검사는 코드에 책임이 있는 사람을 검증합니다. 에이전트 기반 개발은 이 둘이 같은 것이라는 암묵적 가정을 깨뜨렸고, 이런 게이트는 가벼운 장난이 아니라 실용적인 땜질입니다.
제 권장 사항은 이렇습니다. 테스트 스위트를 여전히 주된 안전망으로 두세요. 여기서 소개한 어떤 것도 그것을 대체하지 않습니다. 그리고 이해도 게이트는 보호된 브랜치와 고위험 경로에서만 좁게 추가하고, 기록되는 탈출구를 두세요. 코드를 대부분 직접 손으로 짠다면 이것은 필요 없습니다. 게이트의 가치는 여러분이 쓰지 않은 diff의 비율에 비례합니다. 그리고 퀴즈가 우회하는 귀찮은 일처럼 느껴지기 시작한다면, 그것도 신호입니다. 2분의 멈춤이 비싸게 느껴질 만큼 이해하지 못하는 코드를 빠르게 배포하고 있다는 뜻이니까요. 이 점을 곱씹어 보세요. 이 도구 논리의 불편한 버전이 옳습니다. 사고는 외주를 줄 수 있지만, 이해는 여러분의 몫으로 남아야 합니다. 새벽 3시에 재시도 루프가 아무도 청구하지 않을 때, 에이전트는 당직이 아닙니다. 당직은 여러분입니다.


