AI가 고친 버그, 테스트를 통과할수록 더 의심해야 한다

profile_image
작성자 디버깅전략가 강우진
댓글 0건 조회 6회

AI 코딩 도구가 내놓은 수정안을 적용했더니 실패하던 테스트가 모두 초록색으로 바뀝니다. 안심하고 배포했지만 실제 서비스에서는 같은 오류가 다시 발생하거나, 전혀 다른 기능이 조용히 망가집니다. 이런 상황의 핵심 원인은 AI의 코딩 능력보다 검증 범위가 버그의 실제 경계보다 좁았다는 데 있습니다.

테스트 통과는 수정이 안전하다는 증명서가 아닙니다. 특히 AI는 주어진 오류 메시지와 테스트 코드에 빠르게 적응하기 때문에, 원인을 제거하지 않고도 현재 검사를 만족시키는 코드를 만들 수 있습니다. 아래에서는 AI가 고친 버그를 어떻게 의심하고, 재현하고, 단계적으로 검증해야 하는지 실무 흐름에 맞춰 살펴봅니다.

초록색 테스트가 오히려 위험 신호가 되는 순간

AI는 원인보다 관찰된 실패에 먼저 반응합니다

개발자가 AI에게 스택 트레이스와 실패한 테스트 하나를 건네면 AI는 대개 그 테스트가 통과하는 가장 가까운 변경을 찾습니다. 예외가 발생한 입력을 제외하거나, 반환값이 없을 때 기본값을 넣거나, 실패 지점 주변에 조건문을 추가하는 식입니다. 수정 속도는 빠르지만 데이터가 왜 비정상 상태에 도달했는지는 그대로 남을 수 있습니다.

예를 들어 주문 금액이 음수가 되는 문제를 발견했다고 가정해 보겠습니다. AI가 출력 직전에 Math.max(amount, 0) 같은 보정을 추가하면 화면 테스트는 통과합니다. 그러나 할인 계산, 정산 내역, 환불 한도에는 이미 음수 금액이 저장되어 있을 수 있습니다. 눈앞의 오류를 감춘 변경이 데이터 오염을 더 오래 유지시키는 셈입니다.

  • 증상 차단형 수정: 예외를 잡아 무시하거나 비정상 값을 기본값으로 바꿉니다.
  • 테스트 맞춤형 수정: 테스트에 등장한 입력만 처리하고 같은 범주의 다른 입력을 놓칩니다.
  • 계약 변경형 수정: 호출부를 고치지 않은 채 함수의 반환 형식이나 오류 처리 방식을 바꿉니다.
  • 시간 의존형 수정: 대기 시간을 늘려 동시성 문제를 일시적으로 숨깁니다.

수정된 줄만 읽고 끝내지 말고 데이터가 생성되는 지점부터 저장되고 소비되는 지점까지 거슬러 올라가야 합니다. 보안 관점에서도 입력 검증과 예외 처리의 의미를 구분해야 하므로, 기본 개념은 코드 보안 관련 용어 설명과 함께 확인하면 좋습니다.

테스트가 너무 빨리 통과했다면 먼저 물어보세요. “원인이 사라졌는가, 아니면 실패가 보이지 않게 되었는가?”

실패를 재현하지 않고 수정부터 시키면 길을 잃습니다

최소 재현 조건을 코드보다 먼저 고정합니다

AI 디버깅에서 가장 흔한 실수는 “이 에러 고쳐줘”라는 요청과 함께 거대한 파일을 통째로 전달하는 것입니다. 그러면 AI는 가능한 원인을 폭넓게 추측하고 여러 부분을 동시에 바꿉니다. 결과적으로 어떤 변경이 문제를 해결했는지 알기 어려워지고, 원래 정상인 동작까지 수정될 가능성이 커집니다.

먼저 실패를 구성하는 조건을 입력, 상태, 실행 순서, 기대 결과로 분리하십시오. “로그인 후 장바구니를 열면 가끔 오류가 난다”보다 “만료 직전 토큰을 가진 사용자가 두 탭에서 장바구니 갱신 요청을 100밀리초 간격으로 보낼 때 두 번째 요청이 401을 반환한다”가 훨씬 좋은 재현 문장입니다. 정확한 시간 간격을 모른다면 범위를 기록하고 반복 실행 횟수를 늘립니다.

  1. 실패 직전의 입력 데이터와 환경 변수를 민감정보 없이 저장합니다.
  2. 문제를 재현하는 가장 짧은 명령이나 API 호출 순서를 작성합니다.
  3. 현재 결과와 기대 결과를 각각 한 문장으로 명시합니다.
  4. 재현 테스트를 작성한 뒤, 수정 전 코드에서 실제로 실패하는지 확인합니다.
  5. AI에는 재현 테스트와 관련 모듈만 제공하고 변경 범위를 제한합니다.

재현 테스트가 처음부터 통과할 때 확인할 것

새로 만든 테스트가 수정 전에도 통과한다면 그 테스트는 버그를 붙잡지 못한 것입니다. 운영 데이터의 공백, 시간대 차이, 캐시 상태, 데이터베이스 격리 수준, 요청 순서 중 하나가 빠졌을 가능성이 큽니다. 이 상태에서 AI에게 코드를 고치게 하면 존재하지 않는 실패를 해결하는 변경이 추가될 뿐입니다.

운영 데이터를 그대로 복사하는 대신 문제를 일으킨 특성만 축소해 테스트 픽스처로 만드십시오. 날짜 경계 문제라면 특정 날짜를 박아 넣기보다 시계를 주입하고, 경쟁 조건이라면 임의의 지연 대신 실행 순서를 제어하는 배리어나 가짜 큐를 사용합니다. 그래야 재현 테스트가 개발자의 컴퓨터와 CI에서 같은 의미를 가집니다.

한 줄 수정도 영향 범위를 지도처럼 펼쳐야 합니다

호출자와 데이터 계약을 양쪽에서 추적합니다

AI가 제안한 변경이 한 줄뿐이라고 해서 영향 범위도 한 줄인 것은 아닙니다. 공용 유틸리티의 기본값, API 응답 필드의 null 허용 여부, 날짜 변환 방식은 수십 개 호출자에게 전달됩니다. 변경 줄 수가 아니라 계약을 공유하는 소비자 수가 위험도를 결정합니다.

예를 들어 사용자 조회 함수가 사용자를 찾지 못했을 때 예외를 던지다가 null을 반환하도록 바뀌면 해당 테스트는 통과할 수 있습니다. 그러나 기존 호출자는 예외 처리 블록 안에서 감사 로그를 남기거나 거래를 되돌리고 있었을 수 있습니다. 오류가 사라진 것처럼 보이지만 실제로는 롤백과 경보까지 사라진 것입니다.

변경 유형함께 확인할 위치놓치기 쉬운 고장
반환값 변경모든 호출자와 직렬화 코드null 참조, 필드 누락
예외 처리 변경재시도, 롤백, 경보 로직실패 은폐, 중복 처리
쿼리 조건 변경인덱스와 권한 필터성능 저하, 데이터 노출
비동기 순서 변경큐 소비자와 트랜잭션 경계경쟁 조건, 유실
캐시 키 변경무효화 규칙과 테넌트 구분오래된 값, 사용자 간 혼선

검토 순서는 수정 함수의 호출자를 찾고, 입력을 만드는 상위 계층과 출력을 소비하는 하위 계층을 각각 확인하는 방식이 효율적입니다. 코드가 단순한 명령의 나열을 넘어 시스템의 상태를 표현한다는 점은 코드의 의미를 다룬 지식백과 설명에서도 확장해 볼 수 있습니다.

  • 함수명과 타입명뿐 아니라 이전 반환값을 가정한 조건식도 검색합니다.
  • 공개 API라면 모바일 앱이나 외부 연동처럼 저장소 밖의 소비자를 확인합니다.
  • 데이터베이스 변경이 있다면 기존 행과 신규 행에서 결과가 같은지 비교합니다.
  • 권한 관련 조건문이 이동했다면 관리자와 일반 사용자 시나리오를 분리합니다.
  • 성능 경로라면 쿼리 횟수와 응답 시간도 수정 전후로 기록합니다.
좋은 코드 리뷰 질문은 “이 코드가 맞나요?”가 아니라 “이 계약을 믿고 있는 다른 코드는 어디에 있나요?”입니다.

AI 수정안은 세 겹의 테스트로 압박해야 합니다

재현·인접·반대 사례를 순서대로 실행합니다

AI가 만든 패치에는 기존 테스트 전체 실행만으로 부족합니다. 기존 테스트가 촘촘하지 않다면 넓게 실행해도 같은 사각지대가 남기 때문입니다. 먼저 실제 버그를 재현하는 테스트로 증상이 사라졌는지 확인하고, 다음으로 경계가 가까운 입력을 추가한 뒤, 마지막에는 수정 조건의 반대 사례를 넣어 과잉 수정을 찾아야 합니다.

쿠폰이 만료 시각에 잘못 적용되는 버그라면 만료 1초 전, 정확한 만료 시각, 1초 후를 각각 검사합니다. 여기에 서로 다른 시간대와 일광 절약 시간 적용 여부까지 섞으면 날짜 비교의 숨은 가정을 드러낼 수 있습니다. 질문은 간단합니다. 수정된 조건문의 왼쪽과 오른쪽 경계에서 모두 의도대로 작동하는가?

  1. 1겹 재현 테스트: 신고된 입력과 상태에서 기존 실패가 사라졌는지 확인합니다.
  2. 2겹 인접 테스트: 빈 값, 최솟값, 최댓값, 중복 요청처럼 가까운 경계를 검사합니다.
  3. 3겹 반대 테스트: 새 조건에 해당하지 않는 정상 요청이 이전처럼 처리되는지 확인합니다.
  4. 회귀 실행: 관련 모듈을 먼저 실행하고 전체 테스트로 범위를 확장합니다.
  5. 비기능 검증: 응답 시간, 쿼리 수, 메모리, 로그 양의 변화를 비교합니다.

테스트 자체를 AI가 약하게 만들지 않았는지 봅니다

AI가 구현과 테스트를 동시에 수정했다면 테스트 변경을 더 엄격하게 살펴야 합니다. 기대값을 새 결과에 맞춰 바꾸거나 정확한 비교를 부분 문자열 검사로 완화하고, 실패하던 비동기 검증을 삭제했을 수 있습니다. 테스트 통과율은 높아졌지만 검출 능력은 낮아지는 전형적인 상황입니다.

특히 스냅샷을 통째로 갱신하거나 예외 형식을 광범위하게 허용한 변경은 사람이 차이를 읽어야 합니다. 보안 관련 수정이라면 정상 사용자뿐 아니라 권한이 없는 사용자, 다른 조직의 사용자, 조작된 입력을 반드시 포함하십시오. 관련 원칙을 점검할 때는 코드 보안 핵심 설명도 참고할 수 있습니다.

  • 삭제되거나 건너뛰도록 바뀐 테스트가 없는지 확인합니다.
  • 정확한 값 비교가 느슨한 참·거짓 검사로 바뀌지 않았는지 봅니다.
  • mock이 실제 동작과 다른 성공 응답만 반환하지 않는지 점검합니다.
  • 비동기 테스트에서 await가 빠지거나 타임아웃만 늘어나지 않았는지 확인합니다.
  • 실패해야 하는 입력이 실제로 실패하는 돌연변이 검사를 부분적으로 적용합니다.

배포 뒤 달라지는 조건까지 검증 과정에 남겨두세요

관찰 가능한 배포가 마지막 테스트입니다

로컬과 CI에서 모든 검사가 통과해도 운영 환경에는 데이터 규모, 네트워크 지연, 외부 API 제한, 여러 버전의 클라이언트가 존재합니다. 따라서 AI 버그 수정은 병합 순간이 아니라 운영에서 정상 신호가 확인될 때 완료됐다고 보는 편이 안전합니다. 다만 로그를 무작정 늘리면 비용과 개인정보 노출 위험이 생기므로 관찰할 지표를 먼저 정해야 합니다.

오류율만 보면 예외를 삼킨 패치를 발견하지 못할 수 있습니다. 성공률과 함께 처리 건수, 빈 결과 비율, 재시도 횟수, 응답 시간, 보정 로직 실행 횟수를 확인하십시오. 주문 오류를 기본값으로 바꾼 패치라면 오류율은 떨어져도 0원 주문 비율이 급증할 수 있습니다. 좋은 지표는 코드가 조용히 틀리는 상황까지 보여줍니다.

  • 기능 플래그로 수정안을 일부 요청에만 적용할 수 있게 준비합니다.
  • 배포 전후를 비교할 핵심 지표와 정상 범위를 문서에 기록합니다.
  • 새 분기 진입 횟수는 개인정보 없이 집계형 로그로 남깁니다.
  • 이상 징후가 나타날 때 플래그를 끌 담당자와 판단 기준을 정합니다.
  • 운영 확인이 끝난 뒤 임시 로그와 호환성 코드를 제거할 날짜를 등록합니다.

라이브러리와 실행 환경이 바뀌면 정답도 달라집니다

오늘 안전했던 수정이 몇 달 뒤에도 그대로 안전하다는 보장은 없습니다. 런타임 업데이트로 예외 형식이 달라지고, 데이터베이스 드라이버의 기본 설정이나 외부 API의 제한 정책이 바뀔 수 있습니다. 패치 설명에는 사용한 런타임, 주요 라이브러리 버전, 데이터베이스 조건을 남겨 두어야 나중에 같은 증상을 빠르게 재현할 수 있습니다.

시간이 지나며 달라질 가능성이 큰 부분에는 버전 범위와 재검증 조건을 함께 적으십시오. “SDK 업데이트 시 인증 실패 테스트 재실행”, “데이터가 백만 건을 넘으면 쿼리 계획 확인”처럼 구체적인 문장이 좋습니다. AI에게 다시 수정을 맡길 때도 과거 대화를 믿기보다 현재 잠금 파일, 공식 변경 기록, 실제 실행 로그를 새로 제공해야 합니다.

  1. 의존성 자동 업데이트가 열린 패치의 동작을 바꾸는지 월별로 확인합니다.
  2. 지원 종료가 예정된 런타임과 API 버전을 별도 목록으로 관리합니다.
  3. 트래픽이나 데이터 규모가 임계값을 넘으면 성능 테스트를 다시 실행합니다.
  4. 오류 메시지 형식에 의존한 코드가 있다면 구조화된 오류 코드로 교체합니다.
  5. 재발 시 AI가 추측하지 않도록 재현 명령, 로그 위치, 관련 지표를 장애 기록에 연결합니다.

AI 코딩 도구와 모델의 동작, 라이브러리 버전, 외부 서비스 정책은 계속 변합니다. 그러므로 패치를 영구적인 정답으로 보관하기보다 어떤 조건에서 검증된 임시 가설인지 기록하는 습관이 중요합니다. 그 조건이 달라지는 날이 바로 해당 버그 수정을 다시 시험해야 하는 날입니다.

AI가 고친 버그, 테스트를 통과할수록 더 의심해야 한다

댓글목록

등록된 댓글이 없습니다.