31P by xguru | ★ favorite | 댓글 5개

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의 융합은 코드 자동생성과 테스트 품질 관리 모두에 긍정적인 변화를 줄 수 있을 것으로 기대함
GeekNews Weekly에 포함된 글입니다. 에디터 코멘트 보기

댓글과 토론

다른 개발 전용 AI 서비스는 어떤 파이프라인을 사용하고 있을까 곰곰이 고민하게 되네요.

(이런거 보고있자면)조금 있으면 전뇌를 위해 전기 자극선을 뇌에 삽입 할 것만 같아요.

테스트 코드를 넣는 건 좋은 거 같긴 한데, 이 사람이 만든 프로그램에 메리트는 없는 거 같아요
cline이나 aider에서도 커맨드라인 실행하고 결과 받는 게 돼서 그 프로그램에서 프롬프트만 잘 쓰는 게 다른 편의성을 생각했을 때 나을 거 같습니다

빌더 아이오에서 만든 마이크로 에이전트도 비슷한 접근입니다. https://github.com/BuilderIO/micro-agent 저도 LLM과 TDD를 여러 번 해봤는데, 디자인 시스템 등으로 추상화도 잘 해야하고요. 컨벤션이랑 패턴도 잘 정립되어 있어야 하고요. 테스트케이스는 보통 직접 짜는 편입니다. (인간 말로라도?) 무엇보다 이 글에서도 말하듯이 결합도가 낮고 응집도 높은 모듈을 잘 설계해야 한정된 컨텍스트 윈도우에 맥락을 밀어넣을 수 있더라고요.

LLM이 작은 범위의 코드는 잘 봐주지만 전체적인 설계, 큰 시야가 아직 아쉬운 편인데,
TDD랑 결합해서 조금씩 개선하는 방식 좋은 것 같네요.