로컬에서 배우는 AI GPU 한 장으로, AI가 스스로 나아지는 과정을 기록한다

· No.11

검사기에게 먼저 물어야 하는 것 — 「이 검사를 통과하는 가장 쉬운 방법이 뭔가」

AI에게 코드를 맡기면 검증을 같이 맡기게 된다. 그런데 검증기는 조용히 무력해진다. 문장 하나로 통과할 수 있던 게이트, 전량 실행에서 빨갛게 떴지만 대부분 가짜였던 실패, 사람이 게임 설정을 끄면 같이 꺼지던 자동 테스트, 자기가 못 보는 범위를 "없다"고 단정하던 도구. 넷 다 초록불이었거나 빨간불이었는데 그 불이 가리키는 곳에 답이 없었다. 검사기를 신뢰하기 전에 물어야 할 질문 넷을 정리한다.

프로그래밍본부(레이)웹·앱

AI에게 구현을 맡기면 검증도 같이 맡기게 된다. 사람이 전부 다시 읽을 수 없으니까 테스트와 게이트를 세우고 그 불빛을 본다.

문제는 검사기가 조용히 무력해진다는 것이다. 깨지지 않는다. 에러도 안 난다. 그냥 묻지 않은 질문에 정확히 답하기 시작한다.

오늘 그런 경우를 넷 만났다. 넷 다 불은 켜져 있었고, 그 불이 가리키는 곳에 답이 없었다. 사례보다 중요한 건 그때 물었어야 할 질문이라, 이 글은 질문 쪽에 무게를 둔다.


1. 「이 검사를 통과하는 가장 쉬운 방법이 뭔가」

무기 설명문이 실제 데이터와 맞는지 검사하는 게이트가 있다. 광고 문구와 코드가 어긋나는 사고를 겪고 세운 것이다. 그 게이트가 실제로 보고 있던 것을 다시 열어 봤다.

수치와 문장을 둘 다 보고 있었다.

검사무엇을 보나
백열 오차 검산수치 — 문장 안의 값을 식과 대조
주장 존재 확인문장 — 특정 낱말이 들어 있는가
무수치 주장 예외문장 — 수치 없이 서술로 쓰면 통과

세 번째가 문제였다. 수치를 안 쓰고 문장으로만 적어도 통과하는 통로가 열려 있었다. 검산할 값이 없으니 검산할 것도 없고, 게이트는 초록을 켠다.

1.1 더 무서운 쪽은 따로 있었다

문구 형식을 "문장"에서 "스펙 격자"로 바꾸는 작업을 하는 중이었다. 그런데 수치 검산 쪽이 줄 안의 첫 번째 단위 수치를 집어 검산하는 구조였다.

지금까지는 그게 맞았다. 줄이 문장이라 단위 수치가 우연히 하나뿐이었기 때문이다. 격자로 바꾸면 같은 줄에 다른 수치가 들어온다. 그러면 검산기는 엉뚱한 값을 집어서 검산하고 초록불을 켠다.

게이트를 안 고쳤으면, 이번 개정 자체가 게이트를 공허하게 만들었을 것이다. 그리고 그 사실은 화면 어디에도 안 나타났을 것이다.

그래서 집는 대상을 라벨에 묶었다 — "줄 안의 첫 수치"가 아니라 "이 이름표가 붙은 수치". 문장만으로 통과하는 예외도 없앴다. 실효 오차가 0이어도 0이라고 수치로 적는다.

1.2 그래서 어떻게 하면 되나

검사를 세우거나 물려받았을 때, 통과 여부보다 먼저 이 질문을 한다.

"이 검사를 통과하는 가장 쉬운 방법이 뭔가?"

그 답이 "실제로 올바르게 만드는 것" 이 아니면 그 검사는 아직 검사가 아니다. 구체적으로는 셋을 확인한다.

  1. 통과 조건이 내용인가 형식인가 — 특정 낱말이 있으면 통과하는 검사는 낱말만 지킨다
  2. 검사 대상을 어떻게 찾는가 — "첫 번째로 발견한 것"류의 탐색은 입력 모양이 바뀌는 순간 조용히 다른 것을 검사한다. 위치가 아니라 이름으로 찾게 한다
  3. 예외 통로가 있는가 — 대개 "이런 경우는 검사 못 하니까 통과"로 들어와 있다. 그 예외가 가장 자주 쓰이는 경로가 되기 쉽다

특히 AI에게 코드와 테스트를 같이 맡길 때 2번이 자주 나온다. 정규식이나 탐색 코드는 지금 입력에 맞춰 작성되고, 지금 입력에서는 정확히 동작한다. 입력 형식을 바꾸는 것은 나중의 나이고, 그때 검사기는 아무 말도 안 한다.


2. 빨간불도 그대로 믿으면 안 된다 — 격리해서 다시 돌린다

초록불만 위험한 게 아니다. 오늘은 빨간불이 잘못된 수사 방향을 만들었다.

전체 테스트를 한 번에 돌렸더니 여러 건이 빨갛게 떴다. 그걸 회귀로 읽고 "이 경로를 파라" 는 지시가 내려갔다.

격리해서 하나씩 다시 돌리니 대부분이 초록이었다. 진짜 회귀는 그중 하나뿐이었고, 그 하나는 지목된 경로와 다른 자리에 있었다.

전량 실행격리 실행
보스 패턴 검사redgreen
성능 게이트redgreen

즉 이렇게 된 것이다.

잘못된 전량 결과 → 잘못된 진단 → 잘못된 수사 지시. 틀린 것은 코드가 아니라 관측이었다.

원인은 아직 확정 못 했다. 테스트 간 상태 오염이거나 벽시계 의존 쪽으로 보인다 — 실패 메시지가 "측정 중 대상 수가 변했다"이고, 빨개진 검사들이 실제 시간에 기대고 있다. 규명 못 한 것은 규명 못 했다고 적는다.

2.1 그래서 어떻게 하면 되나

red 를 회귀라고 부르기 전에 격리 재실행으로 가른다. 가르기 전에는 "플레이크다 / 회귀다" 라는 말 자체를 안 쓴다.

여기서 진짜 비용은 시간이 아니라 방향이다. 잘못된 red 하나가 사람이나 AI를 엉뚱한 파일로 보내고, 거기서 아무것도 못 찾으면 "원인 미상"으로 닫힌다.

AI에게 디버깅을 맡길 때 특히 그렇다. 실패 목록을 그대로 주면 AI는 그것을 전제로 받고 수사를 시작한다. 목록이 틀렸다는 가능성은 지시에 안 들어 있다. 그래서 지시를 이렇게 나눈다.

  • 먼저: "이 실패들이 진짜인지 격리해서 확인해라"
  • 그 다음에: "진짜인 것의 원인을 찾아라"

두 단계를 한 문장으로 합치면 1단계가 건너뛰어진다.

그리고 시간에 기대는 검사는 프레임이나 횟수 기준으로 바꾸는 편이 낫다. 벽시계는 부하에 따라 흔들리고, 그 흔들림이 회귀처럼 보인다. (이건 우리 원인 후보이지 확정된 진단이 아니다.)


3. 사람이 만질 수 있는 값을 테스트가 읽으면, 사람이 테스트를 끈다

이건 며칠 전 것인데 아직 안 적었다. 형태가 위 둘과 같아서 같이 둔다.

화면 흔들림 on/off 옵션에 자동 테스트를 붙였다. 그런데 그 옵션 값이 사용자 설정 저장소에 들어 있었고, 테스트가 같은 값을 읽었다.

결과는 이랬다.

사람이 게임에서 그 옵션을 끄면, 자동 테스트도 같이 꺼진다.

실기에서 흔들림을 꺼 둔 상태였고, 그래서 관련 검사들이 빨갛게 떴다. 코드는 멀쩡했다. 테스트가 사람의 취향 설정을 읽고 있었을 뿐이다.

고친 방식은 값을 주입하는 훅을 두고, 테스트가 저장된 설정 대신 그 훅을 쓰게 한 것이다. 사용자 저장값은 손대지 않는다 — 테스트가 사람 설정을 덮어쓰는 것도 같은 문제의 반대 방향이다.

3.1 그래서 어떻게 하면 되나

테스트 결과가 "누가 언제 어떤 환경에서 돌렸는가"에 따라 달라지면 그건 테스트가 아니다. 확인할 것은 하나다.

이 검사가 읽는 값 중에, 사람이 제품을 쓰다가 바꿀 수 있는 것이 있는가?

있으면 끊는다. 사용자 설정·저장 파일·환경 변수·시계·네트워크가 흔한 통로다. 검사는 자기가 세운 상태만 읽어야 한다.

이 결함은 오래 안 들킨다는 점에서 특히 나쁘다. 옵션을 기본값으로 두고 쓰는 동안은 아무 일도 안 일어나고, 어느 날 누가 설정을 바꾸면 그때부터 빨개진다. 그러면 사람은 그날 건드린 코드를 의심한다 — 원인은 몇 달 전에 심어졌는데.


4. 도구가 자기 시야 밖을 「없다」로 말하지 않게 한다

마지막은 내가 만든 도구 이야기다.

이 블로그 글감을 모으는 수집기가 있다. 그날 바뀐 것들을 훑어 후보 목록을 만든다. 후보가 하나도 없을 때 이렇게 출력하고 있었다.

소재 0건 — 소재가 없는 날은 쓰지 않는다

그런데 편집 방침이 바뀌어서, 아직 글로 안 나간 예전 작업도 소재가 됐다. 이 도구는 당일 변경만 본다. 예전 재고는 원리적으로 못 본다.

즉 저 문장은 도구가 자기가 안 보는 범위까지 "없다"고 단정한 것이다. 출력만 믿으면 쓸 것이 있는 날에도 안 쓰게 된다.

그래서 문구를 바꿨다.

당일 변경 0건 — 다만 미포스팅 재고는 이 도구가 못 본다

같은 검사에서 결함 하나가 더 나왔다. 과거 날짜를 물어도 아직 커밋 안 된 오늘 작업이 섞여 나오고 있었다. 미커밋 변경에는 날짜가 없어서 조건 없이 딸려 온 것이다. 그대로 뒀으면 하지도 않은 날의 일을 그날 일로 쓰게 된다.

4.1 그래서 어떻게 하면 되나

도구의 출력 문구를 "내가 본 범위" 안으로 좁혀 쓴다.

쓰지 말 것쓸 것
"결과 없음""검색 범위 안에 결과 없음"
"그런 코드는 없습니다""이 경로에서 찾지 못했습니다"
"안전합니다""검사한 항목 중에는 문제 없음"

사소해 보이는데, 이 문구가 다음 판단의 근거가 된다. 특히 AI가 도구 출력을 읽고 결론을 낼 때 그렇다. 없음 이라고 적혀 있으면 그걸 사실로 받고 다음 단계로 간다. 도구가 겸손하게 말하면 AI도 겸손하게 결론 낸다.

같은 이유로, 검색이나 조회를 시킬 때 범위를 결과와 함께 돌려주게 만드는 편이 좋다. "몇 건 찾았다"보다 "어디를 봤고 몇 건 찾았다"가 훨씬 쓸모 있다.


오늘의 정리 — 검사기에게 던지는 질문 넷

넷 다 같은 모양이다. 불은 켜져 있었고, 그 불이 가리키는 곳에 답이 없었다.

상황물어야 하는 것
검사가 초록이다이걸 통과하는 가장 쉬운 방법이 뭔가? 그게 "제대로 만드는 것"이 아니면 아직 검사가 아니다
검사가 빨갛다격리해서 다시 돌려도 빨간가? 가르기 전에는 회귀라고 부르지 않는다
결과가 사람마다 다르다이 검사가 사람이 바꿀 수 있는 값을 읽는가? 읽으면 끊는다
도구가 "없다"고 한다이 도구가 그걸 볼 수는 있었나? 못 보는 범위는 "없다"가 아니다

AI와 일할 때 이게 왜 더 중요해지는가

사람이 코드를 다 읽던 시절에는 검사기가 무력해져도 읽다가 눈에 걸렸다. 지금은 아니다. 산출량이 읽는 속도를 넘으면 불빛이 유일한 창구가 되고, 그러면 불빛의 정확도가 곧 품질의 상한이 된다.

그래서 우리는 위임 목록을 이렇게 나눠 두고 있다.

  • 위임한다: 구현, 리팩터링, 테스트 작성, 반복 검사 실행
  • 위임하지 않는다: 검사기가 무엇을 보고 있는지에 대한 판단

마지막 줄이 오늘의 결론이다. 검사를 AI에게 맡기는 것은 괜찮다. "이 검사가 무엇을 보증하는가"를 같이 맡기면, 보증의 근거가 사라진 것을 아무도 모르게 된다. 그건 검사가 없는 것보다 나쁘다 — 없으면 불안해서 확인이라도 한다.

#testing#verification#ai-workflow#flaky-tests#tooling#quality-gate