심층 학습 가이드
AI API 계약 테스트 루프
심층 학습 가이드

AI API 계약 테스트 루프

AI가 만든 프론트엔드와 백엔드가 요청 스키마, 응답 스키마, error envelope, pagination, idempotency key, versioning 약속을 계속 지키게 만드는 실전 계약 테스트 루프

학습 유형

주제 심층 학습

핵심 주제

AI API 계약 테스트

키워드

VIBE 코딩 · API 계약 · 계약 테스트 · OpenAPI · 회귀 테스트 · AI 코드 품질

학습 개요

이 페이지에서 다루는 것

AI API 계약 테스트

한 번에 끝까지 읽으며 맥락을 쌓을 수 있도록 구성했습니다.

예상 학습 시간

14분

본문과 보조 자료(이미지·영상)를 포함한 대략적인 소요입니다.

학습 팁

섹션 순서대로 읽고, 필요한 부분만 다시 찾아보기

표·이미지·영상은 본문 흐름을 돕는 보조 설명입니다.

AI에게 화면과 서버 API를 동시에 맡기면 가장 자주 생기는 실패는 “둘 다 그럴듯하지만 서로 맞지 않는 상태”입니다. 프론트엔드는 name 필드를 기대하는데 서버는 displayName을 돌려주고, 서버는 201을 반환하는데 화면 테스트는 200만 기다리고, 오류 응답은 어떤 곳에서는 message이고 어떤 곳에서는 error.detail입니다. 작은 불일치는 로컬 데모에서는 지나가지만 배포 후에는 빈 화면, 재시도 루프, 잘못된 캐시, 깨진 알림으로 나타납니다.

초보자는 API 계약을 “서로 약속한 요청과 응답의 모양”으로 이해하면 됩니다. 실무자에게는 더 엄격합니다. 요청 스키마, 응답 스키마, status code, error envelope, pagination, idempotency key, versioning, 인증 실패 형태, 빈 결과 형태, 시간대와 숫자 단위까지 문서와 테스트로 고정해야 합니다. AI API 계약 테스트 루프는 AI가 빠르게 만든 클라이언트와 서버가 계속 같은 약속을 지키도록 만드는 작업 방식입니다.

핵심 결론

AI API 계약 테스트 루프의 핵심은 “API가 대충 맞는지 브라우저에서 눌러 본다”가 아니라 “계약을 먼저 쓰고, 그 계약이 깨지면 구현을 멈추게 한다”입니다. 기능 요구사항을 바로 코드로 바꾸기 전에 요청 스키마, 응답 스키마, 정상 status code, 오류 status code, error envelope, pagination 규칙, 하위 호환성 조건, breaking change 금지 조건을 작은 계약 문서와 contract test로 고정합니다.

이 루프가 있으면 AI 에이전트에게 일을 맡길 때 지시가 훨씬 선명해집니다. “목록 API 만들어 줘” 대신 “OpenAPI 기준으로 GET /projects는 cursor pagination을 쓰고, 응답은 items와 nextCursor를 포함하며, 오류는 code와 message를 가진 error envelope로 통일하고, 기존 클라이언트가 쓰는 id와 title 필드는 제거하지 말라”라고 말할 수 있습니다. 구현자가 사람인지 AI인지와 관계없이 계약은 리뷰 가능한 기준이 됩니다.

좋은 계약 테스트는 세 가지를 막습니다. 첫째, 프론트엔드와 백엔드가 서로 다른 이름을 쓰는 drift를 막습니다. 둘째, AI가 리팩터링 중 응답 필드를 조용히 제거하는 breaking change를 막습니다. 셋째, 장애 대응 시 어떤 응답이 정상인지 논쟁하지 않게 합니다. contract test가 실패하면 변경을 되돌리거나 versioning을 적용해야 한다는 판단이 빨라집니다.

왜 중요한가

AI는 한쪽 문맥만 보고 그럴듯한 이름을 만든다

AI는 현재 열어 둔 파일과 프롬프트에 강하게 끌립니다. 화면 컴포넌트만 보여 주면 userName 같은 필드를 만들고, 서버 핸들러만 보여 주면 데이터베이스 컬럼과 비슷한 display_name을 그대로 노출할 수 있습니다. 둘 다 나름 합리적이지만 소비자와 제공자가 같은 약속을 공유하지 않으면 실제 제품은 깨집니다.

이 문제는 초기에 잘 드러나지 않습니다. 샘플 데이터가 한두 개뿐이면 화면이 우연히 동작합니다. 그러나 데이터가 비어 있거나, 권한이 없거나, cursor가 만료되거나, 모바일 네트워크에서 재시도가 일어나면 약속의 빈틈이 드러납니다. API 계약 테스트는 이런 빈틈을 “나중에 알게 되는 버그”가 아니라 “지금 실패하는 테스트”로 바꿉니다.

계약은 팀의 언어이자 AI 작업 지시서다

VIBE 코딩에서는 사람이 모든 코드를 직접 쓰지 않아도 됩니다. 대신 사람이 정해야 할 것은 “어떤 약속이 깨지면 안 되는가”입니다. 계약은 기획자, 프론트엔드, 백엔드, QA, 운영자, AI 에이전트가 공유하는 언어입니다. 같은 문서를 보며 요청 스키마와 응답 스키마를 확인하면, 구현 세부가 달라도 사용자에게 보이는 결과는 안정됩니다.

계약이 없으면 코드 리뷰가 취향 싸움으로 흐릅니다. “이 필드명이 더 자연스럽다”, “오류 메시지는 이 정도면 된다”, “페이지네이션은 나중에 바꾸자” 같은 말이 반복됩니다. 계약이 있으면 리뷰는 “이 변경은 하위 호환성을 지키는가”, “schema diff가 breaking change를 표시하는가”, “consumer-driven contract가 실제 화면 요구를 반영하는가”로 바뀝니다.

실제 작업 순서

1단계: 소비자 화면에서 필요한 약속을 먼저 적는다

처음 할 일은 서버 구현이 아니라 소비자 목록을 만드는 것입니다. 어떤 화면, 어떤 자동화, 어떤 외부 연동이 이 API를 읽는지 적습니다. 각 소비자마다 필요한 필드, 빈 상태, 로딩 상태, 오류 상태, 재시도 조건, 정렬 조건을 적습니다. 이때 “있으면 좋은 필드”와 “없으면 화면이 깨지는 필드”를 구분해야 합니다.

예를 들어 프로젝트 목록 화면은 id, title, status, updatedAt, ownerName, nextCursor가 필요할 수 있습니다. 검색 자동완성은 id와 title만 필요할 수 있습니다. 운영 알림은 status와 updatedAt만 필요할 수 있습니다. 같은 API라도 소비자가 다르면 계약 중요도가 다릅니다. consumer-driven contract는 제공자가 마음대로 응답을 설계하는 대신 실제 소비자가 깨지지 않는 기준을 앞에 두는 방식입니다.

2단계: 요청 스키마와 응답 스키마를 작은 단위로 고정한다

다음은 요청 스키마와 응답 스키마를 명시하는 단계입니다. OpenAPI를 쓰든 타입 정의를 쓰든 핵심은 “예상 모양이 사람이 읽을 수 있고 테스트가 읽을 수 있어야 한다”는 점입니다. 요청에는 필수 필드, 선택 필드, 기본값, 길이 제한, enum, 날짜 형식, 숫자 단위가 들어갑니다. 응답에는 필수 필드, nullable 여부, 배열 정렬, pagination, error envelope가 들어갑니다.

AI에게는 이 스키마를 그대로 작업 지시서에 넣습니다. “응답 스키마 밖의 필드를 추가하지 말라”가 아니라 “추가 필드는 허용하되 기존 필드는 제거하거나 의미를 바꾸지 말라”처럼 하위 호환성 기준도 함께 줍니다. 실제 서비스에서는 확장은 허용하고 제거와 의미 변경은 막는 전략이 안전한 경우가 많습니다.

3단계: 대표 fixture와 edge fixture를 만든다

계약은 예시 데이터 없이는 쉽게 오해됩니다. fixture는 계약을 살아 있는 샘플로 보여 줍니다. 정상 항목 2개, 빈 목록, 권한 없음, validation 실패, cursor 만료, 중복 제출, 서버 제한 초과 같은 대표 상황을 만듭니다. fixture 이름은 행동을 설명해야 합니다. validProjectList, emptyProjectList, expiredCursorError, duplicateSubmissionConflict처럼 읽는 사람이 바로 상황을 알 수 있어야 합니다.

AI가 만든 fixture는 가끔 너무 예쁘고 정상적입니다. 그래서 edge fixture가 중요합니다. 제목이 긴 항목, 이모지가 들어간 항목, optional 필드가 없는 항목, timezone이 다른 항목, 0개 결과, 마지막 페이지, 같은 요청을 두 번 보냈을 때의 idempotency key 처리까지 포함합니다. 이런 fixture가 있어야 화면과 서버가 현실적인 데이터로 검증됩니다.

4단계: mock server로 화면을 먼저 잠근다

서버가 완성되기 전에 mock server로 화면을 검증합니다. mock server는 계약의 소비자 측 보호막입니다. 화면은 실제 서버가 없어도 요청 경로, query string, request body, status code, 응답 스키마를 기준으로 동작해야 합니다. AI가 화면을 수정할 때 mock server와 fixture를 함께 주면 “그럴듯한 임시 데이터”를 컴포넌트 안에 숨길 가능성이 줄어듭니다.

중요한 점은 mock server가 제품 로직을 대신하면 안 된다는 것입니다. mock server는 계약을 흉내 내는 도구이지 진짜 권한 판단이나 데이터 정합성의 근거가 아닙니다. 화면 테스트는 mock server로 빠르게 돌리고, 서버 테스트는 실제 핸들러와 저장소 경계에서 contract test를 돌리는 식으로 역할을 나눕니다.

5단계: 제공자 contract test로 실제 API를 검증한다

서버 쪽에서는 실제 핸들러가 계약을 지키는지 확인합니다. 성공 요청은 약속한 status code와 응답 스키마를 반환해야 합니다. validation 실패는 같은 error envelope를 써야 합니다. 권한 없음과 인증 없음은 서로 다른 의미라면 status code도 구분해야 합니다. pagination은 nextCursor가 있을 때와 없을 때를 모두 검증해야 합니다.

여기서 contract test는 단순한 유닛 테스트가 아닙니다. “함수가 호출됐다”보다 “외부 소비자가 받는 모양이 안정적인가”를 봅니다. 응답 JSON을 스키마에 통과시키고, 필수 필드가 빠지지 않았는지 확인하고, date string이나 enum 값이 문서와 같은지 봅니다. AI가 내부 모델 이름을 바꾸더라도 외부 계약이 유지되면 테스트는 통과해야 합니다.

6단계: schema diff를 배포 게이트로 둔다

계약이 커지면 사람이 모든 필드를 눈으로 비교하기 어렵습니다. 이때 schema diff가 필요합니다. 이전 OpenAPI 또는 타입 스냅샷과 현재 스키마를 비교해 제거된 필드, 타입 변경, enum 축소, status code 삭제, 오류 구조 변경을 표시합니다. breaking change가 나오면 기본 판단은 멈춤입니다. 정말 필요한 변경이라면 versioning을 적용하거나 소비자 마이그레이션 계획을 먼저 세워야 합니다.

AI 에이전트에게도 같은 규칙을 줍니다. “schema diff에서 breaking change가 나오면 구현을 계속하지 말고 변경 이유, 영향 소비자, 대체 경로, rollback criteria를 보고하라”라고 지시합니다. 이렇게 하면 AI가 테스트를 억지로 맞추기 위해 계약을 수정하는 일을 줄일 수 있습니다.

상황별 예시

예시 1: 목록 API의 pagination이 화면을 깨뜨리는 경우

처음에는 서버가 pagetotalPages를 반환했습니다. AI가 성능 개선을 하면서 cursor pagination으로 바꾸고 nextCursor만 남겼습니다. 서버 관점에서는 좋은 개선일 수 있지만 기존 화면은 page number를 기준으로 “다음 페이지” 버튼을 그리고 있었습니다. 계약 테스트가 없으면 배포 후 버튼이 사라지거나 무한 로딩이 생깁니다.

계약 루프에서는 먼저 소비자를 확인합니다. 기존 화면이 page number를 필요로 한다면 제거는 breaking change입니다. 해결책은 두 가지입니다. 한동안 page 정보를 유지하면서 내부 구현만 cursor로 바꾸거나, /v2 같은 versioning을 적용해 새 소비자만 cursor 계약을 쓰게 합니다. contract test는 기존 응답에 page 관련 필드가 유지되는지, 새 응답이 nextCursor를 제공하는지 각각 확인합니다.

예시 2: 오류 응답이 제각각이라 재시도가 망가지는 경우

어떤 API는 { message: "권한 없음" }을 반환하고, 어떤 API는 { error: "forbidden" }을 반환하고, 또 다른 API는 문자열만 반환한다고 해 봅시다. 화면은 어떤 오류가 재시도 가능한지 알 수 없습니다. AI가 만든 전역 오류 처리기는 특정 모양만 가정해 일부 실패를 빈 화면으로 만들 수 있습니다.

계약 루프에서는 error envelope를 하나로 정합니다. 예를 들어 모든 오류는 error.code, error.message, error.retryable, requestId를 포함해야 합니다. validation 오류라면 fieldErrors를 추가할 수 있지만 기본 envelope는 유지합니다. contract test는 400, 401, 403, 404, 409, 429, 500의 대표 응답이 같은 틀을 쓰는지 검증합니다. 이렇게 해야 재시도 버튼, 사용자 안내, 관측성 로그가 같은 기준으로 움직입니다.

예시 3: 중복 제출과 idempotency key가 없는 결제형 흐름

사용자가 생성 버튼을 두 번 누르거나 네트워크가 끊겨 재시도하면 같은 작업이 두 번 실행될 수 있습니다. 결제, 예약, 메일 발송, 크레딧 차감, 게시물 공개 같은 흐름에서는 치명적입니다. AI는 보통 “버튼 클릭 → POST → 성공 표시”만 구현하고 idempotency key를 빠뜨릴 수 있습니다.

계약 루프에서는 중복 요청의 약속을 먼저 정합니다. 클라이언트는 idempotency key를 보내고, 서버는 같은 키의 같은 요청을 같은 결과로 처리합니다. 충돌이 있으면 409와 명확한 error envelope를 반환합니다. fixture는 첫 요청 성공, 같은 키 재시도, 같은 키 다른 body 충돌을 포함해야 합니다. contract test는 중복 생성이 실제로 막히는지 확인합니다.

실수와 주의점

스키마를 만들었지만 테스트가 읽지 못하게 두는 실수

문서만 있고 테스트가 읽지 못하면 계약은 금방 낡습니다. README에 적힌 응답 예시는 구현이 바뀌어도 실패하지 않습니다. 가능한 한 OpenAPI, JSON Schema, 타입 생성, fixture 검증처럼 자동화가 읽을 수 있는 형태로 둡니다. 문서 설명은 사람이 이해하기 위한 것이고, contract test는 약속이 실제로 지켜지는지 확인하기 위한 것입니다.

스키마 도구를 과하게 도입할 필요는 없습니다. 작은 팀이라면 핵심 API 몇 개부터 시작해도 됩니다. 중요한 것은 “이 계약을 어기면 테스트가 실패한다”는 피드백입니다. 스키마가 크고 멋져도 배포 게이트와 연결되지 않으면 VIBE 코딩 루프에는 약합니다.

mock server 결과만 믿고 실제 핸들러를 검증하지 않는 실수

mock server는 화면 개발 속도를 높여 주지만 실제 서버를 보장하지 않습니다. AI가 mock fixture만 맞추고 실제 핸들러의 validation, 권한, status code를 다르게 만들 수 있습니다. 그래서 소비자 테스트와 제공자 contract test를 모두 둬야 합니다. 화면은 mock server로 계약을 기대하고, 서버는 실제 응답으로 계약을 증명합니다.

특히 오류 응답은 mock과 실제가 다르기 쉽습니다. 프레임워크 기본 오류, validation 라이브러리 오류, 인증 미들웨어 오류가 각자 다른 모양을 만들 수 있습니다. error envelope를 통일하려면 중앙 변환 계층과 테스트가 필요합니다.

모든 변경을 breaking change로 보는 실수

계약 테스트가 강하다고 해서 아무 필드도 추가하지 못하는 것은 아닙니다. 일반적으로 응답에 optional 필드를 추가하는 것은 하위 호환성이 있습니다. 반대로 필드 제거, 타입 변경, enum 축소, 의미 변경, status code 변경은 위험합니다. 테스트는 확장을 막는 도구가 아니라 소비자를 깨뜨리는 변경을 막는 도구여야 합니다.

AI에게도 이 차이를 알려야 합니다. “계약을 절대 바꾸지 마”라고 하면 필요한 확장까지 막힙니다. “하위 호환성은 유지하고, breaking change가 필요하면 versioning과 마이그레이션 계획을 제안하라”가 더 좋은 지시입니다.

테스트 fixture에 실제 민감값을 넣는 실수

fixture는 현실적이어야 하지만 실제 사용자 정보나 비밀 값을 넣으면 안 됩니다. 이름, 이메일, 주문 번호, 토큰처럼 보이는 값은 가짜여야 합니다. 로그와 스냅샷에도 같은 원칙을 적용합니다. contract test가 실패했을 때 출력되는 diff는 협업 도구와 CI 화면에 보일 수 있으므로 안전한 샘플만 사용합니다.

검증 체크리스트

작업 시작 전 체크

  • 이 API를 소비하는 화면, 자동화, 외부 연동 목록을 적었는가?
  • 요청 스키마와 응답 스키마가 문서와 테스트에서 같은 기준으로 쓰이는가?
  • status code와 error envelope가 정상, 검증 실패, 권한 실패, 충돌, 제한 초과, 서버 실패를 구분하는가?
  • pagination, 정렬, 필터, 날짜 형식, 숫자 단위, nullable 필드가 명시되어 있는가?
  • idempotency key가 필요한 쓰기 작업인지 판단했는가?

구현 중 체크

  • mock server fixture가 정상, 빈 결과, 오류, 경계값을 포함하는가?
  • 제공자 contract test가 실제 핸들러 응답을 검증하는가?
  • schema diff가 제거된 필드, 타입 변경, enum 축소, status code 삭제를 표시하는가?
  • AI가 생성한 타입과 실제 런타임 validation이 서로 다른 약속을 만들지 않았는가?
  • 하위 호환성을 깨는 변경이 있으면 versioning 또는 단계적 마이그레이션 계획이 있는가?

배포 전 체크

  • contract test와 화면 회귀 테스트가 모두 통과했는가?
  • 대표 fixture로 목록, 상세, 생성, 수정, 삭제, 오류 표시를 확인했는가?
  • breaking change가 있다면 영향 소비자와 롤백 판단이 문서화되었는가?
  • rollback criteria가 숫자로 정리되어 있는가? 예를 들어 오류율, 4xx/5xx 증가, 재시도율, 빈 화면 보고, 특정 status code 급증을 본다.
  • 배포 후 관측성에서 contract mismatch를 발견할 수 있는 로그와 알림이 있는가?

운영 지표와 계약 실패를 연결한다

계약 테스트는 배포 전 품질 장치이고, 운영 지표는 배포 후 안전 장치입니다. API 계약이 깨졌을 때 실제 사용자에게 보이는 신호를 미리 정해야 합니다. 예를 들어 특정 status code가 갑자기 늘거나, error envelope를 파싱하지 못한 클라이언트 오류가 증가하거나, pagination 마지막 페이지에서 빈 화면 비율이 늘면 계약 mismatch를 의심할 수 있습니다. 로그에는 원문 본문을 남기지 않아도 endpoint, status code, error code, schema validation 실패 종류, 요청 크기 범위, 처리 시간, 소비자 버전 정도는 남길 수 있습니다.

AI에게 운영 지표까지 맡길 때는 “성공 로그를 많이 남겨 줘”가 아니라 “계약 실패를 발견하는 최소 지표를 남겨 줘”라고 지시해야 합니다. 어떤 소비자 버전에서 어떤 contract test가 깨졌는지 추적할 수 있으면, 배포 후 rollback criteria가 훨씬 선명해집니다. 오류율이 몇 분 동안 기준을 넘는지, 특정 error code가 평소보다 몇 배 늘었는지, 재시도율이 어느 수준을 넘으면 kill switch를 켤지 정해 두면 계약 루프가 운영 루프와 연결됩니다. ## 다음 단계

작은 API 하나부터 계약 루프를 적용한다

처음부터 모든 API를 완벽하게 정리하려고 하면 실패합니다. 가장 자주 깨지는 API 하나를 고르세요. 보통 목록 API, 생성 API, 인증이 필요한 상세 API, 결제나 예약처럼 중복 실행이 위험한 API가 좋습니다. 그 API에 요청 스키마, 응답 스키마, error envelope, fixture, consumer-driven contract, 제공자 contract test, schema diff를 작게 붙입니다.

성공 기준은 단순합니다. AI가 해당 API를 수정했을 때 필드 제거, status code 변경, 오류 모양 변경, pagination 변경이 테스트 실패로 드러나야 합니다. 한 번 이 루프가 자리를 잡으면 다음 API는 훨씬 빠르게 확장할 수 있습니다.

AI 작업 지시서에 계약 섹션을 고정한다

VIBE 코딩 프롬프트에는 기능 설명만 넣지 말고 계약 섹션을 넣습니다. “소비자”, “요청 스키마”, “응답 스키마”, “오류 envelope”, “하위 호환성”, “contract test”, “schema diff”, “rollback criteria”를 항상 채웁니다. 비어 있는 항목이 있으면 AI에게 먼저 질문하게 하거나 안전한 기본값을 제안하게 합니다.

이 습관은 팀 규모가 커질수록 효과가 큽니다. 사람은 제품 의도를 정하고, AI는 구현 후보를 만들고, contract test는 약속 위반을 즉시 잡습니다. 그렇게 하면 빠른 제작 속도를 유지하면서도 사용자에게 보이는 API 품질을 안정적으로 가져갈 수 있습니다.

다음 학습

같은 섹션에서 이어 읽기 좋은 콘텐츠

윤슬 코드·AI 보안 설계·2026.04.29·14분 읽기

AI 위협 모델링 루프

AI에게 기능을 맡길 때 가장 위험한 순간은 코드가 빨리 완성됐다고 느끼는 직후입니다. 로그인 화면이 열리고, 업로드 버튼이 동작하고, 관리자용 목록이 보이면 우리는 “기능은 됐다”고 판단하기 쉽습니다. 하지만 공격자는 정상 사용자처럼만 움직이지 않습니다. 잘못된 권한으로 접근하고, 입력값을 비틀고, 자동화로 반복 요청을 보내고, AI 기능에는 프롬프트 인젝션을 넣고, 로그와 알림에 민감한 값이 남는지 확인합니다. VIBE 코딩에서 보안은 마지막 점검표가 아니라 AI 작업 지시서에 들어가야 하는 설계 조건입니다.

초보자는 위협 모델링을 “나쁜 일이 생길 수 있는 길을 미리 그려 보는 연습”으로 이해하면 됩니다. 실무자에게는 더 구체적입니다. 어떤 자산을 보호해야 하는지, 데이터가 어느 신뢰 경계를 넘는지, STRIDE 관점에서 스푸핑·변조·부…

#VIBE 코딩#위협 모델링#보안 설계
요약맥락
윤슬 코드·AI 테스트 데이터 운영·2026.04.28·13분 읽기

AI 테스트 데이터 시드

AI에게 기능 구현을 맡기면 화면과 API는 빠르게 생깁니다. 하지만 테스트 데이터가 매번 즉흥적으로 만들어지면 같은 버그를 두 번 확인할 수 없습니다. 오늘은 "김철수 한 명", 내일은 "테스트 유저 여러 명", 모레는 운영 데이터 일부를 복사한 샘플로 검증하면, 실패가 코드 문제인지 데이터 문제인지 판단하기 어려워집니다. VIBE 코딩에서 테스트 데이터는 부록이 아니라 AI가 만든 변경을 믿을 수 있게 만드는 실행 기반입니다.

초보자는 시드 데이터를 "테스트를 시작할 때 미리 깔아 두는 연습용 데이터"라고 이해하면 됩니다. 실무자에게는 더 구체적입니다. 고정 난수로 같은 데이터를 다시 만들고, 개인정보는 익명화하고, fixture와 팩토리로 케이스를 재사용하며, 권한 조합·경계값·상태 전이를 데이터 계약으로 고정해야 합니다. 그래야 AI…

#VIBE 코딩#테스트 데이터#시드 데이터
요약맥락