최근 2주 동안에만 리뷰 요청 채널에 테스트 PR 이 스무 건 넘게 올라왔다. 거슬러 올라가면 한 달 반 동안 수십 건이다. 운영 코드는 거의 건드리지 않고, 테스트가 비어 있던 곳을 채우는 PR 들이다.

오늘 올라온 것도 그중 하나였다. 1년 넘게 한 번도 바뀌지 않은 API 함수의 테스트였다. 자동 리뷰 봇이 1분도 안 돼 승인했고, 나도 AI 로 쓴 답글을 달아 승인했다. “회귀 방지 가치가 있어 승인했습니다.”

달고 나서 그 문장이 걸렸다. 나도 요즘 작업하면서 이 테스트들을 고쳤다. 그런데 이 테스트들이 결정적인 에러를 알려 준 적이 있었나. 떠오르는 게 없었다.

그래서 한 달 반 치 기록을 재 봤다. 결론은 이렇다. AI 활용이 목적 없는 테스트와 목적 없는 리뷰 요청으로 이어지면, 숫자는 늘어도 지켜지는 건 없다. 이 테스트들은 머지된 뒤 한 번도 에러를 잡지 못했다. 쓸모가 있었던 건 처음 몇 건, 목적을 갖고 쓴 테스트뿐이었다.

나도 테스트를 고쳤지만, 테스트는 아무것도 알려 주지 않았다

최근 두 작업에서 이 테스트들이 붙은 코드를 고쳤다. 하나는 배송비 표기를 바꾸는 작업이고, 하나는 결제완료 팝업을 막는 작업이다.

두 작업 모두 코드를 바꿀 때 테스트도 AI 가 한 번에 고쳤다. 테스트가 깨졌는지도 몰랐다. 모킹도 AI 가 처리해서, 기존 테스트를 가져다 썼는지조차 몰랐다. 의도하지 않은 변경이 테스트에 걸린 적은 없었고, CI 도 한 번도 실패하지 않았다.

세션 기록을 열어 보니 이유가 보였다. AI 는 스크립트 하나로 코드와 테스트 기대값을 같이 바꾸고, 그다음에 테스트를 돌렸다. 64개가 전부 통과했다. 바뀐 테스트 하나는 이름 끝에 “(회귀)”가 붙어 있었다. 기존 표기가 바뀌지 않는다는 걸 지키라고 같은 작업에서 한 시간 전에 AI 가 쓴 테스트였다. 그 테스트는 실패를 한 번도 보여 주지 못한 채 다시 쓰였다.

테스트가 깨졌는데 내가 못 본 게 아니었다. 깨지기 전에 고쳐졌다.

이번 변경은 의도한 것이라 결과는 맞았다. 하지만 의도하지 않은 변경이었어도 똑같이 조용히 지나갔을 것이다. 테스트의 쓸모는 빨간불이 켜졌을 때 사람이 “이게 의도한 변경인가”를 판단하는 데 있다. 코드와 테스트를 같은 손이 한 번에 고치면 그 순간이 사라진다.

한 달 반 치를 재 보니

내 작업만 그런지 보려고 한 달 반 동안 들어온 테스트 PR 을 다 셌다.

  • 들어온 것. 테스트 파일 수십 개, 케이스 수백 개, 만 줄이 넘는 코드. 근무일로 치면 한 달 가까이다.
  • 머지 뒤 잡은 에러는 0건. 테스트가 붙은 코드를 나를 포함해 여러 사람이 고쳤는데, 이 테스트 때문에 CI 가 실패한 적은 없다. 대부분은 코드와 같은 커밋에서 테스트도 같이 고쳐졌다.
  • 애초에 잘 바뀌는 코드도 아니었다. 테스트를 붙인 파일의 절반은 그전 반년 동안 커밋이 한 번도 없었다.
  • 리뷰도 마찬가지였다. 리뷰 열 건 중 여덟 건 가까이는 자동 리뷰였다. 나머지는 본문이 비었거나 “결함은 찾지 못해 승인합니다” 같은 한 줄이 대부분이었다.

리뷰는 AI 가 하고, 깨질 뻔한 테스트도 AI 가 고쳤다. 사람에게 남은 건 승인 버튼이었다.

쓸모 있었던 건 목적이 있던 테스트뿐이다

그렇다고 전부 헛일은 아니었다. 테스트를 쓰다가 진짜 결함을 네 건 찾았다. 조회 조건이 캐시 키에서 빠진 쿼리, 호출할 때마다 상태가 쌓이는 정규식, 빈 배열에서 터지는 다이얼로그, 지워도 켜진 채 남는 찜 표시.

그런데 네 건 중 세 건이 처음 몇 개 PR 에서 나왔다. 그 뒤 수십 건에서는 한 건이었다.

처음 작업들은 목적이 분명했다. “이 분기가 틀리면 매출 집계가 어긋나는데, 지금은 그걸 잡을 수단이 없다”는 위험 근거가 먼저 적혀 있었다. 테스트를 다 쓴 뒤 코드를 일부러 망가뜨려서 테스트가 정말 실패하는지 확인한 작업도 있었다. 같은 일이 수십 번 반복되면서 작업 설명은 하나의 틀로 굳었다. 반복을 AI 에 맡기면 흔히 생기는 일이다. 그 틀에서 위험 근거가 빠지자 결함도 거의 나오지 않았다.

그러니 문제는 AI 가 아니다. 같은 코드베이스에서, 같은 흐름으로 만든 테스트인데 목적이 있을 때와 없을 때 결과가 갈렸다. AI 는 목적이 있으면 그 일을 빠르게 해내고, 목적이 없으면 목적 없는 일을 빠르게, 많이 해낸다.

리뷰도 같다. 리뷰가 무엇을 잡았고 무엇을 놓쳤는지 다시 재는 사람이 없으니, 같은 체크리스트가 수십 번 찍힌다. 결과를 재고 다음 실행을 고치는 고리가 없으면 실행 횟수만 쌓인다. 내 스킬부터 확인해 보기에서 하려던 게 바로 그 고리였다.

커버리지도 리뷰 횟수도 결국 세기 쉬운 숫자다. 지표가 목표가 되면 더는 좋은 지표가 아니다라는 말이 있는데, AI 는 그 숫자를 채우는 비용을 거의 0으로 만들었다.

토큰 낭비가 아니라 착시다

이걸 토큰 낭비라고 부르고 싶지는 않다. 토큰 값은 싸다. 문제는 숫자가 실제보다 커 보인다는 점이다.

PR 수십 건은 그만큼의 개선처럼 보인다. 케이스 수백 개는 그만큼의 안전장치처럼 보인다. 수백 번의 리뷰는 그만큼 사람이 들여다본 것처럼 보인다. 실제로는 처음 몇 건이 일을 했고, 나머지는 같은 틀을 반복했고, 리뷰는 대부분 자동이었다. AI 를 얼마나 의미 있게 썼느냐가 아니라 얼마나 많이 썼느냐로 결론을 내리면, 그 차이가 성과로 기록된다.

구글 테스팅 블로그의 커버리지 글은 높은 커버리지가 테스트의 질을 보장하지 않으며, 숫자를 100% 에 가깝게 올리는 데 매달리면 잘못된 안정감을 준다고 했다. 같은 블로그의 다른 글은 코드를 바꿀 때마다 깨지기만 하고 결함은 잡지 못하는 테스트를 변경 감지기 테스트라고 부르며 “음수의 가치”를 가진다고 했다. 이번 한 달 반은 그 두 글을 AI 의 속도로 다시 확인한 셈이다.

그래서 이렇게 해 보려 한다

  1. 테스트를 쓰기 전에 목적부터 적는다. 이 테스트가 없으면 어떤 에러를 놓치는지 한 문장으로 적는다. 적을 수 없으면 쓰지 않는다. 대상은 커버리지 숫자가 아니라 최근에 자주 바뀌었거나 에러가 났던 코드부터 고른다. 다 쓴 뒤에는 코드를 일부러 망가뜨려 테스트가 실패하는지 본다.
  2. 기존 테스트가 깨지면, AI 는 고치기 전에 먼저 보고한다. 무엇이 깨졌고 그게 의도한 변경인지 사람이 판단한 다음에 고친다. 기대값을 바꾼 수정은 리뷰어 눈에 보이게 따로 둔다.
  3. 리뷰 요청과 리뷰에도 목적을 단다. 무엇을 봐 달라는지 한 줄을 적는다. 자동 리뷰가 실제로 무엇을 잡았는지 가끔 다시 재서 프롬프트를 고친다.

숫자의 한계도 적어 둔다. 한 달 반은 짧고, 회귀를 막는 값은 시간이 지나야 쌓인다. CI 로그는 한 달쯤 지나면 사라져서 앞쪽 일부는 보지 못했다. 개발자가 로컬에서 보고 고친 실패도 셀 수 없다. 다만 내 경우는 그걸 본 사람조차 없었다.

마치며

오늘 단 승인 답글을 다시 읽는다. “회귀 방지 가치가 있어 승인했습니다.” 이 문장에는 판단이 하나도 없었다. AI 가 리뷰한 테스트를, AI 가 쓴 답글로 내가 승인했다.

남 이야기만은 아니다. 나도 그 PR 들에 리뷰를 열 번 넘게 남겼다. 다시 읽어 보니 거의 전부 “구현과 대조했다, CI 가 통과했다, 승인한다”였고, 그 리뷰들도 대부분 AI 와 함께 썼다. 테스트가 구현과 맞는지는 봤지만, 이 테스트가 왜 필요한지는 거의 묻지 않았다.

다르게 물은 적이 몇 번은 있다. 가장 분명했던 건 빈 배열에서 다이얼로그가 터지는 동작을 테스트가 “기대 동작”으로 굳히려 할 때였다. 이걸 정말 지켜야 하느냐고 물었고, 그 PR 에서는 테스트를 고치는 대신 결함을 고쳤다. 테스트는 결함을 찾아 놓고도 그대로 굳히려 했고, 그걸 바꾼 건 질문 하나였다. 그런 질문은 뒤로 갈수록 사라졌다. 테스트 설명이 하나의 틀로 굳는 동안, 내 리뷰도 같이 굳었다.

겉멋 AI에서 “가장 비싼 것은 토큰이 아니라 동료의 주의력”이라고 썼다. 이번에 본 건 그다음 장면이다. 리뷰와 승인까지 자동으로 돌자, 주의력을 쓰는 사람조차 없어졌다.

다음 승인 답글에는 한 줄을 직접 쓰겠다. “이 테스트가 깨지면, 누가 보나요?”

읽을 거리