조성개발실록
테마 변경
태그
방명록
ABOUT ME

AI에게 'TDD해라'하면 회귀 버그가 ~10% 증가한다? - Testing/TDD 시리즈 (1)

TILDevelopmentTesting

March 31, 2026



! 주의 : TIL 게시글입니다. 다듬지 않고 올리거나 기록을 통째로 복붙했을 수 있는 뒷고기 포스팅입니다.

좋은 테스트 코드는 무엇일지, TDD는 구체적으로 어떤 철학인지, 등을 막 AI랑 얘기하다가
웹서칭을 마친 클로드가 갑자기 흥미로운 발견을 뱉었습니다

흥미로운 발견! TDAD

아?? "TDD 해라"라는 일반적 지시를 내리면 오히려 회귀 버그가 최대 10% 증가한다고요??
이런 이유를 말하고 있는 것 같은데요

  • 장황한 지시문으로 인한 필수 컨텍스트 손실: 만약 TDD의 절차를 설파하면... -> 컨텍스트 토큰을 실제 필요한 레포지토리 컨텍스트가 아닌 장황한 TDD 이론으로 사용해버려 성능 저하
  • 목표는 크지만 타겟팅이 없는 수정 시도: TDD 지시를 받은 에이전트는 야심차게 코드를 수정하고 많은 파일을 건드리는 경향을 보이고 -> 오히려 "코드 간의 의존성"을 모른 채로 건드리다가 부수적인 피해를 유발

근데 이게 소형 모델로만 실험했고 통계적으로 유의미한 샘플 크기도 아니기도 하고, 등등 그대로 믿기는 조심스럽긴 합니다.
사실은 TIL글인 김에 그냥 후킹해봤어요. 죄송합니다 ㅎㅎ;;

그래도 아, 좋은 테스트가 뭔지, TDD가 자세히 어떤 철학에서 나온 실행관례인지, 를 내가 알아야 잘 다룰 수 있겠구나,
싶어서 이를 주제로 오늘 배운 내용들 정리해두려고 TIL 쓰러 왔습니다.

Testing에 관한 학파가 있는거 아셨나요

얼마전에 TSBM(TypeScript Backend Meetup)에 갔다가, 이런 얘기를 들었는데요

테스트에는 두 분파가 있다. 주로 Mock에 대한 의견 차이로 나뉜다.

와 같은 이야기였습니다.
재밌지 않나요.

고전파(시카고 학파)와 Mockist(런던 학파)로 나뉘는데요

  • 고전파: 가능한 실제 객체를 사용하고 Mock은 외부 의존성만 제한적으로
    • Kent Beck 같은 사람들이 여기에 속한다고 하고
    • 테스트 단위를 행위(Behavior) 단위로 봅니다. 여러 클래스가 협력하는 경우에도 "행위"가 기준이므로 하나의 단위로 간주할 수 있습니다.
    • 리팩토링에 강한 편이지만 테스트 실패 시 원인 추적이 불리할 수 있습니다.(여러 객체가 얽혀 있어서)
  • Mockist: 테스트 대상을 제외한 모든 협력 객체들을 Mock으로 대체하여 테스트 대상에 집중
    • 테스트 단위를 단일 클래스/메서드/모듈 등으로 철저히 격리합니다.
    • 실패 원인이 명확하게 드러나지만 내부 구현에 과의존하게 될 수 있습니다(구현의 거울 문제)

블랙박스와 화이트박스

또한 블랙박스 테스트와 화이트박스 테스트라는 개념도 있는데요

  • 화이트박스: 내부 구현까지 들여다보며 검증합니다
    • ex. "이 함수는 A를 호출 -> B를 거쳐 -> C를 반환하니까, A,B를 Mock하고 C를 assert하자"
  • 블랙박스: 내부를 들여다보지 않은 채 인터페이스(계약)만 검증합니다.
    • ex. "이 함수에 X를 넣으면 Y가 나와야 한다"

블랙박스 vs 화이트박스

화이트박스는 코드 줄과 분기 하나하나까지 구현 상세를 fix하려면 괜찮겠네요
만약 (프로덕트를 만들 때는 보통 안그러겠지만) 테스트 커버리지 100%가 목표라면 이렇게 할 것 같습니다.
예전에 Node.js 코어 레포지토리에 기여하면서 테스트 코드를 작성할 때는 화이트박스 방식에 속했던 것 같아요
Node.js의 특성상 그럴만하긴하죠

프로덕트는 계속 변하고 확장되어야 하고, 유저 시나리오 위주로 설명되어야 하므로 블랙박스가 더 지향할 쪽이라고 생각합니다
AI하고 일할 때도 더 이쪽이 지향점이지 않을까요?

저같은경우도 테스트가 블랙박스에 가까울 때 리팩토링할 때 가드레일이 되어준다고 많이 느끼고
화이트박스에 가까운 테스트 코드를 작성해버리면 리팩토링할 때 테스트 코드가 같이 깨지니 여간 불편한게 아닙니다.
실상 테스트 코드는 제 기준에서는 리팩토링 내성이 테스트 코드를 작성하는 이유 중 하나인데, 화이트박스 테스트는 그 역할을 못해줍니다.

이상적 블랙박스 테스트의 원칙? by Vladimir Khorikov

Unit Testing: Principles, Practices, and Patterns라는 책을 쓰신 Vladimir Khorikov라는 분이 계신데
그분이 정리한 기준에 의하면 좋은 테스트의 4가지 속성은

좋은 테스트의 4가지 속성 설명
리팩토링 내성 (Resistance to refactoring) 내부 구현을 바꿔도 테스트가 깨지지 않음
회귀 방지 (Protection against regressions) 버그를 잡아냄
빠른 피드백 (Fast feedback) 빠르게 실행됨
유지보수성 (Maintainability) 읽기 쉽고, 고치기 쉬움

런던 학파인 Mockist의 치명적 약점은 첫 번째로, 리팩토링 내성이 낮다는 점으로 꼽힙니다
Mock이 많을 수록 "어떻게"에 결합되고 리팩토링마다 테스트를 같이 고쳐줘야 합니다.

현대 실무적 합의 - 중간 지점

원칙 주요 내용 예시 (Do / Don't)
1. 관찰 가능한 행위 테스트 최종 결과(반환값, 상태 변경, 외부 호출)에 집중 Do: 모듈 반환값 확인, 이메일 발송 여부 확인
2. 구현 세부사항 지양 내부에서 어떻게 돌아가는지는 검증하지 않음 Don't: 내부 함수 호출 순서, private 변수 상태 체크
3. Mock은 외부 의존성에만 제어할 수 없는 영역(DB, API)에만 목킹 적용 Do: 외부 API 호출
Don't: 같은 프로세스 내의 클래스/함수
4. 유저 시나리오 기반 작성 기술적인 단계보다 사용자의 행동 흐름으로 작성 Do: "버튼 클릭 시 데이터 저장"
Don't: "saveHandler가 repository 호출"

마틴 파울러, Sociable vs. Solitary 단위 테스트

단위 테스트에서 단위란 무엇인가?

를 이야기합니다.

Solitary: 격리형 -> SUT(System Under Test, 테스트 대상)를 제외한 모든 협력자를 mock으로 대체합니다.
런던학파(Mockist)의 기본 전략에 속하고, "단위 = 하나의 클래스/함수" 가 됩니다.

[테스트] → [SUT] → [Mock A]
                   → [Mock B]
                   → [Mock C]

Sociable: 사고형 -> 협력 객체를 실제 그대로 사용
시카고학파(고전파, Mock 최소화)의 기본 전략에 속하고, "단위 = 하나의 행위(behavior)" 로 정의합니다(여러 클래스가 협력해도 하나의 단위).

[테스트] → [SUT] → [실제 A] → [실제 B]
               → [실제 C]

테스트 경계 (Test Boundary)

위에서 Khorikov라는 사람이 의존성에 의한 테스트 경계 분류를 제시했는데요

의존성 종류 예시 Mock 사용 여부
관리 불가 + 관찰 가능 외부 API, 이메일 발송, 결제 반드시 Mock — 부수 효과가 외부(유저나 타 시스템)에 노출됨
관리 불가 + 관찰 불가 DB (내 앱만 전용으로 접근) 상황에 따라 — 통합 테스트에서는 실제 객체 사용을 권장
관리 가능 + Private 내부 클래스, 유틸 함수 절대 Mock 금지 — 실제 객체를 사용하여 리팩토링 내성 확보
관리 가능 + Shared 같은 프로세스의 다른 모듈 가급적 실제 사용 — 의존성을 가진 채로 실제 동작을 검증

결정트리로 나타내면 이정도?

                    관리 가능한가?
                   /            \
                Yes              No
           (in-process)     (out-of-process)
                |                |
        공유 상태인가?      관찰 가능한가?
         /        \          /        \
       Yes        No       Yes        No
        |          |        |          |
    [주의필요]  [실제사용]  [Mock]     [Mock?실제?]


이렇게 해서 대충 막 찾아보면서 공부해보면서 메모했던 것들을 옮겨뒀는데요
다음번에는 이 개념들이 프론트엔드 테스팅에서 어떻게 적용될 수 있는지 TIL로 남겨보고
또 그 다음번에는 진짜 책이나 강의를 한번 봐보고 배워본 다음 TIL로 남겨보면 괜찮겠네요

여담으로 아까 맨 처음에 살펴본 "TDD하라고 그냥 대충 시키면 오히려 역효과다" 고 한거,
실제로 저도 은근 경험하는 내용인데요(게다가 Opus 4.6같은 대형 모델에서도)
만약 TDD로 할건데, 먼저 위 요구사항에 대한 테스트 먼저 구상해보자 이라고 대충 던져주면
뭐가 호출되면 store의 setState가 트리거된다 같은 화이트박스 테스트를 제안하곤 합니다
이런 것들은 구현에 너무 종속적이고, 관리 가능한 경계에 있는 것 같은데 말이죠
무슨 테스트가 나한테 좋을지 생각을 안해두면 그냥 이대로 OK, 그대로 해줘. 하게 되고
나중에 와서 살짝만 건드려도 테스트가 터져있어요
그래서 이런 내 목적과 상황에 좋은 테스트 코드에 대해 생각해둘 필요성을 느끼는 요즘입니다

이만 마쳐요