테스트를 통과했다고 해서 그것이 정답인 것은 아닙니다
요약
테스트 통과가 코드의 정확성을 보장하지 않는다는 Dijkstra의 원칙을 강조합니다. 특히 AI가 생성한 코드와 테스트가 모두 틀릴 수 있는 시대에, 코드를 확인하는 것이 아니라 의도적으로 망가뜨리려는 시도가 엔지니어링의 핵심임을 설명합니다.
핵심 포인트
- 테스트 통과는 결함의 부재가 아닌, 아직 발견되지 않은 실패를 의미함
- AI 시대에는 코드와 테스트가 동시에 틀릴 위험이 커져 엣지 케이스 검증이 필수적임
- 코드를 확인(confirm)하려 하지 말고, 깨뜨리려(break) 시도하는 습관을 가져야 함
- 경계값, 빈 입력, 에러 경로 등 불변량을 위반하는 테스트를 우선 작성해야 함
테스트 스위트(test suite)가 초록색(pass)이라는 것은 당신의 코드가 정확하다는 의미가 아니라, 아직 실패를 발견하지 못했다는 의미입니다. Dijkstra의 경고는 코드를 확인(confirm)하는 테스트가 아니라, 코드를 망가뜨리려고 시도하는 테스트를 작성하라는 일깨움입니다. AI가 현장에서 실패할 사례를 숨긴 채 쉬운 테스트만 통과하는 코드를 생성하는 지금, 이러한 규율은 더욱 중요합니다.
1970년 Edsger Dijkstra는 모든 엔지니어가 기억해야 할 문장을 작성했습니다: "프로그램 테스트는 버그의 존재를 보여주는 데 사용될 수 있지만, 버그의 부재를 보여주는 데는 결코 사용될 수 없다!" 이 문장은 짧고 정확합니다. 실패하는 테스트는 확실한 무언가를 알려줍니다: 여기에 결함이 있다는 것입니다. 통과하는 테스트는 보이는 것보다 훨씬 적은 것을 알려줍니다. 그것은 단지 이 경로에서, 이 입력값이 이번에는 실패하지 않았다는 것만을 말해줄 뿐입니다.
통과하는 테스트는 당신이 아직 결함을 발견하지 못했다는 것을 증명할 뿐입니다. 그 이상은 아닙니다.
대부분의 엔지니어는 그 문장을 읽고 동의하지만, 실제로는 그 반대가 사실인 것처럼 작업합니다. 테스트 스위트가 초록색이 되면 작업이 완료된 것처럼 느껴집니다. 하지만 초록색은 정확성(correctness)의 증명이 아닙니다. 그것은 단지 이번에는 실패가 포착되지 않았음을 의미할 뿐입니다. 이 두 가지 사이의 간극에서 대부분의 운영(production) 버그가 발생합니다. 당신이 생각하지 못한 입력값, 시도하지 않은 순서, 당신의 책상 위에 있는 것과는 다르게 동작하는 보드 등이 바로 그 원인입니다.
초록색은 포착된 실패의 부재를 의미하는 것이지, 정확성의 존재를 의미하는 것이 아닙니다.
이것이 지금 더 중요한 이유
AI는 이 간극을 더 넓혔습니다. 모델은 몇 초 만에 함수와 함께 통과하는 테스트 세트를 생성할 수 있으며, 둘 다 같은 방향으로 틀릴 수 있습니다. 코드는 테스트가 확인하는 케이스를 처리하고, 테스트는 코드가 처리하는 케이스를 확인합니다. 하지만 둘 다 현장에서 실패할 엣지 케이스(edge case)에는 닿지 않습니다. 모든 것이 완료된 것처럼 보입니다. 도구는 당신의 의도를 이해하지 못한 채 테스트를 통과할 수 있습니다. 왜냐하면 도구는 정확한 시스템이 아니라 초록색 결과(green result)를 향해 작동했기 때문입니다.
도구는 코드와 테스트가 일치하게 만들 수 있지만, 둘 다 여전히 틀릴 수 있습니다.
따라서 Dijkstra의 문장 뒤에 숨겨진 규율은 이제 단순한 친절함이 아닌 핵심적인 엔지니어링 기술이 되었습니다. 코드와 테스트를 모두 저렴하게 생성할 수 있는 시대에, 희소한 능력은 여전히 무엇이 고장 났는지 찾아 나서는 능력입니다. 그것은 과학적인 습관입니다. 자신의 아이디어를 확인하려 드는 것이 아니라, 그것을 깨뜨리려 시도하는 것이며, 정직한 실패 시도를 견뎌낸 후에야 비로소 그 아이디어를 조금 더 신뢰하는 것입니다.
당신의 코드를 확인(confirm)하려 하지 마세요. 그것을 깨뜨리려(break) 하세요.
진심을 담아 테스트하는 방법
패러다임의 전환은 당신의 코드와 일치하는 테스트를 작성하는 것에서, 코드를 깨뜨리려 시도하는 테스트를 작성하는 것으로 옮겨가는 것입니다. 몇 가지 구체적인 습관은 다음과 같습니다:
- 모든 함수에 대해, 어떤 입력이 함수를 망가뜨릴지 자문하고 그 테스트를 가장 먼저 작성하세요. 만약 떠오르는 것이 없다면, 당신은 아직 그 함수를 이해하지 못한 것입니다.
- 중간이 아닌 경계(edges)를 테스트하세요: 빈 입력(empty input), 최댓값, 오프 바이 원(off-by-one) 오류, 동시 접근(concurrent access), 아무도 실행하지 않는 에러 경로(error path) 등을 확인하세요.
- 코드가 항상 유지해야 하는 불변량(invariant)을 명시한 다음, 오직 그 불변량을 위반하는 것만을 목적으로 하는 테스트를 작성하세요.
- AI가 통과하는 테스트와 함께 코드를 건네준다면, 그것을 검토의 끝이 아닌 시작으로 간주하세요. 모델이 확인하지 못한 케이스를 추가하세요.
- 버그가 운영 환경(production)에 도달했다면, 수정 사항을 작성하기 전에 그 버그를 잡아낼 수 있었을 테스트를 먼저 작성하세요. 그 테스트는 수정 코드보다 더 가치가 있습니다.
이것이 테스트를 작성할 가치가 없다는 뜻은 아닙니다. 테스트는 우리가 가진 가장 좋은 도구 중 하나입니다. 다만, 통과하는 테스트가 당신에게 무엇을 말해주고 무엇을 말해주지 않는지를 정확히 알고 있어야 한다는 뜻입니다. 테스트 스위트(test suite)는 당신이 찾아 나선 실패들의 기록이며, 그 범위는 무엇이 잘못될 수 있는지에 대한 당신의 통찰력보다 넓을 수 없습니다. 그 통찰력을 넓히면 테스트는 더 강력해집니다. 이를 생략하면, 초록색 결과(green result)는 결함을 숨긴 채 사용자가 발견할 때까지 당신을 안심시키는 거짓 위안이 됩니다.
당신이 가장 확신하는 코드일수록 가장 엄격하게 살펴보세요.
코드를 단순히 실행하는 것에 그치지 않고, 코드를 읽고 깨뜨리는 법을 배우세요. 그리고 통과하는 테스트를 정답이 아닌 질문으로 대하십시오.
— Raghu Bharadwaj
Edsger W. Dijkstra의 "Notes on Structured Programming" (EWD249), 1970을 바탕으로 작성되었습니다. 원문은 TECH VEDA에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기