AI 코딩 어시스턴트와 TDD: 실패하는 테스트부터 확인하는 node:test 실습

테스트 목록 → 실패 확인 → 구현 → 경계값 수정까지 실제 실행 결과로 따라가기

AI 코딩 도구로 TDD 하기 썸네일

핵심 요약

  1. AI에 구현과 테스트를 한 번에 맡기면 테스트가 구현의 실수를 그대로 따라갈 수 있다.
  2. 테스트 목록을 표로 먼저 받고, 사람이 위험 경로를 골라 테스트로 만든 뒤 구현 전 실패를 확인한다.
  3. 경계값 테스트가 "이상/초과"를 잘못 옮긴 구현 버그를 잡았다.
  4. 기대값은 계산식이 아니라 요구사항에서 나온 상수로 적는다.

AI 코딩 어시스턴트에 "이 기능 구현하고 테스트도 붙여 줘"라고 하면 구현과 테스트가 함께 나온다. 이때 테스트는 구현을 보고 쓰였기 때문에 구현의 실수까지 그대로 따라가는 경우가 많다. TDD의 순서, 즉 테스트를 먼저 쓰고 실패를 확인한 뒤 구현하는 흐름은 AI를 쓸 때 오히려 더 중요하다. 이 글은 배송비 계산 함수를 예로 그 흐름을 Node.js 내장 node:test로 실습한다. 아래 출력은 2026-10-07에 Node.js 24.15로 실행한 결과다.

실습 순서

  1. 요구사항을 AI에 주고 테스트 케이스 목록을 표로 먼저 받는다. 코드는 아직 받지 않는다.
  2. 목록에서 위험 경로(경계값, 지역 할증, 잘못된 입력)를 사람이 고르고, 선택한 케이스만 테스트 코드로 만든다.
  3. 구현 없이 테스트를 실행해 모두 실패하는지 확인한다.
  4. 테스트 파일을 컨텍스트로 주고 구현을 요청한다. 테스트 파일은 수정하지 말라고 명시한다.
  5. 실패한 테스트가 있으면 구현을 고친다. 테스트를 고쳐 통과시키지 않는다.

1단계: 테스트 목록을 표로 받기

요구사항은 "주문 금액 5만 원 이상 무료, 미만은 3,000원, 제주·도서산간은 3,000원 추가, 20kg 초과는 5,000원 추가, 음수 금액은 오류"다. AI에 받은 후보 중 아래 다섯 가지를 골랐다.

케이스입력기대 결과고른 이유
기본 요금49,000원, 서울, 1kg3,000원가장 흔한 경로
무료 경계50,000원, 서울, 1kg0원"이상"의 경계값
지역 할증80,000원, 제주, 1kg3,000원무료와 할증이 겹침
무게 할증10,000원, 서울, 21kg8,000원할증 누적
잘못된 입력-1원RangeError방어 코드

2단계: 테스트를 먼저 쓰고 실패 확인

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { shippingFee } from './shipping.mjs';

test('5만 원 미만은 기본 배송비 3,000원', () => {
  assert.equal(shippingFee({ subtotal: 49_000, region: 'seoul', weightKg: 1 }), 3000);
});

test('5만 원 이상이면 무료 (경계값 포함)', () => {
  assert.equal(shippingFee({ subtotal: 50_000, region: 'seoul', weightKg: 1 }), 0);
});

test('제주·도서산간은 무료 배송이어도 추가 3,000원', () => {
  assert.equal(shippingFee({ subtotal: 80_000, region: 'jeju', weightKg: 1 }), 3000);
});

test('20kg 초과는 5,000원 추가', () => {
  assert.equal(shippingFee({ subtotal: 10_000, region: 'seoul', weightKg: 21 }), 8000);
});

test('음수 금액은 오류', () => {
  assert.throws(() => shippingFee({ subtotal: -1, region: 'seoul', weightKg: 1 }), RangeError);
});

구현 파일에는 throw new Error('not implemented')만 두고 실행한다. node --test는 *.test.mjs 같은 기본 패턴의 파일을 찾아 실행한다.

$ node --test --test-reporter=spec
✖ 5만 원 미만은 기본 배송비 3,000원 (0.5927ms)
✖ 5만 원 이상이면 무료 (경계값 포함) (0.0981ms)
✖ 제주·도서산간은 무료 배송이어도 추가 3,000원 (0.0629ms)
✖ 20kg 초과는 5,000원 추가 (0.0592ms)
✖ 음수 금액은 오류 (0.618ms)
ℹ tests 5
ℹ pass 0
ℹ fail 5

다섯 개가 모두 실패해야 정상이다. 구현 전에 이미 통과하는 테스트가 있다면 그 테스트는 새 요구사항을 검증하지 못한다는 뜻이다.

3단계: 구현과 경계값 버그

첫 구현은 다음과 같았다. 금액 조건을 >로 썼다.

const REMOTE = new Set(['jeju', 'island']);

export function shippingFee({ subtotal, region, weightKg }) {
  if (subtotal < 0) throw new RangeError('subtotal must be >= 0');
  let fee = subtotal > 50_000 ? 0 : 3000;
  if (REMOTE.has(region)) fee += 3000;
  if (weightKg > 20) fee += 5000;
  return fee;
}
$ node --test --test-reporter=spec
✔ 5만 원 미만은 기본 배송비 3,000원 (0.5652ms)
✖ 5만 원 이상이면 무료 (경계값 포함) (0.6804ms)
✔ 제주·도서산간은 무료 배송이어도 추가 3,000원 (0.0847ms)
✔ 20kg 초과는 5,000원 추가 (0.6034ms)
✔ 음수 금액은 오류 (0.3556ms)
ℹ tests 5
ℹ pass 4
ℹ fail 1
  3000 !== 0
    actual: 3000,
    expected: 0,

"5만 원 이상"을 >로 옮긴 경계값 실수를 테스트가 잡았다. >=로 고치면 모두 통과한다.

$ node --test --test-reporter=spec
✔ 5만 원 미만은 기본 배송비 3,000원 (0.6006ms)
✔ 5만 원 이상이면 무료 (경계값 포함) (0.0922ms)
✔ 제주·도서산간은 무료 배송이어도 추가 3,000원 (0.0671ms)
✔ 20kg 초과는 5,000원 추가 (0.0593ms)
✔ 음수 금액은 오류 (0.3597ms)
ℹ tests 5
ℹ pass 5
ℹ fail 0

구현을 따라 쓴 테스트는 같은 버그를 놓친다

구현과 테스트를 한 번에 생성하면 아래처럼 기대값을 구현과 같은 식으로 계산하는 테스트가 나오기 쉽다. > 버그가 있는 구현에 이 테스트를 돌리면 통과한다.

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { shippingFee } from './shipping.mjs';

// 약한 테스트: 기대값을 구현과 같은 식으로 계산한다.
test('5만 원 경계 (구현을 따라 쓴 테스트)', () => {
  const subtotal = 50_000;
  const expected = subtotal > 50_000 ? 0 : 3000;
  assert.equal(shippingFee({ subtotal, region: 'seoul', weightKg: 1 }), expected);
});
$ node --test --test-reporter=spec   # > 버그가 있는 구현
✔ 5만 원 경계 (구현을 따라 쓴 테스트) (0.5356ms)
ℹ pass 1
ℹ fail 0

기대값은 요구사항에서 나온 상수(0원)로 적어야 한다. AI가 만든 테스트를 리뷰할 때는 기대값 자리에 계산식이 있는지부터 본다.

AI에 테스트를 맡길 때 요청 문장

요구사항: (위 배송비 규칙)
1. 구현 코드는 쓰지 말고, 테스트 케이스 후보를 표로 만들어라.
   열: 케이스, 입력, 기대 결과(상수), 위험도
2. 경계값과 규칙이 겹치는 경우를 반드시 포함하라.
3. 기대 결과는 계산식이 아니라 구체적인 값으로 적어라.

테스트 파일이 확정된 뒤 구현을 요청할 때는 "shipping.test.mjs를 수정하지 말 것"을 함께 적는다. 에이전트가 실패한 테스트를 고쳐서 통과시키는 일을 막기 위해서다. 테스트 작성 전반의 흐름은 AI 생성 코드 테스트 워크플로우, 타입으로 걸러낼 수 있는 오류는 TypeScript와 AI 코딩 품질에서 다룬다.

초보자가 자주 실수하는 포인트

  • 구현 전에 이미 통과하는 테스트를 추가하고 새 요구사항을 검증했다고 여긴다.
  • 기대값을 구현과 같은 식으로 계산하는 테스트를 그대로 받아들인다.
  • 실패한 테스트를 에이전트가 고쳐 통과시키게 둔다.
  • 경계값 없이 대표 입력만 테스트한다.

체크리스트

  • 테스트 케이스 목록을 코드보다 먼저 검토했다.
  • 구현 전에 모든 새 테스트가 실패하는 것을 확인했다.
  • 경계값과 규칙이 겹치는 케이스가 있다.
  • 기대값이 상수로 적혀 있다.
  • 구현 요청에 "테스트 파일 수정 금지"를 적었다.

자주 묻는 질문

node:test 대신 Jest나 Vitest를 써도 되나요?

네. 순서와 원칙은 같습니다. 이 글은 설치 없이 바로 실행할 수 있도록 Node.js 내장 테스트 러너를 썼고, 프로젝트가 이미 쓰는 프레임워크가 있다면 그 스타일을 AI에 먼저 읽히는 것이 좋습니다.

테스트 목록을 받는 단계가 시간이 더 들지 않나요?

목록 검토는 몇 분이면 끝나고, 필요 없는 테스트가 코드로 만들어지는 것을 줄입니다. 이 실습처럼 경계값 하나가 버그를 잡으면 리뷰와 수정 시간이 더 크게 줄어듭니다.

참고 자료 · 검증 기준

위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.

이 글은 실제 실무 환경 테스트 및 검증된 기술 자료를 바탕으로 작성되었으며, 환경 변화에 따라 지속적으로 갱신됩니다.