AI 코딩 어시스턴트를 개발에 활용하면서 코드 작성에 들어가는 비용은 이전보다 크게 낮아졌습니다. 요구사항을 설명하면 구현 코드를 빠르게 만들어낼 수 있고, 같은 방식으로 구현을 검증하기 위한 테스트 코드도 함께 작성할 수 있습니다.
저 역시 AI 코딩을 사용할 때 검증용 테스트를 적극적으로 활용해 왔습니다. AI가 작성한 코드를 그대로 결과물로 받아들이기보다, 요구사항을 반영한 테스트를 함께 만들고 이를 실행해 구현 결과를 다시 확인하는 방식입니다. 구현 코드뿐 아니라 테스트 코드를 작성하는 비용까지 낮아진 만큼, 테스트를 추가하는 부담도 이전보다 줄었습니다.
그러던 중 AI 개발 Workflow를 보조하는 Plugin을 사용하면서 TDD(Test-Driven Development)를 다시 살펴보게 되었습니다. 제가 사용한 Plugin은 Superpowers로, 기능 구현에 TDD를 강하게 적용하고 있었습니다. 테스트 코드 자체는 익숙했지만 TDD를 실제 개발 방식으로 적용해 본 경험은 없었습니다. 기존에는 구현 이후의 검증을 위해 테스트를 작성했다면, TDD에서는 테스트가 개발 과정의 앞쪽에 놓입니다. 이 차이를 정리하다 보니 “프론트엔드에서는 TDD를 어떻게 적용할까?”, 그리고 “AI 코딩 어시스턴트와 TDD는 왜 잘 맞을까?”라는 질문으로 관심이 이어졌습니다.
1. 테스트 코드를 작성하는 것과 TDD로 개발하는 것은 다릅니다
제가 기존에 익숙했던 테스트 작성 순서는 대체로 구현이 먼저였습니다. 요구사항을 반영해 코드를 작성한 뒤, 해당 코드가 의도한 대로 동작하는지 테스트를 추가했습니다. AI 코딩을 사용할 때도 이 기본 순서는 같았습니다.
요구사항 확인
↓
코드 구현
↓
테스트 코드 작성
↓
구현 결과 검증
TDD는 이 순서를 바꿉니다. 먼저 아직 구현되지 않은 요구사항을 테스트로 표현합니다. 테스트가 실패하는 상태가 RED입니다. 이후 현재 실패하는 테스트를 통과시키는 최소한의 코드를 작성해 GREEN을 만들고, 테스트가 계속 통과하는 상태에서 구조를 정리하는 REFACTOR를 진행합니다.
요구사항
↓
RED : 실패하는 테스트 작성
↓
GREEN : 테스트를 통과시키는 최소 구현
↓
REFACTOR : 동작을 유지하며 코드 정리
↓
다음 요구사항으로 반복
예를 들어 “주문 금액이 10,000원 이상이면 배송비가 무료다”라는 요구사항이 있다면 배송비 계산 로직보다 테스트를 먼저 작성할 수 있습니다.
@Test
void 만원_이상_주문하면_배송비는_무료이다() {
int deliveryFee = calculateDeliveryFee(10_000);
assertThat(deliveryFee).isEqualTo(0);
}
아직 calculateDeliveryFee가 없다면 테스트는 실패합니다. 이후 이 테스트를 만족시키는 최소 구현을 만들고 다음 요구사항을 다시 테스트로 추가합니다. 중요한 차이는 테스트 코드의 유무가 아니라, 테스트가 개발 프로세스의 어디에 위치하느냐입니다.
2. 테스트가 검증 도구에서 실행 가능한 요구사항으로 이동합니다
구현 이후에 작성하는 테스트에서는 이미 만들어진 코드가 기준점입니다. 반면 TDD에서는 요구사항을 먼저 테스트로 구체화하고 구현 코드가 그 테스트를 따라갑니다.
구현 후 검증 : 구현 → 테스트 → 결과 확인
TDD : 요구사항 → 테스트 → 구현 → 결과 확인
예를 들어 로그인 기능이라면 “이메일 없이 제출하면 필수 입력 메시지를 표시한다”, “로그인 요청 중에는 버튼을 비활성화한다”, “로그인에 성공하면 메인 화면으로 이동한다”와 같은 동작을 하나씩 테스트로 표현할 수 있습니다.
이렇게 작성된 테스트 목록은 기능이 만족해야 할 작은 요구사항 목록이 됩니다. 테스트가 현재 구현을 설명하는 문서가 아니라 앞으로 구현해야 할 동작을 표현하는 실행 가능한 명세가 되는 것입니다.
3. 그렇다면 프론트엔드에서는 무엇을 테스트할까요?
TDD의 흐름을 정리한 뒤 가장 먼저 든 질문은 프론트엔드였습니다. 백엔드에서는 메서드의 반환값, 예외 발생 여부, 상태 변화처럼 입력과 출력이 비교적 명확합니다. 반면 프론트엔드에서는 사용자가 화면에서 무엇을 보고 어떤 행동을 할 수 있는지가 중요한 테스트 대상이 됩니다.
처음 렌더링했을 때 무엇이 보이는가
버튼을 클릭하면 화면이 어떻게 변하는가
입력값이 잘못되었을 때 어떤 메시지가 나타나는가
요청 중 Loading 상태가 표시되는가
성공 / 빈 데이터 / 오류 상태가 올바르게 표현되는가
저에게 프론트엔드 검증은 Storybook에 더 익숙했습니다. Storybook은 컴포넌트를 애플리케이션 전체와 분리해 여러 상태를 시각적으로 확인하고 문서화하는 데 강점이 있습니다. 반면 단위·컴포넌트 테스트는 특정 행동 이후 기대한 결과가 계속 유지되는지를 자동으로 검증합니다.
Storybook
→ 컴포넌트의 여러 상태를 시각적으로 확인하고 문서화
Unit / Component Test
→ 정해진 동작이 계속 유지되는지 자동으로 검증
프론트엔드 단위 테스트를 살펴보면서 Vitest, Testing Library, user-event, jsdom 등의 도구가 서로 다른 역할을 맡는다는 점도 정리하게 되었습니다.
Vitest
└─ 테스트 파일 실행, expect, mock / spy, 성공·실패 판정
└─ Testing Library
├─ 컴포넌트 렌더링과 DOM 탐색
└─ user-event : 클릭, 입력 등 사용자 행동 재현
jsdom
└─ Node.js 테스트 환경에 가상의 DOM 제공
Vitest는 테스트를 실행하고 결과를 판정합니다. Testing Library는 React나 Vue 컴포넌트를 렌더링하고 사용자 관점에서 요소를 찾도록 돕습니다. user-event는 클릭이나 입력 같은 행동을 재현하고, jsdom은 실제 브라우저를 띄우지 않아도 DOM 기반 테스트를 실행할 수 있는 환경을 제공합니다.
특히 Testing Library는 CSS class나 내부 컴포넌트 구조보다 사용자가 인식할 수 있는 역할과 이름을 기준으로 요소를 찾는 방식을 권장합니다.
screen.getByRole('button', { name: '로그인' });
즉 “login-button이라는 class가 존재하는가”보다 “사용자가 로그인이라고 인식할 수 있는 버튼이 존재하는가”를 확인합니다. 프론트엔드에서도 내부 구현보다 사용자에게 관찰되는 동작을 중심으로 테스트할 수 있다는 의미입니다.
4. 프론트엔드에서도 Red → Green → Refactor는 같습니다
예를 들어 처음에는 0을 표시하고 증가 버튼을 누르면 값이 1 증가하는 Counter 컴포넌트를 만든다고 가정해보겠습니다. TDD라면 컴포넌트를 먼저 완성하지 않고 다음과 같은 테스트부터 작성할 수 있습니다.
it('증가 버튼을 누르면 숫자가 1 증가한다', async () => {
const user = userEvent.setup();
render(<Counter />);
expect(screen.getByText('현재 값: 0')).toBeVisible();
await user.click(screen.getByRole('button', { name: '증가' }));
expect(screen.getByText('현재 값: 1')).toBeVisible();
});
아직 증가 기능이 없다면 RED입니다. Count 상태와 클릭 동작을 추가해 테스트가 통과하면 GREEN이 되고, 이후 구조를 정리하며 REFACTOR합니다. 백엔드와 프론트엔드는 사용하는 도구와 테스트 대상이 다르지만 TDD의 기본 사이클은 같습니다.
5. 왜 AI 코딩 어시스턴트와 TDD가 잘 맞을까
앞에서 살펴본 것처럼 TDD에서는 테스트가 구현해야 할 동작을 먼저 정의합니다. 이 구조에 AI 코딩 어시스턴트가 들어오면 테스트는 명세를 넘어 작업 과정에서 사용할 수 있는 기준이 됩니다. 특히 다음 세 가지 지점에서 TDD의 흐름과 잘 맞습니다.
완료 조건을 명확하게 만들 수 있습니다
“로그인 기능을 구현해줘”라는 요청만으로는 어디까지 구현되었을 때 작업이 끝났다고 볼지 애매할 수 있습니다. 코드가 컴파일되는 것, API가 호출되는 것, 화면이 렌더링되는 것은 서로 다른 완료 조건입니다.
반면 미리 정의된 테스트가 있다면 실패는 아직 해당 동작을 만족하지 못했다는 신호이고, 성공은 적어도 테스트에 표현된 조건을 만족했다는 신호가 됩니다. AI 코딩 어시스턴트가 구현 결과를 판단할 수 있는 비교적 명확한 기준이 생기는 셈입니다.
실패 결과가 다음 행동을 위한 피드백이 됩니다
테스트의 실패는 단순히 “틀렸다”는 결과만 주는 것이 아닙니다. 기대값과 실제값의 차이, 예외가 발생한 위치와 같은 정보는 무엇을 수정해야 하는지 판단하는 단서가 됩니다.
코드 작성
↓
테스트 실행
↓
결과 관찰
↓
코드 수정
↓
다시 테스트
AI 코딩 어시스턴트는 이 결과를 바탕으로 코드를 수정하고 다시 테스트할 수 있습니다. 테스트가 구현 결과를 관찰할 수 있는 피드백 루프를 만들어주는 것입니다.
한 번에 구현하는 범위를 작게 유지할 수 있습니다
AI 코딩 어시스턴트는 짧은 시간에 많은 코드를 생성할 수 있는 만큼, 요구 범위 이상으로 구조를 확장하거나 아직 필요하지 않은 추상화를 추가할 수도 있습니다. TDD의 GREEN 단계는 현재 실패한 테스트를 통과시키는 최소 구현을 우선합니다.
작은 테스트를 하나씩 추가하면서 구현하면 “무엇을 만들 것인가”뿐 아니라 “지금 어디까지 만들 것인가”도 제한할 수 있습니다. 코드 생성 속도가 빨라진 환경에서는 이런 범위 제어가 오히려 더 중요할 수 있습니다.
구현이 끝난 뒤에는 다시 안전망으로 남습니다
TDD를 적용한다고 해서 기존에 사용하던 검증용 테스트의 의미가 사라지는 것은 아닙니다. 구현을 이끌었던 테스트는 이후 코드가 수정되거나 리팩터링될 때 기존 동작이 깨지지 않았는지 확인하는 회귀 테스트가 됩니다.
즉 제가 AI 코딩에서 사용해 오던 “구현 후 검증”과 TDD는 서로 대체되는 방식이라기보다 테스트가 개발 과정의 더 앞쪽까지 확장되는 관계에 가깝습니다. 구현 전에는 동작을 정의하고, 구현 중에는 성공과 실패를 통해 방향을 제공하며, 구현 후에는 기존 동작을 지키는 안전망으로 남습니다.
6. 마치며
다시 제목의 질문으로 돌아가 보겠습니다. AI 시대에 테스트의 역할은 완전히 새로운 것으로 바뀌었다기보다 개발 과정의 앞단까지 확장되었다고 보는 편이 더 적절해 보입니다. 기존처럼 구현 결과를 검증하는 동시에, TDD에서는 구현해야 할 동작과 완료 조건을 먼저 정의하고 실행 결과를 다음 수정의 피드백으로 사용할 수 있습니다.
AI 코딩으로 Production Code와 Test Code를 만드는 비용이 모두 낮아질수록 중요한 것은 얼마나 많은 코드를 빠르게 만들 수 있는가보다, 만들어진 코드가 우리가 의도한 동작을 만족하고 있는지를 어떻게 정의하고 계속 확인할 것인가일 수 있습니다. 그런 관점에서 테스트는 AI 코딩 시대에 중요한 개발 도구로 남아 있다고 생각합니다.
읽어주셔서 감사합니다.
Hhkk