프롬프트 ≈ 코드, 물결표가 다시 불러온 TDD

AI 코딩 시대에 TDD가 왜 다시 주목받는지, 그리고 TDD가 원래부터 갖고 있던 가치는 무엇인지 정리한 글

사람이 코드를 짜던 시대에서, AI가 짜주는 시대로

수학과에 있다 보니 증명을 배울 때 A = B = C = D = A 같은 순환 논증을 자주 접합니다.

컴퓨터 쪽으로 눈을 돌려도 비슷한 등호의 사슬이 보입니다. 고급언어는 어셈블리로, 어셈블리는 바이너리로, 바이너리는 결국 회로 위의 이진 신호로 번역됩니다. 각 단계는 형태만 다를 뿐 같은 의미를 유지한 채 다음 층위로 넘어갑니다.

그런데 최근 몇 년 사이 이 사슬의 맨 앞에 새로운 단계가 하나 붙었다는 생각이 듭니다. 바로 프롬프트입니다.

이렇게 쓰고 싶어지는 순간들이 분명 있습니다. 자연어로 원하는 것을 설명하면 그 즉시 동작하는 코드가 나오니까요.

등호가 아니라 물결표였다

하지만 실제로 AI로 코딩을 해보면 이 등호가 그렇게 깔끔하게 성립하지 않는다는 걸 금방 알게 됩니다. 원하는 결과를 얻기 위해 비슷한 문장의 프롬프트를 몇 번이고 고쳐 쓴 경험이 다들 있을 겁니다. 그리고 그 끝에서 돌이켜보면, 차라리 처음부터 직접 코드를 짰다면 더 빨리 끝났을 일인 경우도 적지 않습니다.

그래서 이 관계는 등호(=)보다는 물결표(≈)에 더 가깝다고 생각합니다. 비슷하긴 한데, 완전히 같지는 않다는 뜻입니다. 그리고 그 작은 간극이 반복 프롬프팅이라는 형태로 비용을 만들어냅니다.

그렇다고 손으로 다 짤 수도 없다

그렇다고 모든 코드를 직접 작성하는 방향으로 되돌아갈 수도 없는 노릇입니다. 불이 위험하다고 해서 전기 사용 자체를 포기하지는 않는 것처럼, AI가 완벽하지 않다는 이유로 AI 활용 자체를 그만둘 수는 없습니다. 결국 남는 선택지는 하나입니다. 물결표를 등호에 최대한 가깝게 좁히는 방법을 찾는 것입니다.

물결표를 좁히려는 시도, 하네스 엔지니어링

실제로 이 간극을 줄이려는 흐름이 업계에서 이미 이름을 얻고 있습니다. 바로 하네스 엔지니어링(harness engineering)입니다.

하네스 엔지니어링의 핵심 아이디어는 이렇습니다. AI 에이전트에게 "우리 코딩 컨벤션을 지켜줘"라고 프롬프트로 부탁하는 것과, 컨벤션을 어기면 자동으로 병합을 막는 린터를 붙여두는 것은 전혀 다른 접근입니다.

전자는 확률적인 순응에 기대는 방식이고, 후자는 결정론적인 제약을 거는 방식입니다. 소프트웨어 엔지니어링 진영에서는 이 구분을 에이전트 = 모델 + 하네스라는 짧은 공식으로 요약하기도 합니다. 모델 자체를 바꿀 수 없다면, 모델을 둘러싼 컨텍스트와 도구, 검증 루프, 품질 게이트를 설계해서 신뢰도를 끌어올리자는 것입니다.

그중에서도 테스트 코드가 특별한 이유

하네스를 구성하는 여러 장치 중에서 저는 테스트 코드가 유독 큰 비중을 차지한다고 생각합니다. 이유는 단순합니다. 자연어로 적은 지침은 결국 다시 자연어로만 검증할 수 있지만, 테스트 코드는 실행해서 통과 여부를 기계적으로 확인할 수 있습니다. 확률적인 지침을 결정론적인 판정 기준으로 바꿔주는 셈입니다.

앞서 (프롬프트) = (고급언어)는 아직 시기상조라고 말씀드렸습니다. 그런데 흥미롭게도 (테스트 코드) = (고급언어)라는 명제는 지금의 AI로 충분히 성립할 수 있다고 생각합니다. 테스트 코드는 애초에 입력과 기대 출력이라는 형식으로 의도를 촘촘하게 못 박아두는 글쓰기이기 때문에, AI가 그 사양을 실제 구현으로 옮기는 작업에서는 프롬프트 단독으로 구현을 부탁할 때보다 훨씬 안정적인 결과를 얻을 수 있습니다.

물론 코드를 먼저 직접 짜고 나서 AI에게 "이 코드에 맞는 테스트를 만들어줘"라고 부탁할 수도 있습니다. 그런데 이 순서로 해본 사람은 알겠지만, 이렇게 나온 테스트는 제 의도를 온전히 담지 못하는 경우가 많습니다. AI는 이미 작성된 코드의 동작을 기준으로 테스트를 맞춰 쓸 뿐이라, 코드에 숨어 있는 잠재적 버그까지 함께 정당화해버리는 일이 생깁니다. 그래서 순서가 중요합니다. 테스트를 먼저 쓰고 그 다음에 구현이 따라와야, 테스트가 제 의도를 보존하는 사양 역할을 할 수 있습니다.

이 지점에서 자연스럽게 TDD(테스트 주도 개발)가 다시 떠올랐습니다.

TDD는 원래도 좋은 방법론이었다

사실 TDD가 새로운 아이디어는 아닙니다. 이미 오래전부터 검증된 좋은 방법론이었습니다.

마이크로소프트 리서치와 노스캐롤라이나 주립대 연구진이 마이크로소프트 3개 팀과 IBM 1개 팀을 대상으로 진행한 산업 현장 사례 연구에 따르면, TDD를 적용한 팀들의 출시 전 결함 밀도는 TDD를 쓰지 않은 유사 프로젝트 대비 40퍼센트에서 90퍼센트까지 낮아졌습니다. 다만 같은 연구에서 팀들은 개발 초기 시간이 15퍼센트에서 35퍼센트가량 늘었다고도 보고했습니다.

전문 개발자 24명을 TDD 그룹과 비TDD 그룹으로 나눈 조지와 윌리엄스의 통제 실험에서도 비슷한 그림이 나옵니다. TDD 그룹은 블랙박스 기능 테스트 통과율 기준으로 18퍼센트 더 높은 품질을 보였고, 개발 시간은 16퍼센트 더 걸렸습니다.

결국 품질은 확실히 올라가지만 시간과 유지보수 비용이라는 청구서가 함께 따라온다는 뜻입니다. 그래서 TDD는 좋은 방법론이라는 공감대가 있으면서도, 대기업 정도가 아니면 널리 자리 잡지 못했던 것 같습니다.

AI가 바꾼 비용 구조

그런데 AI가 이 비용 구조를 흔들고 있습니다. 테스트 코드를 손으로 한 줄씩 쓰고 유지보수하는 비용이 줄어들면, TDD가 주던 품질상의 이점만 남기고 발목을 잡던 비용은 덜어낼 수 있습니다.

이 방향의 실증 연구도 이미 나오고 있습니다. 풀스택 웹 애플리케이션을 자연어 설명이나 디자인으로부터 생성하면서 TDD 루프를 내장한 TDDev 프레임워크는 기존 최고 수준 방법 대비 전체 정확도가 14.4퍼센트 향상되었다고 보고했습니다.

개발자 15명을 대상으로 한 TiCoder 연구에서는, 테스트로 의도를 명확히 하며 대화형으로 코드를 생성하는 워크플로가 참여자들이 AI가 만든 코드를 더 정확히 판단하도록 돕고 인지 부담도 줄여주었다고 밝혔습니다. 같은 연구에서 이상화된 사용자 피드백 조건이라는 단서가 붙긴 하지만, 다섯 번의 상호작용 안에서 코드 생성 정확도가 평균 45.97퍼센트포인트 향상됐다는 수치도 함께 제시되었습니다.

물론 이런 수치들은 특정 벤치마크와 실험 조건 안에서 나온 것이라 그대로 일반화하기는 조심스럽습니다. 그래도 방향성만큼은 뚜렷합니다. 테스트를 먼저 정의하고 AI가 그 테스트를 통과시키도록 반복시키는 구조가, 프롬프트 한 번으로 구현을 통째로 요청하는 방식보다 더 안정적인 결과를 만들어낸다는 것입니다.

AI와 무관하게 테스트 코드 자체가 주는 이점

여기까지는 AI와 맞물렸을 때 테스트 코드가 갖는 가치를 이야기했습니다. 그런데 사실 테스트 코드는 AI 유무와 상관없이 그 자체로도 이점이 분명합니다. 오히려 이 이점들이 있었기 때문에 TDD가 AI 시대 이전부터 좋은 방법론으로 꼽혀온 것이라고 보는 게 맞을 것 같습니다.

첫째는 자동화가 주는 편리함입니다. 기능을 하나 고칠 때마다 화면을 켜고 시나리오를 손으로 하나씩 눌러보는 대신, 테스트 슈트를 한 번 돌리는 것으로 검증을 끝낼 수 있습니다. 사람이 매번 반복해야 했던 확인 작업을 기계에게 위임하는 셈입니다.

둘째는 수정의 안전성입니다. 테스트가 촘촘히 깔려 있으면 코드를 고칠 때 생기는 사이드이펙트를 곧바로 잡아낼 수 있습니다. 리팩터링이나 의존성 변경처럼 코드베이스 전반에 영향을 줄 수 있는 작업도, 테스트가 실패하는 지점을 보고 영향 범위를 빠르게 좁힐 수 있어서 훨씬 부담 없이 손댈 수 있습니다. 테스트가 없는 코드는 건드릴 때마다 무엇이 깨질지 모르니 자연스럽게 손을 대기 꺼려지게 되는데, 테스트 코드는 이런 심리적 장벽 자체를 낮춰줍니다.

셋째는 문서화입니다. 테스트 코드는 "이 함수는 이런 입력에 이렇게 동작해야 한다"는 걸 실행 가능한 형태로 남겨둔 문서라고 볼 수 있습니다. 주석이나 별도 문서는 코드가 바뀌어도 갱신되지 않은 채 방치되기 쉽지만, 테스트 코드는 실제 코드와 함께 실행되고 실패하기 때문에 최신 상태를 유지할 수밖에 없습니다. 새로운 팀원이 코드를 파악할 때도 테스트 코드를 읽으면 그 함수나 모듈이 의도한 동작 범위를 훨씬 빠르게 감을 잡을 수 있습니다.

정리하면 테스트 코드는 AI가 등장하기 훨씬 전부터 이미 세 가지 몫을 하고 있었습니다. 검증의 자동화, 수정의 안전망, 그리고 살아있는 문서화입니다. 여기에 AI가 테스트 코드를 작성하고 유지보수하는 비용을 낮춰주면서, 원래도 좋았던 방법론이 이제는 비용 부담 없이 누릴 수 있는 방법론으로 바뀌고 있는 것입니다.

다시, TDD라는 오래된 답으로

정리해보면 이야기는 이렇게 흘러왔습니다. 프롬프트와 코드 사이에는 등호가 아니라 물결표가 있었고, 그 간극을 좁히려는 시도가 하네스 엔지니어링이라는 이름으로 진행되고 있었습니다. 그리고 그 하네스를 이루는 여러 장치 중에서 테스트 코드는 유독 등호에 가까이 다가선 도구였습니다.

그런데 이 글을 쓰면서 계속 든 생각은, TDD의 진짜 값어치는 결함 밀도가 몇 퍼센트 줄었다는 숫자보다 더 앞쪽에 있다는 것입니다. 테스트를 먼저 쓴다는 건 단순히 검증 도구를 하나 만드는 일이 아닙니다. 입력에 어떤 값이 들어올 수 있는지, 어디서 예외가 터질 수 있는지, 이 함수를 고쳤을 때 어떤 사이드이펙트가 튈 수 있는지를 코드를 쓰기도 전에 먼저 고민하게 만드는 일입니다.

테스트 코드를 한 줄도 못 쓰겠다면, 그건 대개 테스트 작성 실력이 부족해서가 아니라 그 기능을 아직 제대로 이해하지 못했다는 신호인 경우가 많습니다. TDD가 좋은 방법론으로 꼽혀온 진짜 이유는 여기에 있다고 생각합니다. 구현에 들어가기 전에 문제의 본질을 한 번 더 짚고 넘어가게 만든다는 것입니다.

AI는 이 구조에서 딱 한 가지만 바꿔놓았습니다. 테스트를 쓰고 유지보수하는 손품이 줄어든 것뿐입니다. 문제를 먼저 이해하고 그 이해를 테스트라는 형태로 못 박아두는 일, 그리고 그 위에서만 구현이 안전하게 따라올 수 있다는 원칙 자체는 AI가 등장하기 한참 전부터 그대로였습니다.

그래서 저는 지금의 흐름을 "AI 덕분에 TDD가 다시 쓸 만해졌다"는 쪽보다, "AI가 방해물을 치워주니 원래 옳았던 방법론이 제자리를 찾아가고 있다"는 쪽으로 읽고 싶습니다. 결국 남는 건 프롬프트도 하네스도 아니고, 구현보다 먼저 문제를 이해하려는 태도 그 자체인 것 같습니다.