본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 26. 17:38

AI 계획 생성기에게 테스트를 별도의 하위 작업으로 분리하지 말라고 명령한 이유

요약

AI 계획 생성기가 작업을 분할할 때 테스트를 구현과 별도의 하위 작업으로 분리하면 CI 실패를 유발할 수 있습니다. 이를 해결하기 위해 테스트를 반드시 코드 수정 작업과 동일한 하위 작업에 포함하도록 프롬프트 규칙을 수정하여 병렬 작업의 안정성을 확보했습니다.

핵심 포인트

  • 테스트와 구현을 별도 작업으로 분리하면 CI 실패 위험 증가
  • 병렬 작업 간의 의존성 문제로 인해 테스트 브랜치에서 구현 코드 부재 발생
  • 프롬프트에 '테스트를 동일 작업에 포함'하는 규칙 추가로 해결
  • AI의 논리적 분해(Decomposition)와 실제 실행 환경 간의 간극 관리 필요

실행 결과는 실패로 표시되었습니다. 세 개의 하위 작업 중 두 개는 깔끔하게 병합되었습니다. "test_inbox_service_unread_propagation.py에서 is_sent=True가 읽음으로 처리되는 것에 대한 테스트 추가"라는 제목의 세 번째 작업은 끝내 완료되지 않았습니다. CI(지속적 통합)는 제한 횟수까지 재시도했으나 모두 실패했고, 결국 포기했습니다. 실제 코드의 3분의 2가 이미 그린 브랜치(green branches)에 반영되었음에도 불구하고 전체 계획이 폐기되었습니다.

해결책은 프롬프트(prompt) 내의 단 한 문단이었습니다. 디스패처(dispatcher)의 코드 변경도 아니었고, 새로운 CI 플래그도 아니었습니다. 단지 다음과 같은 규칙을 추가했을 뿐입니다: 만약 하위 작업이 코드를 도입하거나 수정한다면, 해당 코드에 대한 유닛 테스트(unit tests)는 동일한 하위 작업에 포함되어야 한다. "테스트를 별도의 작업으로 분리하는" 패턴은 금지된다.

제가 관찰한 내용과 왜 AI가 잘못된 분해(decomposition)를 선택했는지, 그리고 그 간극을 메운 정확한 프롬프트 규칙을 소개합니다.

실제로 일어난 일

Codens Purple에는 제가 '계획 생성기(plan generator)'라고 부르는 것이 있습니다. 이는 하나의 PRD(제품 요구 사항 문서)나 버그 리포트를 받아 하위 작업으로 나누는 시스템의 부분입니다. 각 하위 작업은 각자의 Git 브랜치에서 할당되어 다른 작업들과 병렬로 실행되며, CI가 통과(green)되면 베이스(base)로 다시 병합됩니다. 실제로 분할을 수행하는 계획 생성기의 부분은 내부적으로 '분석 프롬프트(analyze prompt)'라고 부르는 것에 의해 구동되는데, 이는 모델이 "이 작업을 어떻게 나눌 것인가"를 결정할 때 보는 시스템 프롬프트(system prompt)입니다.

opsguide-back이라는 프로젝트의 한 버그에 대해, 계획 생성기는 다음과 같은 세 가지 작업을 생성했습니다:

1. test_inbox_service_unread_propagation.py에서 
   is_sent=True가 읽음으로 처리되는 것에 대한 테스트 추가
2. inbox_service.py의 _store_messages_batch를 수정하여 
   표시...

사람 검토자로서 이를 읽는다면 매우 훌륭해 보입니다. 세 가지의 깔끔한 관심사(concerns)로 나뉘어 있고, 독립적으로 검토하기 쉬우며, 수정되는 파일 간의 중복도 없습니다. 교과서적인 병렬화(parallelization)입니다.

결국 실패했습니다. 하위 작업(Sub-task) 2와 3은 모두 완료되어 병합되었습니다. 테스트 전용이었던 하위 작업 1은 CI(지속적 통합)에서 계속 실패했습니다. 해당 브랜치에는 테스트 파일에 대한 변경 사항만 포함되어 있었습니다. 테스트가 검증하려는 구현 함수들은 해당 브랜치에 아직 존재하지 않았는데, 그 이유는 구현 코드가 이 브랜치에서는 볼 수 없는 형제 브랜치(sibling branch)에 있었기 때문입니다. pytest는 테스트를 수집하고 헬퍼(helpers)를 임포트하려 시도했지만, 검증하려는 동작이 단순히 존재하지 않았습니다. 재시도, 재시도, 재시도, 그리고 포기. 실행 실패.

잔인한 점은 만약 병합 순서가 운 좋게 두 구현 브랜치가 모두 반영된 후 테스트 브랜치를 마지막에 배치했다면, 테스트는 통과했을 것이라는 사실입니다. 하지만 우리는 그 순서를 보장할 수 없습니다. 각 하위 작업은 각자의 속도로 경주(race)합니다.

AI가 이렇게 행동한 이유

이것은 모델의 실패가 아니었습니다. 모델은 모든 범용 분해 휴리스틱(decomposition heuristic)이 지시하는 대로 정확히 수행했습니다. 병렬로 진행할 수 있도록 테스트를 구현에서 분리하십시오. 이는 리뷰어와 병합 대기열(merge queue)이 순서를 정직하게 유지하고, 개발자가 병합 전에 테스트 PR을 구현 PR 위로 리베이스(rebase)할 수 있는 인간 팀에게는 올바른 조언입니다.

모델이 알지 못했던 점은 우리의 디스패치 시스템(dispatch system)이 각 하위 작업을 독립된 격리 브랜치에서 실행한다는 사실입니다. 각 하위 작업은 베이스 브랜치와 자신의 변경 사항만을 볼 수 있으며, 그 외에는 아무것도 볼 수 없습니다. 형제 하위 작업의 작업 내용은 병합 시점 전까지는 보이지 않습니다. 이것은 소프트웨어 개발에 관한 보편적인 사실이 아닙니다. 이는 구체적으로 우리가 병렬 에이전트(parallel agents)를 실행하는 방식의 특성입니다. 모델의 학습 코퍼스(training corpus) 중 그 어떤 것도 이 제약 조건이 적용된다고 알려주지 않는데, 그 이유는 대부분의 코퍼스가 인간 팀에 관한 것이기 때문입니다.

따라서 모델은 자신이 아는 가장 많이 인용되는 분해 패턴을 선택했지만, 그것은 공교롭게도 우리의 디스패처(dispatcher)에는 맞지 않는 방식이었습니다. 실수는 프롬프트(prompt)에 있었습니다. 우리는 우리 시스템에서의 "병렬"이 실제로 어떤 규칙을 따르는지 알려주지 않은 채, 모델에게 병렬 작업을 계획하라고 요청하고 있었던 것입니다.

이것은 제가 마주쳤던 수많은 AI 에이전트 실패 사례들의 일반적인 형태입니다. 에이전트의 추론 (Reasoning) 능력이 부족한 것이 아닙니다. 에이전트는 잘못된 우주에서 올바르게 추론하고 있는 것입니다. 프롬프트 (Prompt)가 그 우주를 설명하는 것을 잊었기 때문입니다.

해결 방법 (The fix)

우리는 분석 프롬프트 (Analyze prompt)에 이 블록을 추가했습니다. 이것이 유일한 변경 사항입니다.

## CRITICAL: Tests live with their implementation

NEVER split tests for new behaviour into a separate sub-task. Every sub-task
...

여기서는 두 가지 요소가 작동하고 있습니다. 첫 번째는 명시적인 "금지 (FORBIDDEN)" 프레임워크 (Framing)입니다. 두 번째는 실무에서 더 중요하다고 생각되는 제목 휴리스틱 (Title heuristic)입니다. 모델은 본문을 작성하기 전에 제목을 작성합니다. 만약 모델이 제목 단계에서 스스로를 포착할 수 있다면, 잘못된 계획은 애초에 생성되지 않으므로 나중에 이를 수정하기 위해 별도의 단계를 거칠 필요가 없습니다.

우리는 또한 동일한 프롬프트 내의 퓨샷 (Few-shot) 예시들을 다시 작성했습니다. 이전에는 구현 (Impl) 하위 작업의 ## Steps 섹션에 소스 코드 파일 수정 사항만 나열되어 있었습니다. 수정 후에는 모든 구현 하위 작업 예시가 구현 파일 수정과 테스트 파일 수정을 나란히 나열합니다. 대략 다음과 같습니다:

 ## Steps
 1. Edit src/inbox_service.py: in _store_messages_batch,
    set is_read=True when message.sender_email == account_owner_email.
...

이 작은 차이 (Diff)가 동작을 바꾸는 부분입니다. 모델은 퓨샷 (Few-shot) 예시에 대해 매우 강력하게 패턴 매칭 (Pattern-match)을 수행합니다. 모든 예시가 구현과 테스트를 묶어서 보여준다면, 모델도 동일한 형태를 만들어냅니다.

이 규칙이 도입된 이후, 계획 생성기 (Plan generator)는 새로운 동작에 대해 "...에 대한 테스트 추가"와 같은 하위 작업을 생성하는 것을 멈췄습니다. 테스트 전용 실패 모드 (Test-only failure mode)가 사라진 것입니다.

예외 사항 (The exception)

여전히 괜찮은 테스트 전용 하위 작업의 형태가 하나 있습니다. 만약 이미 베이스 브랜치 (Base branch)에 존재하는 코드에 대해 회귀 테스트 (Regression test)를 소급 적용하는 경우라면, 테스트 전용 하위 작업이 허용됩니다. 그 이유는 원래의 실패 원인과 대칭적입니다. 구현이 이미 메인 (Main) 브랜치에 존재할 때는, 테스트 전용 브랜치만으로도 컴파일, 임포트 (Import), 그리고 단언 (Assert)에 필요한 모든 것을 갖추게 됩니다. pytest가 함수를 찾아내고, 테스트가 실행되며, CI가 통과하게 됩니다.

프롬프트는 모델이 새로운 규칙을 과도하게 적용하여 정당한 백필 (backfill) 작업을 거부하기 시작하지 않도록 이를 명시적으로 지적합니다. 프롬프트의 해당 문구는 대략적으로 "이 규칙은 이 계획에서 도입되는 새로운 동작에 관한 것이지, 지금까지 존재했던 모든 테스트 전용 하위 작업에 관한 것이 아니다"라는 내용을 담고 있습니다.

일반화 (Generalizing)

더 큰 교훈은 AI 에이전트가 기본적으로 인간 팀의 분해 (decomposition) 방식을 따르려 한다는 점이며, 이는 당신의 디스패치 시스템 (dispatch system) 또한 인간 팀처럼 동작할 때는 문제가 되지 않습니다. 하지만 대부분의 에이전트 디스패치 시스템은 그렇지 않습니다. 우리의 시스템은 상호 가시성이 없는 격리된 브랜치에서 하위 작업을 실행합니다. 어떤 팀은 수명이 긴 공유 워크트리 (worktree)에서 에이전트를 실행하기도 하고, 어떤 팀은 직렬화 (serialize)하여 실행하기도 합니다. 이 각각의 방식은 무엇을 분리할 수 있고 무엇을 분리할 수 없는지에 대해 각기 다른 보이지 않는 제약 조건을 생성합니다.

에이전트는 당신이 어떤 방식을 사용하는지 알지 못합니다. 코드베이스로부터 이를 추론할 수도 없는데, 왜냐하면 그러한 제약 조건 중 어느 것도 코드에 인코딩되어 있지 않기 때문입니다. 그것들은 디스패처 (dispatcher)에 존재합니다.

따라서 에이전트가 병렬 하위 작업을 계획하도록 허용하기 시작할 때 해야 할 일은, 프롬프트 토큰을 사용하여 분리 가능한 것과 분리할 수 없는 것 사이의 경계선을 긋는 데 시간을 쓰는 것입니다. 우리에게 그 경계선은 "새로운 코드에 대한 테스트는 새로운 코드와 함께 존재해야 한다"였습니다. 다른 누군가에게는 "마이그레이션 (migration)을 그것에 의존하는 코드와 절대 분리하지 마라"가 될 수도 있고, 혹은 "설정 변경 (config change)을 그것을 사용하는 배포 (deployment)와 절대 분리하지 마라"가 될 수도 있습니다. 규칙의 형태는 모델이 아니라 전적으로 당신의 디스패처에 달려 있습니다.

제가 제안하는 패턴은 계획 프롬프트에 디스패처가 부과하는 제약 조건을 나열하는 단일 "CRITICAL" 섹션을 추가하는 것입니다. 모델이 본문을 생성하기 전에 잘못된 계획을 스스로 거부할 수 있도록 타이틀 단계의 휴리스틱 (heuristic)을 사용하세요. 그리고 퓨샷 (few-shot) 예시들을 올바른 형태를 보여주도록 다시 작성하세요. 왜냐하면 모델은 실제로 그것을 복사하기 때문입니다.

우리는 Codens를 Codens로 다시 구축합니다. 이와 같은 모든 프롬프트 규칙은 실제 실행이 실패하는 것을 지켜본 뒤, 그것을 방지할 수 있었던 단 한 문장을 추가함으로써 만들어졌습니다. 병렬 플래너 (parallel planner)가 엔드 투 엔드 (end-to-end)로 어떻게 작동하는지 보고 싶다면, 영어 랜딩 페이지는 https://www.codens.ai/en/에서 확인할 수 있습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0