요즘 AI를 활용해서 개발하다보면, 코드 작성 속도가 그전과는 비교도 안되게 빨라지긴 했다.
다만, 그만큼 한 가지 고민도 같이 생겼다.
바로 AI가 만들어준 코드가 정말 안전한가?에 대한 고민이다.
특히, 운영 중인 백엔드 시스템에서는 단순히 코드가 동작하는 것만으로는 부족하다.
기능이 바뀌어도 기존 동작이 깨지지 않아야 하고, 수정 후에도 어느 정도 확신을 가지고 배포할 수 있어야 한다.
그래서 이번에는 AI가 TDD(Test Driven Development) 흐름을 따르도록
Skill로 강제하는 방식으로 개발을 진행해보았다.
TDD란?
TDD는 Test Driven Development의 약자로, 말 그대로 테스트가 개발을 주도하는 방식이다.
일반적으로는 코드를 먼저 만들고, 나중에 테스트를 작성하는 흐름이 익숙하다.
하지만 TDD에서는 순서가 반대다.
먼저 실패하는 테스트를 만들고, 그 테스트를 통과할 만큼만 코드를 작성한 뒤,
마지막으로 코드를 정리하는 방식이다.
🔴 RED
- 실패하는 테스트를 먼저 작성한다.
- 이 단계에서 기능을 테스트 코드로 정의한다.
🟢 GREEN
- 방금 만든 테스트를 통과할 수 있도록 코드를 작성한다.
- 이 단계의 목표는 클린 코드가 아니라, 빠르게 테스트를 통과하는 것이다.
🔵 REFACTOR
- 동작은 유지하되, 코드를 읽기 좋게 정리한다.
- Green의 "통과하는 코드"를 "유지보수 가능한 코드"로 바꾼다.
- ex. 공통함수 사용, Util Class 사용 ...
쉬운 예제로 살펴보기
예를 들어 이런 요구사항이 있다고 해보자.
로그인 실패 횟수 5회 이상이면 계정을 잠금 처리해줘.
일반적인 개발 흐름이라면 Account 클래스를 먼저 만들고, 그 뒤에 테스트를 작성할 가능성이 높다.
하지만 TDD에서는 먼저 실패하는 테스트를 작성한다.
🔴 RED
@Test
void 로그인_실패_5회면_계정_잠김() {
Account account = new Account();
for (int i = 0; i < 5; i++) {
account.failLogin();
}
assertTrue(account.isLocked());
}
아직 Account 클래스도 없거나, isLocked 기능이 없을 수 있다.
그러므로 해당 테스트케이스는 당연히 실패한다.
하지만 이 실패가 중요하다.
왜냐하면 이 테스트가 곧 우리가 만들 기능(코드)의 기준이 되기 때문이다.
🟢 GREEN
class Account {
private int failCount = 0;
private boolean locked = false;
void failLogin() {
failCount++;
if (failCount >= 5) {
locked = true;
}
}
boolean isLocked() {
return locked;
}
}
일단 테스트가 통과되도록 구현한다.
목표는 테스트를 통과시키는 것이므로 코드나 구조가 조금 투박해도 상관없다.
🔵 REFACTOR
class Account {
private static final int MAX_FAIL_COUNT = 5;
private int failCount = 0;
private AccountStatus status = AccountStatus.ACTIVE;
void failLogin() {
failCount++;
if (failCount >= MAX_FAIL_COUNT) {
status = AccountStatus.LOCKED;
}
}
boolean isLocked() {
return status == AccountStatus.LOCKED;
}
}
마지막으로 의미 있는 상수를 분리하고, boolean 값보다 상태를 표현할 수 있는 구조로 변경한다.
테스트가 이미 있기 때문에 리팩토링을 해도 기존 동작이 깨졌는지 바로 확인할 수 있다.
그동안 TDD 적용이 힘들었던 이유
위 개념만 보면 TDD는 정말 좋아 보인다.
테스트를 먼저 만들고, 그 테스트를 기준으로 개발하고, 리팩토링까지 안정적으로 진행한다.
문제는 우리 서비스에 도입하기 어렵다는 점이다.
1️⃣ 이미 자리잡은 레거시 코드베이스
- 테스트 없는 코드가 대부분인 경우가 많다.
- 기존 코드를 수정하거나 재사용하려면 현재 동작을 보장하는 테스트가 먼저 필요하다.
- private 메서드 안에서 DB, 파일 I/O, 외부 API 호출이 섞여 있으면 테스트 격리도 쉽지 않다.
2️⃣ 배포가 잦은 환경
- 장애가 났다.
- Red, Green, Refactor를 지켜서 개발해야지
- n시간 후...
이런 상황에서는 TDD보다 빠른 원인 파악과 복구가 우선될 때가 많다.
3️⃣ TDD 경험과 설계 역량 부족
- 대부분은 코드 작성 → 테스트 코드 작성 순서에 익숙하다.
- 기능을 작게 쪼개는 능력, Mock 전략, 테스트 경계 설정이 생각보다 어렵다.
하지만 AI와 함께라면?

하지만, AI Native 시대에서는 이 3가지 한계점을 극복할 수 있다고 생각한다.
AI는 이미 방대한 코드베이스와 패턴을 학습하고 있기 때문에
테스트 작성, Mock 전략, 경계 설정과 같은 설계 역량을 일정 수준 이상으로 보완할 수 있다.
또한, 테스트 작성과 코드 구현을 동시에 빠르게 수행할 수 있기 때문에
시간 제약이 있는 환경에서도 TDD 흐름을 유지하는 것이 가능하다.
즉, 사람이 하기 어려웠던 TDD 이론이 AI에게는 오히려 더 잘 맞는 방식일 수 있다고 생각했다.
그래서 실제로 이 방식을 적용해보았다.
💡 AI Native 시대에서 TDD는 무섭지 않다!
AI + TDD 적용기
실제로 AI에게 TDD 흐름을 맡겨보았다.
기존 기능에서 GitLab, Bitbucket API를 지원하고 있었고, 여기에 GitHub API 연동을 추가해야 했다.
1️⃣ Skills 설치
필자 전용 마켓플레이스를 운영하고 있는데, 여기서 다운받아서 사용하면 된다. (skill 상세 내용)
npx skills add https://github.com/myeongju-kim/marketplace.git --skill test-driven-development


2️⃣ Skills 활용 (codex)
$test-driven-development github api 연동을 추가해줘
AI는 TDD Skill에 맞춰 아래 순서로 작업을 진행했다.
🔴 RED


🟢 GREEN

🔵 REFACTOR

구현 결과 확인

작업이 끝난 뒤에는 아래 기준으로 결과를 확인했다.
- 기존 프로젝트 구조를 준수했는지?
- 테스트 커버리지가 충분한지?
- 테스트 코드가 프로덕션 코드보다 복잡하지 않은지?
- 테스트가 기능 명세 역할을 할 수 있는지?
개인적으로 가장 좋았던 부분은 테스트 코드가 단순 검증용 코드가 아니라, 기능 명세서처럼 남는다는 점이었다.
나중에 GitHub 연동 기능을 수정해야 할 때도 테스트를 보면 어떤 동작을 보장해야 하는지 빠르게 파악할 수 있다.
오늘의 결론

AI Native 개발 환경에서는 코드를 빠르게 만드는 것보다,
어떤 흐름으로 만들게 할 것인가가 더 중요해지고 있다고 느낀다.
따라서 단순한 생산성 향상에 집중하기보다는,
AI를 어떻게 통제하고 효과적으로 활용할 수 있을지에 대한 고민이
앞으로의 개발 방식에서 더 중요한 기준이 될 것이라고 생각한다.
'AI > AI-Native' 카테고리의 다른 글
| Claude Code + n8n으로 반복 업무 자동화하기 (2) | 2026.06.18 |
|---|---|
| Karpathy Skills 분석 (65줄 CLAUDE.md) (4) | 2026.05.13 |
| Spec-Driven Development 도입기 (with OpenSpec) (2) | 2026.04.10 |

