Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

23장. 테스트를 Agent의 눈으로 쓰기 — 스스로 검증하게 만들기

5장에서 한 줄로 지나간 문장이 있다.

테스트가 없는 프로젝트에서
Agent의 “수정 완료” 는 의견에 불과하다.

이 장은 그 문장을 실무로 옮긴다.

관점 하나만 바꾸면 된다.

테스트는 품질 보증 장치이기 전에
Agent가 세상을 보는 유일한 창이다.


사람과 Agent가 테스트를 쓰는 방식은 다르다

사람Agent
실패하면 코드를 열어본다실패 메시지만 본다
애매하면 동료에게 묻는다다음 시도로 넘어간다
감으로 이상함을 느낀다통과하면 끝났다고 판단한다

🔥 세 번째가 핵심이다.

Agent에게 “통과” 는 완료의 근거다.

그래서 테스트가 약하면
Agent는 약한 근거로 자신 있게 완료를 보고한다.


세 가지 용도

에이전틱 코딩에서 테스트는 세 역할을 한다.

1️⃣ 현재 동작을 고정한다

레거시를 고칠 때 가장 먼저 필요한 것이다.

@Test
fun `부분 취소 시 포인트는 비례 배분된다`() {
    // 지금 이렇게 동작한다 (옳은지는 별개)
}

옳고 그름을 판단하지 않고 현재를 박제한다.
36장의 Characterization Test다.

2️⃣ 버그를 증명한다

9장에서 본 재현 테스트다.

실패하는 테스트가 있으면
Agent는 목표를 정확히 안다.

3️⃣ 회귀를 막는다

가장 익숙한 용도이고,
Agent 시대에 가치가 몇 배로 올랐다.

사람은 하루 세 번 코드를 고치지만
Agent는 한 시간에 세 번 고친다.


Agent에게 좋은 테스트의 조건

세 가지다.

빠르다

테스트 시간Agent의 행동
10초매번 돌린다
1분자주 돌린다
4분마지막에 한 번 돌린다
15분사실상 안 돌린다

⚠️ 느린 테스트는 없는 테스트에 수렴한다.

Feedback Loop가 4분마다 한 바퀴 돌면
Agent는 검증 없이 여러 수정을 몰아서 한다.

그래서 실패했을 때 원인을 짚기도 어려워진다.

빠른 단위 테스트를 따로 분리해두는 것이
에이전틱 코딩에서는 큰 투자 대비 효과를 낸다.

./gradlew test --tests '*UnitTest'    # 12초
./gradlew test                        # 4분

결정적이다

가끔 실패하는 테스트는 최악이다.

1차 시도  실패 → Agent가 코드를 고친다
2차 시도  통과 → "고쳐졌습니다"

⚠️ 고친 것이 아니라 운이 좋았던 것인데
Agent는 자기 수정이 효과가 있었다고 학습한다.

그리고 그 잘못된 인과가 Context에 남는다.

시간·랜덤·외부 호출·실행 순서 의존은
Agent와 함께 일하기 전에 정리해야 한다.

실패 메시지가 말을 한다

Agent가 받는 유일한 단서다.

❌ expected: true but was: false

✅ 주문 취소 시 포인트 환급은 한 번만 발생한다
   expected size: 1 but was: 2
   actual: [PointHistory(+1000, REFUND), PointHistory(+1000, REFUND)]

두 번째를 받은 Agent는
“환급이 두 번 일어났다” 를 즉시 안다.

첫 번째를 받으면 코드를 다시 읽어야 한다.
Context를 더 쓰고, 추측이 늘어난다.

좋은 실패 메시지는
Agent의 Context를 아껴준다.


테스트 작성을 시킬 때

기존 테스트를 먼저 읽히는 것이 순서다.

@src/test/kotlin/order/OrderCancelTest.kt 를 읽고
같은 스타일로 부분 취소 케이스를 추가해줘.

- 새 테스트 기반 클래스를 만들지 마
- 기존 IntegrationTestBase 를 그대로 상속해
- given/when/then 주석 스타일을 유지해

⚠️ 이 지시가 없으면 Agent는 새 스타일을 만든다.

일반적으로 좋은 스타일일 수는 있다.
하지만 테스트 파일마다 스타일이 다른 프로젝트가 된다.


Agent가 테스트를 망가뜨리는 네 가지 패턴

3장에서 예고한 그 문제다.

패턴겉보기실제
단정문 삭제통과검증이 사라짐
@Disabled 추가통과문제를 미룸
기댓값을 실제값으로 수정통과버그를 정답으로 승격
예외를 잡아서 무시통과증상만 감춤

세 번째가 가장 자주 일어나고 가장 위험하다.

// Agent의 수정
assertThat(histories).hasSize(1)   →   assertThat(histories).hasSize(2)

버그가 명세가 됐다.

방지는 세 겹으로 한다.

# CLAUDE.md
- 테스트를 삭제하거나 @Disabled 를 추가하지 않는다
- 실패하는 테스트의 기댓값을 바꾸지 않는다
  (기댓값이 틀렸다고 판단되면 수정하지 말고 보고한다)
# 작업 지시
테스트 코드는 수정하지 말고 구현만 고쳐줘
# Diff 검토
git diff --stat src/test/

세 번째가 마지막 방어선이다.
테스트 디렉터리의 변경량만 먼저 본다.


테스트가 아예 없는 영역

레거시에서는 흔한 상황이다.

이때 순서는 이렇다.

flowchart LR
    A[현재 동작 파악] --> B[Characterization Test]
    B --> C[통과 확인]
    C --> D[그 다음에 수정]

⚠️ 테스트 없이 수정부터 하지 않는다.

무엇이 깨졌는지 알 방법이 없는 상태에서
Agent의 속도는 위험이 된다.

36장에서 이 작업을 본격적으로 다룬다.


이 장의 핵심

  • 테스트는 Agent가 세상을 보는 유일한 창이다
  • Agent에게 “통과” 는 완료의 근거다 — 약한 테스트는 약한 근거를 만든다
  • 용도는 셋이다 — 현재 동작 고정, 버그 증명, 회귀 방지
  • 느린 테스트는 없는 테스트에 수렴한다 — 빠른 단위 테스트를 분리한다
  • 가끔 실패하는 테스트는 Agent에게 잘못된 인과를 학습시킨다
  • 좋은 실패 메시지는 Agent의 Context를 아껴준다
  • 기존 테스트를 먼저 읽히지 않으면 새 스타일이 생긴다
  • 기댓값을 실제값으로 고치는 수정이 가장 위험하다 — 버그가 명세가 된다
  • 테스트 디렉터리의 변경량을 따로 확인하는 것이 마지막 방어선이다