Search
moon
sun

AI 테스트의 허수를 걸러내기

URL
생성 일시
2026/11/13 08:00
최종 편집 일시
2026/11/13 08:00
태그
네이버페이
파일과 미디어
안녕하세요. 포인트혜택FE팀 고석진입니다. 요즘은 코드도, 그 코드를 검증할 테스트도 AI를 이용해 짭니다. AI가 만든 코드는 양이 많아서 사람이 한 줄씩 다 읽기 어렵습니다. 그래서 PR을 볼 때도 코드보다 CI의 테스트 결과를 먼저 보게 되고, 테스트가 통과했으면 일단 믿고 넘어가기 쉽습니다. 결국 코드를 믿을지 말지를 테스트 통과 여부가 대신 정해 주고 있습니다. 그런데 그 테스트까지 AI가 짰다면, 테스트가 통과했다는 건 과연 무엇을 확인해 준 걸까요? AI가 짠 테스트 중에는 통과는 하지만 정작 아무것도 확인하지 않는 테스트가 섞여 있을 수 있습니다. 이 글에서는 이런 테스트를 허수 테스트라고 부르겠습니다. 어떤 모양으로 생기는지, 어떻게 찾고 막을 수 있는지 정리해 보겠습니다. TL;DR AI가 짠 테스트는 통과해도 아무것도 검증하지 않을 수 있습니다. 진짜 검증인지는 기대값이 어디서 왔는지 보면 알 수 있습니다. 명세에서 온 값이면 검증이고, 코드를 돌려 보고 받아 적은 값이면 구현을 복사해 둔 것에 가깝습니다.찾을 때는 비용이 싼 방법부터 씁니다. 정규식으로 의심스러운 코드 모양을 뽑고, 실행된 expect가 0개인 테스트를 세고, E2E는 API 응답을 일부러 망가뜨려 봅니다. 그래도 애매하면 뮤테이션 테스트로 코드를 일부러 틀리게 바꿔 봅니다. 애초에 막으려면 AI에게 테스트를 맡길 때 구현 대신 명세를 먼저 주면 됩니다.1. AI가 쓴 테스트는 왜 허수가 되는가 AI 덕분에 테스트를 쓰는 비용은 거의 사라졌습니다. 그런데 줄어든 건 테스트를 짜는 비용이지, 그 테스트가 제대로 검증하게 만드는 비용은 아니었습니다. AI에게 “테스트 써 줘”라고 하면 AI는 명세보다 눈앞의 코드를 보고 테스트를 씁니다. 테스트가 실패하면 가장 빨리 통과시킬 수 있는 쪽을 고릅니다. 확인을 건너뛰는 조건문을 넣거나, 기대값을 지금 구현에 맞추거나, 검증을 아예 지워 버립니다. 작은 예시: 전화번호 마스킹 “전화번호 마스킹하는 함수 만들어 줘”라고 하면 AI는 대략 이런 코드와 테스트를 내놓습니다. const maskPhone = (phone: string): string => { return `${phone.slice(0, 3)}-****-${phone.slice(-4)}` }it('전화번호를 마스킹한다', () => { expect(maskPhone('010-1234-5678')).toBe('010-****-5678') })테스트는 통과합니다. 그런데 “가운데 네 자리를 가린다”는 규칙은 누가 정했을까요? 요구사항에는 “전화번호를 마스킹한다”고만 적혀 있었습니다. 몇 자리를 어느 쪽에서 가릴지는 아무도 정하지 않았습니다. 테스트의 기대값, 즉 toBe() 안에 적어 둔 '010-****-5678'은 이 코드를 한 번 돌려 보고 나온 결과를 그대로 받아 적은 것입니다. 규칙은 코드를 짠 AI가 정했고, 테스트는 그걸 받아 적었고, 사람은 테스트가 통과한 것만 보고 넘어갔을 뿐입니다. 기대값이 어디서 왔는가 그래서 저는 테스트를 볼 때 기대값이 어디서 왔는지를 봅니다. 명세 문장에서 나온 값이라면 명세를 검증하는 테스트입니다. 코드를 실행해서 얻은 값이라면 구현을 복사해 둔 것에 가깝습니다. 기대값이 명세에서 나왔다면 코드가 틀리는 순간 테스트가 실패합니다. 코드에서 나왔다면 테스트도 같은 틀린 값을 기대하고 있으니 늘 통과합니다. 이런 테스트는 차라리 없는 게 나을 때도 있습니다. 테스트가 있다는 이유로 다들 안심하게 되니까요. 2. 허수 테스트 패턴 8가지 실제 프로젝트에서 찾은 케이스를 여덟 가지로 묶었습니다. 1~5는 확인 자체를 건너뛰는 경우, 6은 실패하면 기준을 코드 쪽으로 옮기는 경우, 7은 기대값을 구현에서 베끼는 경우, 8은 판정을 목에 맡기는 경우입니다. 패턴 1. 결과를 기다리지 않고 끝낸다 it('품절 상품에는 품