You can't trust agent tests

Alex O'Callaghan

에이전트 테스트는 믿을 수 없다

에이전트를 이용해 53개의 Enzyme 테스트 스위트를 React Testing Library로 마이그레이션했습니다. 테스트는 모두 통과했고 코드도 일관성 있어 보였지만, 자세히 들여다보니 회귀를 잡아내지 못하는 테스트, 잘못된 대상을 검증하는 테스트, 사실상 아무것도 테스트하지 않는 테스트가 있었습니다.

에이전트가 생성한 테스트에서는 이런 문제를 놓치기 쉽습니다. 결과물은 매끈해 보이고 코드는 깔끔하며 양도 방대하기 때문입니다.

Enzyme에서 RTL로의 마이그레이션

Enzyme 테스트는 수년간 낮은 우선순위로 백로그에 쌓여 있던 기술 부채였습니다. 이런 잘 정의된 기계적인 작업이야말로 에이전트가 잘해야 하는 일입니다. 복잡도별로 작업을 나누고, 제가 만든 prepare-mr-skill을 이용해 커밋을 구조화하고 설명을 작성했더니 잘 되는 듯했습니다. 적어도 겉보기에는 그랬습니다. 테스트는 마이그레이션됐고, 통과했으며, 코드 변경 사항도 합리적으로 보였습니다.

병합하기 전에 평소대로 팀 리뷰 과정을 거쳤고, 겉보기엔 올바르지만 회귀를 잡아내지 못하는 테스트들을 발견했습니다. 예를 들어 특정 ARIA role을 가진 요소가 렌더링되지 않았음을 검증하는 테스트가 있었는데, 해당 role 자체가 유효하지 않아 컴포넌트가 애초에 그 요소를 렌더링할 수 없는 경우였습니다. 또한 disabledaria-disabled를 혼동한 경우도 있었습니다. 두 속성은 동작이 다르고 접근성 측면에서 중요하지만 보기에는 비슷해서 그럴듯해 보이는 테스트가 실패하지 않은 채 잘못된 검증을 하고 있었던 것입니다.

왜 이런 일이 생기는가

이 문제는 에이전트에만 국한되지 않습니다. 사람이 직접 작성해도 이런 테스트를 만들기 쉽습니다. 다만 잘못된 검증을 작성하는 사람이라면 보통 컴포넌트를 잘 알기 때문에 테스트가 너무 쉽게 통과한다는 느낌을 알아차릴 수 있습니다. 에이전트에게는 그런 직관이 없습니다. 구현 코드를 패턴 매칭해 그럴듯해 보이는 결과물을 만들어내고는 넘어가 버립니다. 게다가 에이전트가 한 번에 만들어내는 코드 양이 워낙 많다 보니 이런 문제가 쉽게 묻혀 버립니다.

구현이 이미 존재하는 경우(예: 테스트 마이그레이션, 커버리지 보강, 수년 전에 작성된 테스트 검증)에는 테스트 주도 개발(TDD)이 제공하는 자연스러운 안전장치가 작동하지 않습니다. 이미 동작하는 코드를 대상으로 테스트를 작성하는 것이므로 잘못된 검증이 실패할 기회 자체가 없습니다. 이를 잡아내는 유일한 방법은 의도적으로 컴포넌트를 망가뜨려 보는 것입니다. 컴포넌트를 임시로 변경해 테스트가 이를 감지하는지 확인한 뒤 되돌리는 방식입니다.

검증이 빠지지 않게 만들기

처음에는 프롬프트를 고쳐 에이전트가 이런 실수를 반드시 확인하도록 강제하면 되겠다고 생각했습니다. RTL 마이그레이션 스킬을 업데이트해 모든 테스트를 실패 상태에 대해 검증하도록 명시했습니다.

하지만 제대로 동작하지 않았습니다. 에이전트는 요구사항을 고려하는 듯하다가 가끔 뭔가를 잡아내고는 그냥 넘어가 버렸습니다. 검증을 반드시 지켜야 할 제약이 아니라 체크박스 정도로 취급한 것입니다.

문제의 일부는 구조적입니다. 에이전트에 많은 테스트를 한 번에 마이그레이션하라고 하면 작업이 길어지고 컨텍스트가 가득 찹니다. 컨텍스트가 커질수록 모델은 지시사항에 대한 집중을 잃기 시작합니다. 일부 모델은 컨텍스트 윈도우의 끝에 가까워지면 작업을 일찍 마무리하려는 “컨텍스트 불안”을 보이기도 합니다. 이 때문에 에이전트가 이런 검증 지시를 잊거나 건너뛰게 됩니다.

한 번에 처리하는 대신 테스트 스위트 단위로 마이그레이션을 실행해 봤습니다. 컨텍스트 윈도우를 짧게 하면 완료해야 한다는 압박이 줄어들어 에이전트가 검증 단계에서 좀 더 천천히 작업할 여유가 생길 거라 생각했기 때문입니다. 에이전트가 검증을 수행할 가능성은 높아졌지만, 매번 모든 테스트를 검증하기보다는 “몇 가지 핵심 테스트”만 검증하겠다고 자의적으로 결정하는 경우가 많았습니다.

마지막으로 마이그레이션과 리뷰 단계를 두 개의 별도 에이전트 작업으로 나누기로 했습니다. 각 테스트 스위트를 순회하며 두 개의 에이전트 프롬프트를 순차적으로 실행하는 bash 스크립트를 작성했습니다. 하나는 마이그레이션용, 하나는 리뷰용이었습니다.

#!/bin/bash

echo "Starting migration..."

declare -a files=(
  "path/to/test-suite.test.tsx"
)

for i in "${files[@]}"; do
  echo "Migrating $i"

  agent -p --force --output-format stream-json --stream-partial-output --model claude-4.6-sonnet-medium \
    "Migrate $i to use RTL. Ensure linting passes and then commit your changes in a single commit."

  agent -p --force --output-format stream-json --stream-partial-output --model claude-4.6-sonnet-medium \
    "Review the RTL tests in $i -

		* Identify any unnecessary tests, remove them
		* Identify any tests not following best practices, fix them
		* For every single test intentionally change the implementation to break the specific feature tested and confirm the test fails as expected, fix them if they don't

		Ensure linting passes and then commit your changes in a single commit."

  echo "Migrated $i"
done

검증에 집중된 작업이 주어지자 에이전트는 스스로 할 일 목록을 만들고 각 테스트를 빠짐없이 처리하며, 리뷰에서 발견된 문제뿐 아니라 그 이상의 개선까지 해냈습니다.

비용은 얼마나 들었나

이 방식은 효과가 있었지만 비용이 훨씬 더 많이 들었습니다. 모든 마이그레이션은 Cursor에서 claude-4.6-sonnet-medium을 사용해 22개 테스트 스위트 배치에 대해 실행했습니다.

방식비용
한 번에 일괄 처리~$13
스위트별 처리~$32
스위트별 처리 + 리뷰어~$62

스위트별 리뷰어 단계를 거치자 사람이 리뷰에서 잡아냈던 모든 문제가 해결됐지만, 한 번에 일괄 처리하는 방식에 비해 비용이 거의 5배 증가했습니다.

에이전트 덕분에 더 많은 일을 할 수 있게 됐고, 그렇지 않았다면 백로그에 그대로 남아 있었을 현대화 작업을 해낼 수 있었습니다. 하지만 믿을 수 있는 결과물을 제대로 만드는 데는 생각보다 많은 비용이 듭니다. 할 거라면 제대로 해야 한다고 생각하지만, 소요 시간은 크게 줄더라도 실제 금전적 비용 측면에서는 트레이드오프가 존재합니다.

조직은 이렇게 높아진 생산성이 AI 벤더 청구서의 비용 증가로만 끝나지 않고 실제 수익 증가로 이어지도록 의도적으로 설계해야 합니다.

핵심 요약

두 개의 프롬프트로 나눈 스크립트는 더 나은 프롬프트가 아니라 다른 아키텍처였습니다. 한 에이전트에게 마이그레이션과 검증을 같은 패스에서 하라고 하면 작업 구조 자체가 검증을 방해합니다. 어떤 과정이 계속 누락된다면 해결책은 대개 더 나은 지시가 아니라 그 단계를 건너뛸 수 없는 워크플로를 만드는 것입니다.

개념적으로 여기서 리뷰어가 하고 있는 일은 뮤테이션 테스팅입니다. 각 검증마다 컴포넌트에 변화를 주면 테스트가 실패하는지를 묻는 것입니다. Stryker 같은 뮤테이션 테스팅 도구를 테스트 파이프라인에 도입하면 이런 문제를 잡아내는 데 도움이 되고, 에이전트가 수동으로 하던 작업을 줄여 비용도 절감할 수 있습니다.

에이전트 워크플로를 설계하는 데 드는 시간도 작업의 일부이며, 에이전트 사용으로 인한 생산성 향상을 평가할 때 함께 고려해야 합니다. 그것이 믿을 수 있는 결과물과 그저 믿을 수 있어 보이는 결과물을 가르는 차이입니다.

원문은 Alex O'Callaghan님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.