재미와 이익을 위한 "LLM 기반 TDD"
(blog.yfzhou.fyi)TDD(Test-Driven Development)와 LLM의 결합
- TDD는 프로그램 작성을 시작하기 전, 포괄적인 단위 테스트를 먼저 작성하는 개발 방법론임
- 테스트가 사실상 사양서 역할을 맡기 때문에, 최종적으로 모든 테스트가 통과하면 코드의 정확성을 어느 정도 증명할 수 있음
- 전통적으로 TDD는 생산성을 저해하거나 비효율적이라는 비판을 받기도 함
- 하지만 LLM의 등장으로 테스트 작성과 코드를 반복 수정하는 과정이 훨씬 수월해짐
내가 LLM을 평소에 사용하는 방법
- Github Copilot 같은 도구를 적극적으로 사용해 왔음
- LLM은 반복 패턴을 찾고 다음 몇 줄을 자동완성하는 데 능숙하지만, 문제 전체를 깊이 이해하여 완결성 있는 모듈을 단번에 만들어내는 데에는 종종 어려움이 있음
- 문제 해결에 필요한 맥락을 과도하게 제공하면 모델이 주제에서 벗어나기 쉬움
- 필요에 따라 정보(오류 출력 등)를 부분적으로만 제공하며 작업을 진행하면, 모델이 디버깅에도 훌륭한 도움을 줌
- IDE, 터미널, 챗 인터페이스 간 복사-붙여넣기를 반복하는 과정에서 마찰이 생긴다는 점을 체감함
자동화할 수 있을까?
- 이러한 과정을 자동화하기 위해 직접 event loop 개념을 도입함
- 첫 번째 프롬프트에 구현할 함수의 사양과 함수 시그니처를 명시하면, 모델은 단위 테스트와 코드의 초안을 제시함
- 이 코드를 ‘sandbox’ 디렉토리에 저장하고 자동으로
go test
를 실행함 - 테스트가 실패하면, 두 번째(반복) 프롬프트에 기존 코드와 테스트 결과(컴파일 오류나 실패 정보)를 함께 전송함
- 모델은 이를 바탕으로 수정된 테스트 및 구현 코드를 다시 제안함
- 모든 테스트가 통과할 때까지 이 과정을 반복함
- 이 접근법은 맥락을 과도하게 누적하지 않고도 점진적인 개선을 가능케 함
- 모델이 동일 테스트 케이스에서 반복적으로 실패할 수도 있는데, 그럴 경우 사람이 직접 문제 부분을 짚어 힌트를 제공함
- LLM이 만든 테스트가 충분히 엄격한지 의심해야 하는 ‘감시자 부재’ 문제를 인지해야 함
- 동일한 오류나 불완전한 설계를 코드와 테스트가 함께 공유할 가능성이 있음
- 따라서 인간이 추가로 테스트 케이스를 강화해주는 과정이 중요함
- 필요하다면, mutation testing 같은 기법을 AI로 실험해볼 수도 있음
LLM 기반 개발과 인지 부하(cognitive load)
- LLM과 함께 TDD를 적용하면, 일반적인 알고리즘 문제뿐 아니라 실제 종속성이 있는 코드베이스에서도 가능할 것으로 예상함
- 단, 프로젝트 구조를 더 작은 단위로 쪼개어 유지보수성을 높이고, 각 디렉토리/패키지가 독립적으로 테스트 가능해야 함
- 각 패키지는 주요 타입 정의(
shared.go
)와 특정 로직을 담당하는 파일(x.go
) 및 테스트(x_test.go
)로 분리하여 인지 부하를 줄이는 방식을 권장함 - AI를 활용하는 과정에서 전체 코드를 매번 모델에 제공하는 대신, 특정 부분만 선택적으로 포함해 모델이 집중하도록 유도함
- 이는 테스트 커버리지를 높이면서 모듈 간의 결합도를 줄여 장기 유지보수에도 이점을 줌
- 큰 프로젝트라도 작고 명확한 단위로 쪼개어, 각 단위의 논리를 풍부하게 담되 범위를 최소화하는 구조를 지향함
마무리
- AI의 발전 속도를 고려하면, 내일 당장 새로운 아키텍처가 등장해서 LLM의 한계를 뛰어넘을 수도 있음
- 따라서 10만 줄이 넘는 대규모 레거시 코드를 급작스럽게 리팩터링하기보다는, 작은 규모에서부터 TDD와 LLM의 결합 가능성을 탐색해보는 것을 추천함
- TDD와 LLM의 융합은 코드 자동생성과 테스트 품질 관리 모두에 긍정적인 변화를 줄 수 있을 것으로 기대함
빌더 아이오에서 만든 마이크로 에이전트도 비슷한 접근입니다. https://github.com/BuilderIO/micro-agent 저도 LLM과 TDD를 여러 번 해봤는데, 디자인 시스템 등으로 추상화도 잘 해야하고요. 컨벤션이랑 패턴도 잘 정립되어 있어야 하고요. 테스트케이스는 보통 직접 짜는 편입니다. (인간 말로라도?) 무엇보다 이 글에서도 말하듯이 결합도가 낮고 응집도 높은 모듈을 잘 설계해야 한정된 컨텍스트 윈도우에 맥락을 밀어넣을 수 있더라고요.
테스트 코드를 넣는 건 좋은 거 같긴 한데, 이 사람이 만든 프로그램에 메리트는 없는 거 같아요
cline이나 aider에서도 커맨드라인 실행하고 결과 받는 게 돼서 그 프로그램에서 프롬프트만 잘 쓰는 게 다른 편의성을 생각했을 때 나을 거 같습니다