AI 보조 TDD vs 순수 TDD: 나는 반복적인 댄스를 선택했다
요약
AI 코딩 어시스턴트 사용 시 TDD(테스트 주도 개발)의 설계 가치를 유지하기 위한 워크플로우를 제안합니다. AI가 구현을 먼저 제안하는 '성급한 구현'의 함정을 피하고, 테스트를 먼저 생성한 뒤 구현을 요청하는 2단계 프롬프트 전략을 다룹니다.
핵심 포인트
- AI가 구현을 먼저 제안하면 TDD의 설계 가이드 효과가 상실됨
- AI를 구체적인 지침이 필요한 주니어 개발자처럼 대우해야 함
- 테스트 우선 프롬프트(Test-First Prompts) 전략 활용 권장
- 테스트 생성과 구현 단계를 분리하여 AI 워크플로우 최적화
AI 보조 TDD vs. 순수 TDD: 나는 반복적인 댄스를 선택했다
테스트를 채 마치기도 전에 AI 어시스턴트가 구현 코드를 작성하고 싶어 안달이 나 있는 상황에서, 어떻게 진정한 테스트 우선 (test-first) 규율을 유지할 수 있을까요? 이 질문이 몇 달 동안 저를 괴롭혔습니다.
저는 수년간 TDD (테스트 주도 개발) 옹호자였지만, GitHub Copilot 및 Claude Sonnet 4.6과 같은 AI 코딩 어시스턴트의 등장은 제 프로세스에 혼란을 주었습니다. 저의 평소 리듬인 Red-Green-Refactor (레드-그린-리팩터) 과정이 끊임없이 방해받는 느낌이었습니다. 저는 TDD의 이점을 잃지 않으면서 AI를 활용할 방법을 찾아야 했으며, 특히 제가 새로 시작하는 .NET 9 프로젝트에서 더욱 그러했습니다. 저는 단순히 내 테스트 '주변에' AI가 코드를 생성하는 것이 아니라, 진정한 ai tdd dotnet 워크플로우를 원했습니다.
나의 초기 실수와 '조기 구현'의 함정
솔직히 말해서, AI를 저의 TDD 사이클에 통합하려 했던 저의 첫 시도들은 엉망이었습니다. 저는 보통 "문자열을 반전시키는 C# 13 메서드를 작성하고, 그에 대한 단위 테스트를 작성해줘"와 같은 프롬프트를 입력하곤 했습니다. 그러면 거의 매번, Copilot의 인라인 제안이든 Cursor 0.42에서 받은 Claude Opus 4.7의 전체 응답이든, AI는 _구현 (implementation)_을 먼저 내뱉은 다음, 그저 자신이 작성한 코드를 확인해주는 테스트를 내놓았습니다. 그것은 TDD가 아니었습니다. 그것은 "AI 생성 검증이 곁들여진 구현 주도 개발"이었습니다.
이것은 속임수처럼 느껴졌고, 기껏해야 핵심을 놓치고 있는 것처럼 느껴졌습니다. 저에게 있어 TDD의 가치는 코딩을 하기 _전_에 동작을 정의하고, 테스트가 설계를 가이드하도록 하는 데 있습니다. AI가 정답을 미리 제시해 버리면, 그 결정적인 설계 단계가 생략됩니다. 제가 틀렸을 수도 있지만, 저에게 있어 TDD의 마법은 제약 조건, 즉 TDD가 강제하는 사고 과정에 있습니다. 생각을 완성하려는 AI의 열망은 지속적인 장애물이었습니다.
다음은 제가 계속해서 얻게 되었던 결과물과, 제가 적극적으로 피하려고 노력했던 결과물의 단순화된 버전입니다:
// 나의 초기 프롬프트 (머릿속 혹은 명시적): "문자열에서 int를 안전하게 파싱하는 메서드가 필요해."
// 내가 엣지 케이스(edge cases)를 생각하기도 전에 나오는 AI의 성급한 응답:
public static class StringParser
...
구현 자체는 종종 괜찮았지만, 그것은 내 뇌의 TDD 근육 기억 (muscle memory)을 우회해 버렸습니다. 나는 끊임없이 이 "성급한 구현 (premature implementation)" 함정에 빠지곤 했습니다.
내가 찾아낸 리듬: 테스트 우선 프롬프트 (Test-First Prompts)
나는 깨닫는 데 부끄러울 정도로 많은 시간을 허비했습니다. 즉, AI를 매우 유능하지만 문자 그대로만 받아들이는, 아주 구체적인 지침이 필요한 주니어 개발자처럼 대해야 한다는 사실을 말입니다. 핵심은 AI가 오직 테스트만 생성하도록 제한하고, 그 테스트가 실패하는 것을 확인한 후에 구현을 요청하는 것이었습니다. 이것이 나의 핵심적인 tdd ai pair 전략이 되었습니다.
결과적으로 나는 2단계 프롬프트 접근 방식을 갖게 되었는데, 주로 초기 테스트 생성에는 Cursor 0.42의 채팅을 사용하고, 구현 단계의 빠른 완성(completions)에는 Copilot이 포함된 Visual Studio 2026을 사용했습니다.
먼저, 종종 C# 13 및 .NET 9 컨텍스트를 지정하며 테스트를 위한 프롬프트를 작성했습니다:
"C# 13과 .NET 9 환경에서 작업 중입니다. 새로운 정적 메서드인 `GuidUtils.IsValidGuid(string s)`에 대한 단위 테스트 (unit test)가 필요합니다.
null, 빈 문자열, 잘못된 형식의 문자열, 그리고 유효한 GUID를 처리해야 합니다. 구현은 제외하고 오직 테스트에만 집중하세요."
그러면 Claude Sonnet 4.6은 다음과 같이 놀라울 정도로 집중된 결과물을 내놓았고, 나는 이를 테스트 프로젝트에 넣었습니다:
using NUnit.Framework;
using System;
...
나는 테스트를 실행하고, 모든 테스트가 실패하는 것(빨간색!)을 지켜본 후에, AI에게 구현을 요청했습니다. 이때 종종 실패한 테스트 코드를 컨텍스트로 제공하거나 단순히 메서드 시그니처(method signature)를 참조했습니다:
"위의 테스트를 통과할 수 있도록 C# 13으로 `GuidUtils.IsValidGuid` 메서드를 구현하세요. 효율적으로 작성하세요."
그러면 AI는 테스트를 통과(green)시키기 위한 최소한의 코드를 제공했습니다:
using System;
public static class GuidUtils
...
이것은 진정한 test driven ai처럼 느껴졌습니다. 나는 여전히 동작 정의를 주도하고 있었고, AI는 그린 상태(green state)에 도달하기 위한 놀라운 가속기 역할을 했습니다.
여전히 조정 중인 부분: 리팩터링 (Refactoring) 및 엣지 케이스 (Edge Cases)
그래서, 저는 지금 어디에 있을까요? 저는 ai tdd pair가 실행 가능하고 생산적인 워크플로우(workflow)라는 진영에 확고히 서 있습니다. 제 테스트 스위트(test suites)는 더 빠르게 성장하고 있으며, AI가 테스트 시나리오를 제안하는 능력이 매우 뛰어나기 때문에 더 많은 엣지 케이스(edge cases)를 사전에 포착하고 있습니다. 특히 유틸리티 메서드(utility methods)와 잘 정의된 알고리즘(algorithms)에서 강력한 성능을 발휘합니다.
하지만 여정은 끝나지 않았습니다. 예를 들어, 리팩터링(Refactoring)은 여전히 까다로운 영역입니다. AI에게 "가독성을 높이기 위해 이 C# 메서드를 리팩터링해줘"라고 프롬프트(prompt)를 입력할 수는 있지만, AI는 제가 전형적인 TDD 방식으로 수행하는 점진적이고 동작을 보존하는 리팩터링(behavior-preserving refactor)보다는 패턴에 기반한 전체 재작성(full rewrite)을 제안하는 경우가 많습니다. 결국 저는 리팩터링을 더 수동으로 수행하게 되며, AI는 실제 코드 변경보다는 리팩터링을 위한 아이디어를 브레인스토밍(brainstorming)하는 용도로 더 많이 사용하고 있습니다. 또한, 미묘한 비즈니스 규칙이 포함된 복잡한 도메인 로직(domain logic)의 경우, 제 이해도를 진정으로 내재화하기 위해 여전히 처음 몇 개의 테스트는 직접 작성하곤 합니다. 사람마다 경험은 다를 수 있겠지만, 저에게는 테스트 생성(test generation) 부분이 여전히 가장 큰 이점입니다.
저에게 남은 가장 큰 과제는 코드나 테스트를 작성하기 전인 설계(design) 단계에서 복잡한 도메인 모델링(domain modeling)을 위해 AI를 활용하는 것입니다.
만약 .NET 환경에서, 특히 기존 코드베이스(codebases)의 리팩터링을 위해 TDD와 AI를 페어링(pairing)해 보셨다면, 어떤 부분이 잘 안 되었는지 또는 어떤 놀라운 워크플로우를 발견하셨는지 꼭 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기