MAKE IT SIMPLE
AI가 코드를 짜주는 시대, 왜 TDD가 더 중요해졌나 본문
AI 코딩의 역설
요즘 개발 현장에서 AI 코딩 도구를 안 쓰는 곳을 찾기 어렵다. GitHub Copilot, Claude, ChatGPT 등 AI에게 "이런 기능 만들어줘"라고 말하면 순식간에 코드가 완성된다.
그러다보니 예전에는 개발 공수 때문에 신중하게 결정했던 것들이 이제는 "일단 만들어보고 바꾸죠"가 되어버렸다.
결과는? 기획 변경이 잦아지고, UI/UX 수정이 늘어나고, 스펙이 수시로 바뀐다.
빠른 변경이 가능해진 건 좋다. 하지만 빠른 변경 ≠ 안전한 변경이다. AI가 아무리 빨리 코드를 짜줘도, 그 코드가 기존 기능을 망가뜨리지 않았는지는 인간이 확인해야 한다.
여기서 TDD의 새로운 가치가 드러난다.
SDD(Spec-Driven Development)란?
SDD는 Spec-Driven Development, 즉 스펙을 먼저 정의하고 그에 맞춰 개발하는 방식이다.
요구사항 분석 → 스펙 문서화 → 구현 → 검증
AI와 협업할 때 SDD는 특히 효과적이다. AI는 명확한 지시를 받을수록 좋은 결과물을 낸다. "적당히 예쁘게"보다 "너비 320px, 둥근 모서리 8px, 배경색 #F5F5F5"라고 말해야 원하는 결과가 나온다.
그래서 많은 팀이 SDD를 도입하고 있다.
1. PRD(Product Requirements Document) 작성
2. 기술 스펙 정의
3. Acceptance Criteria(AC) 도출
4. AI에게 스펙 기반으로 구현 요청
여기까지는 좋다. 문제는 그 다음이다.
여기서 문제: 스펙이 바뀌면?
현실에서 스펙은 바뀐다. 아니, AI 시대에는 더 자주 바뀐다.
전형적인 시나리오
1. PM이 기능 A의 스펙을 정의한다
2. 개발자가 AI에게 스펙을 주고 구현을 요청한다
3. AI가 뚝딱 코드를 만들어낸다
4. 리뷰 중 PM이 말한다: "여기 조금만 바꾸면 안 될까요?"
5. "금방 될 것 같은데요?" (AI가 빠르니까)
6. 스펙 수정 → AI에게 다시 요청 → 새 코드 생성
이 사이클이 반복되면서 문제가 커진다.
AI는 전체 맥락을 기억하지 못한다
AI에게 "기능 A를 이렇게 바꿔줘"라고 요청하면, AI는 기능 A만 수정한다. 하지만 기능 A의 변경이 기능 B, C에 영향을 줄 수 있다는 건 AI가 알려주지 않는다.
- 기능 A에서 사용하던 상태 구조가 바뀌면?
- 기능 A를 호출하던 다른 컴포넌트는?
- 기능 A의 API 응답 형태가 달라지면?
이 모든 영향 범위를 인간이 직접 파악하고 검증해야 한다.
휴먼 에러의 함정
스펙 변경이 한두 번이면 괜찮다. 하지만 5번, 10번 반복되면 어떨까?
- "이 부분은 지난번에 확인했으니까 괜찮겠지" → 버그
- "여기는 안 건드렸으니까 문제없겠지" → 버그
- "시간 없으니까 핵심만 테스트하자" → 버그
AI가 만든 코드는 빠르게 쌓이지만, 인간의 검증 능력은 그대로다. 이 간극에서 버그가 발생한다.
SDD 와 TDD의 결합: AC 기반 TDD
여기서 TDD(Test-Driven Development)가 등장한다. TDD의 핵심은 간단하다: 테스트를 먼저 작성하고, 테스트를 통과하는 코드를 작성한다.
테스트 작성(Red) → 구현(Green) → 리팩토링(Refactor) → 반복
AI 시대에 TDD가 중요해진 이유는, 테스트 코드가 스펙의 실행 가능한 버전이 되기 때문이다.
Acceptance Criteria를 테스트로 변환
스펙 문서의 AC를 그대로 테스트 코드로 옮긴다.
AC 예시:
- 사용자가 "저장" 버튼을 누르면 데이터가 저장된다
- 저장 성공 시 "저장되었습니다" 토스트가 표시된다
- 저장 실패 시 에러 메시지가 표시된다
테스트 코드:
describe('저장 기능', () => {
it('저장 버튼 클릭 시 데이터가 저장된다', async () => {
const { getByText } = render(<SaveForm />)
fireEvent.press(getByText('저장'))
await waitFor(() => {
expect(mockSaveAPI).toHaveBeenCalled()
})
})
it('저장 성공 시 토스트가 표시된다', async () => {
mockSaveAPI.mockResolvedValue({ success: true })
const { getByText } = render(<SaveForm />)
fireEvent.press(getByText('저장'))
await waitFor(() => {
expect(getByText('저장되었습니다')).toBeTruthy()
})
})
it('저장 실패 시 에러 메시지가 표시된다', async () => {
mockSaveAPI.mockRejectedValue(new Error('Network Error'))
const { getByText } = render(<SaveForm />)
fireEvent.press(getByText('저장'))
await waitFor(() => {
expect(getByText('저장에 실패했습니다')).toBeTruthy()
})
})
})
이제 "저장 기능의 스펙"은 문서가 아니라 실행 가능한 테스트로 존재한다.
스펙 변경 시 일어나는 일
기획이 바뀌어서 AC가 수정되었다고 하자
- 저장 성공 시 "저장되었습니다" 토스트가 표시된다
+ 저장 성공 시 "저장되었습니다" 토스트가 표시되고, 목록 화면으로 이동한다
1. 테스트 수정: 새로운 AC에 맞게 테스트 업데이트
2. 테스트 실행: 당연히 실패 (Red)
3. 구현 수정: AI에게 "저장 후 목록으로 이동하도록 수정해줘" 요청
4. 테스트 통과 확인: 모든 테스트가 통과하면 (Green) 스펙을 만족
핵심은 이거다: 테스트가 실패하면, 뭔가 빠뜨린 거다.
AI가 코드를 수정했는데 테스트가 깨졌다면, 그건 AI가 기존 스펙을 위반했다는 신호다. 인간이 일일이 체크하지 않아도 테스트가 알려준다.
SDD + TDD 워크플로우
두 방법론을 결합하면 이런 흐름이 된다:
스펙 정의 → AC 도출 → 테스트 작성(Red) → 구현(Green) → 리팩토링(Refactor)
↑ │
└──────────────────────────────┘
(다음 AC로 반복)
[스펙 변경 시]
AC 수정 → 테스트 수정 → Red → Green → ...
1. 스펙 정의
요구사항을 분석하고 기능 명세를 작성한다. PRD, 기술 스펙 문서 등
2. AC(Acceptance Criteria) 도출
"이 기능이 완성되었다고 말할 수 있는 조건"을 나열한다. JIRA 를 사용한다면 개발 티켓에 AC에 대한 내용이 들어간다.
기능: 로그인
AC:
- 이메일과 비밀번호를 입력하고 로그인 버튼을 누르면 로그인된다
- 이메일 형식이 올바르지 않으면 "올바른 이메일을 입력하세요" 메시지가 표시된다
- 비밀번호가 틀리면 "비밀번호가 일치하지 않습니다" 메시지가 표시된다
- 로그인 성공 시 홈 화면으로 이동한다
3. 테스트 작성 (Red)
AC를 테스트 코드로 변환한다. 이 시점에서 구현은 없으니 테스트는 실패한다.
4. 구현 (Green)
AI에게 스펙과 실패하는 테스트를 주고 구현을 요청한다.
> "다음 테스트를 통과하는 로그인 컴포넌트를 만들어줘"
AI가 만든 코드로 테스트가 통과하면 Green.
5. 리팩토링 (Refactor)
테스트가 통과하는 상태에서 코드 품질을 개선한다. 테스트가 보호막이 되어 리팩토링 중 실수해도 바로 알 수 있다.
6. 스펙 변경 시
AC가 바뀌면 테스트부터 수정한다. 테스트가 실패하는 걸 확인하고(Red), 구현을 수정해서 통과시킨다(Green).
왜 TDD가 AI 시대의 가드레일인가?
1. AI 출력물의 품질 검증 도구
AI가 만든 코드가 "돌아가는지"는 AI도 확인할 수 있다. 하지만 "요구사항을 만족하는지"는 테스트가 확인한다.
2. 스펙 변경의 영향 범위 즉시 파악
스펙이 바뀌면 관련 테스트를 수정하고 실행한다. 실패하는 테스트가 곧 "영향받는 기능 목록"이다.
3. 회귀 버그 방지
AI에게 "A 기능 수정해줘"라고 했는데, AI가 실수로 B 기능을 건드렸다면? B 기능의 테스트가 실패하면서 알려준다.
4. 인간이 검토할 범위 축소
테스트가 통과하면 인간은 "테스트가 커버하지 못하는 부분"만 집중해서 검토하면 된다.
5. AI에게 명확한 성공 기준 제공
"이 기능 만들어줘"보다 "이 테스트를 통과하는 코드 만들어줘"가 더 명확한 지시다.
결론: AI 시대의 개발자는 무엇을 잘해야 될까
요즘 AI가 코드를 개발자보다 전문적이고 빠르게 작성해주다 보니 나의 역할이 바뀌고 있는게 체감이 된다. 확실히 AI는 훌륭한 실행자다. 하지만 이게 맞는지 판단하고 그 가이드 라인을 상세하게 정해주고 매니징 하는건 여전히 인간의 몫이자 개발자들의 핵심역량이 될거 같다.
TDD는 이 판단을 도와주는 도구다. AC를 테스트로 작성해두면:
- 스펙 변경 시 영향 범위를 자동으로 파악할 수 있다
- AI 출력물이 요구사항을 만족하는지 즉시 확인할 수 있다
- 회귀 버그 없이 안전하게 변경을 적용할 수 있다
AI가 빠르게 코드를 만들어줄수록, 테스트라는 가드레일이 더 중요해진다. 속도와 안전을 동시에 잡으려면 SDD와 TDD를 함께 사용해야 한다. 빠르게 달리려면 브레이크가 좋아야 한다. TDD는 AI 시대의 브레이크다.