AI가 커밋에 'mutation-checked'라고 적은 테스트를 다시 부숴보았습니다
요약
AI가 생성한 코드의 테스트가 실제로 유효한지 'Mutation Testing'을 통해 검증하는 방법을 설명합니다. 단순히 테스트를 통과했다는 사실만으로는 코드가 안전하지 않을 수 있으며, 실제 환경처럼 동작하도록 Mocking(대리) 함수와 추가적인 테스트 케이스를 보강해야 함을 강조합니다.
핵심 포인트
- 테스트가 통과해도 코드의 취약점은 남아있을 수 있습니다.
- Mutation Testing은 코드를 일부러 망가뜨려 테스트 실패 여부를 확인하는 방법입니다.
- Mocking된 함수는 실제 환경처럼 동작하도록 업데이트되어야 합니다.
- 실제 사용되는 모든 출력값에 대한 검증이 필요합니다.
AI에게 코드를 고치게 하면, 테스트도 함께 작성해 줍니다. 하지만 그 테스트가 정말로 작동하는지는 테스트가 통과했다는 사실만으로는 알 수 없습니다. 수정된 부분을 망가뜨려도 계속 통과하는 테스트는 아무것도 감시하고 있지 않기 때문입니다.
제가 직접 만든 WordPress 플러그인 PDF Image Creator의 1.4.33 버전에서, Claude는 커밋 메시지 마지막에 다음과 같이 적었습니다.
- Dead code removed; translations (263); tests for each fix, mutation-checked.
이는 수정된 부분마다 테스트를 추가하고, 그것을 mutation으로 확인했다는 의미입니다. mutation은 코드를 일부러 망가뜨려서 테스트가 실패하는지 보는 방법입니다. 만약 실패한다면, 그 테스트는 해당 부분을 감시하고 있다는 뜻이고, 통과해 버린다면 아무것도 감시하지 않고 있다는 뜻입니다.
이 문구를 읽고 저도 직접 부숴보았습니다.
1.4.32 버전까지는 이스케이프(escape)에 대한 테스트가 실패하는 것이 없었다
이 플러그인의 테스트는 WordPress를 로드하지 않고 실행됩니다. harness.php 파일은 사용하는 함수들만 대리(mock)로 준비해 줍니다. 1.4.32 버전까지, 그 안에 있던 esc_html() 함수는 다음과 같았습니다.
function esc_html($s) { return $s; }
입력받은 문자열을 그대로 반환합니다. 이렇게 되면, 실제 코드에서 esc_html()을 제거하더라도 테스트에서 보이는 결과는 변하지 않습니다. 이스케이프를 확인하는 테스트는 작성해도 실패할 일이 없었습니다.
1.4.33 버전에서는 대리 함수가 실제와 똑같이 이스케이프하도록 변경되었습니다.
function esc_html($s) { return htmlspecialchars((string) $s, ENT_QUOTES); }
게다가 1.4.34 버전에서는 test-admin-notices.php 파일이 새로 추가되었습니다. 맨 위에는 다음과 같이 적혀 있습니다.
/**
* Mutation testing found these lines could be changed or removed with every
* suite still passing: the escaping of the server's own error text, the...
1.4.33 버전 이후에도 다시 한번 망가뜨려 보았더니, 모든 테스트가 통과한 상태에서 변경되거나 제거될 수 있는 라인이 발견되었습니다. 그것이 바로 이 테스트 덕분입니다.
esc_html(을 하나씩 제거하기
망가뜨리는 방법 1: includes/ 폴더의 PHP에 있는 esc_html( 부분을 하나씩 (로 대체하고, 그럴 때마다 모든 테스트를 실행했습니다. 테스트는 한 번당 약 1.3초가 걸립니다. esc_html__()와 같은 번역이 포함된 함수는 계산하지 않았습니다.
| 버전 | 제거한 개수 | 테스트 통과 (감시되지 않음) |
|---|---|---|
| 1.4.32 | 10 | 10 |
| ... |
1.4.32 버전은 대리 함수가 그대로 작동했기 때문에 모두 통과했습니다. 1.4.33 버전은, 대리 함수를 고쳤음에도 불구하고 여전히 10군데는 통과했습니다. 대리 함수가 실제처럼 되어도, 출력을 확인하는 테스트가 없으면 제거한 것을 알아차릴 수 없습니다. 1.4.34 버전에서는 관리자 화면 알림 중 서버의 에러 문구를 출력하는 부분이 감시되게 되었습니다.
1.4.34 버전에서 통과한 채로 남아있는 8군데는 PDF의 제목을 출력하는 곳(Inserter.php)과 사이트 건강 상태에 서버 에러 문구를 출력하는 곳(SiteHealth.php)이 포함되어 있습니다. PDF 제목은 게시자가 자유롭게 지정할 수 있는 값입니다. 현재 코드는 둘 다 이스케이프하고 있지만, 누군가 제거해도 테스트는 통과합니다.
Admin.php의 문장을 한 줄씩 지우기
망가뜨리는 방법 2: 또 다른 방식으로, includes/Admin.php 파일에서 ;로 끝나는 한 줄짜리 문장들을 하나씩 빈 문자열로 만들고 실행했습니다.
| 버전 | 삭제한 라인 | 테스트 실패 | 통과 (감시되지 않음) |
|---|---|---|
| 1.4.33 | 110 | 33 (30%) | 77 |
| 1.4.34 | 118 | 66 (56%) | 52 |
설정을 저장할 때 사용하는 absint()의 5개 라인은, 1.4.33 버전에서는 지워도 통과했습니다. 하지만 1.4.34 버전에서는 5개 모두 실패합니다.
1.4.34 버전에서 통과한 채로 남아있는 52라인은 add_action()
등록, 알림 바깥쪽의 <div>
또는 </a>
을 출력하는 echo,
설정 화면 템플릿을 불러오는 줄 등입니다. 이들 모두 지우면 화면은 바뀌지만, 이 테스트는 WordPress를 구동하지 않기 때문에, 후크가 등록되었는지나 화면 전체의 HTML까지는 확인하고 있지 않습니다.
「mutation-checked」가 가리키던 범위
1.4.33 커밋의 한 문장은 「tests for each fix」에 걸쳐 있습니다. 수정한 부분마다 추가한 테스트를, 부숴서 확인했다라고 읽을 수 있습니다. 수정된 부분 각각에 대한 테스트가 정말로 그러한지까지는, 이번에는 하나씩 확인하지 않았습니다.
확인한 것은, 파일 전체를 망가뜨렸을 때의 경우입니다. 그쪽에서는, 통과한 채 남아있는 줄이 아직 많이 있었습니다. 「수정한 곳은 확인했다」와 「이 파일은 확인했다」는 별개의 문제입니다. 커밋 메시지만으로는, 어디까지 부숴서 확인했는지 알 수 없습니다. 1.4.34에서, 망가뜨려 발견된 구멍을 메우는 테스트가 추가된 것은, 그 범위를 넓혔다는 의미일 수도 있습니다.
망가져 보는 것은 생각보다 싸다
사용된 것은, 줄을 지우거나 esc_html(를 제거하는 정도의 거친 방식입니다. 진짜 mutation 테스트 도구는 더 세밀하게 부숩니다. 그럼에도 불구하고, 테스트가 1회에 1.3초라면, 118곳을 하나씩 망가져 봐도 몇 분 안에 끝납니다.
# 줄마다 공백으로 만들고, 테스트가 통과하는지 본다 (구문이 망가진 것은 제외)
for i in candidates:
mutated = lines[:]
...
통과한 채 남아있는 모든 줄이, 고쳐야 할 구멍은 아닙니다. 바깥쪽의 echo
을 감시하는 테스트는 아마 필요 없을 겁니다. PDF 제목의 이스케이프는, 감시하는 편이 좋을 것 같습니다. 무엇을 감시할지 결정하는 것은 사람의 일이고, 그 재료를 이 몇 분 동안 얻을 수 있습니다.
다음 자신에게 남기는 메모
- 테스트가 통과했다는 것이, 테스트가 유효하다는 증거가 될 수는 없다
- 대역이 정상 작동하면, 그 함수를 제외해도 테스트는 떨어지지 않는다. 대역은 본물과 같은 답변을 하게 한다
- 커밋 메시지의 「확인했다」는 어느 범위를 확인했는지까지 읽어라
- 자신도 직접 망가져 봐라. 테스트가 빠르면, 몇 분 안에 끝난다
- 통과한 채 남아있는 줄 중, 이용자가 작성한 값을 출력하는 곳부터 감시하라
AI가 「확인했다」라고 적은 테스트를, 당신 스스로 망가져 본 경험이 있습니까?
평소에는 raplsworks.com에서, WordPress 플러그인 개발이나 Claude Code 관련 글을 쓰고 있습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기