2026년 7월 14일 | 바이브코딩 Tips
JSON 한 층이 빠진 mock
주문 상태를 읽는 코드가 실제 서비스에서만 KeyError를 냈다. 코딩 에이전트가 고친 파서는 response["status"]를 사용했고, 관련 테스트는 전부 통과했다. 그런데 실제 API는 상태값을 바로 주지 않고 data.status 아래에 넣어 보냈다. 테스트의 mock 응답에서 data 한 층이 빠져 있었던 것이다.
처음에는 에이전트가 JSON 접근 코드를 잘못 썼다고 생각했다. diff만 보면 그렇게 보였다. 하지만 더 정확히 말하면 에이전트는 테스트가 보여 준 계약을 충실하게 구현했다. 문제는 그 계약이 실제 경계와 달랐다는 데 있었다. mock 테스트의 초록색은 코드와 mock이 서로 맞는다는 뜻이지, 코드와 외부 API가 맞는다는 뜻은 아니다.
mock은 편한 예제가 아니라 경계 기록이다
mock을 만들 때 필요한 필드만 짧게 적으면 테스트가 읽기 쉬워진다. 나도 예전에는 그렇게 줄이는 편을 좋아했다. 다만 응답의 중첩 위치, 배열 여부, null 가능성, 숫자와 문자열의 타입까지 지우면 단순화가 아니라 다른 API를 만든 셈이 된다. 특히 에이전트는 저장소 안에서 가장 선명한 증거를 따라가므로, 오래된 mock이 실제 문서보다 더 강한 명세처럼 작동한다.
그래서 mock을 볼 때 값의 내용보다 모양을 어디서 가져왔는지 먼저 확인한다. 공식 스키마에서 만든 것인지, 실제 응답을 익명화한 것인지, 사람이 구현에 맞춰 손으로 쓴 것인지가 중요하다. 마지막 경우라면 테스트가 구현을 검증하는 대신 구현을 되풀이할 가능성이 크다. 파서가 잘못된 키를 읽고 mock도 같은 키를 제공하면 둘은 함께 틀린 채 통과한다.
실제 응답 샘플은 작게, 구조는 그대로
외부 API 응답 전체를 fixture로 저장할 필요는 없다. 개인정보, 토큰, 긴 설명 필드는 빼도 된다. 대신 파서가 의존하는 부모 키와 자료형은 유지한다. 주문 예제라면 data 객체, status 문자열, 선택 필드의 null, 목록이 비었을 때의 모양 정도를 남긴다. 값은 가짜여도 괜찮지만 구조까지 가짜가 되면 안 된다.
{
"data": {
"status": "paid",
"updated_at": null
}
}
나는 이런 fixture 옆에 캡처 시점과 API 버전도 짧게 적는다. 응답이 바뀌었을 때 “테스트가 낡았나, 구현이 틀렸나”를 바로 나누기 위해서다. 실제 응답을 저장할 수 없는 환경이라면 공식 OpenAPI schema나 공급자 문서의 예제를 기준으로 삼고, 어느 출처를 썼는지 남긴다. fixture의 출처가 없으면 다음 에이전트는 현재 구현을 기준으로 fixture를 다시 고칠 수 있다.
성공 응답 하나만 남기는 것도 부족했다. 같은 endpoint가 200에서는 data 객체를 주고, 404에서는 error 객체를 주며, 204에서는 body 자체가 없을 수 있다. mock이 언제나 JSON 객체를 돌려주면 파서는 빈 body나 HTML 오류 페이지를 만났을 때 엉뚱한 JSON 예외를 낸다. 그래서 정상, 비어 있음, 구조화된 오류에서 각각 하나씩 모양을 고정한다.
HTTP status와 Content-Type도 fixture 바깥의 장식으로 넘기지 않는다. 본문이 같아도 200과 202는 처리 시점이 다르고, JSON처럼 보이는 문자열도 헤더가 다르면 클라이언트 동작이 달라질 수 있다. 민감한 값은 지우되 status, header, body shape의 조합은 보존해야 실제 경계에 가까운 테스트가 된다.
단위 테스트와 계약 확인을 같은 PASS로 묶지 않는다
모든 테스트가 실제 API를 호출하게 만들면 느리고 불안정하며 비용도 생긴다. 그렇다고 mock만 남기면 경계가 떠다닌다. 나는 두 층을 나눈다. 빠른 단위 테스트에서는 고정 fixture로 파서의 분기와 예외 처리를 확인하고, 별도의 계약 확인에서는 실제 API나 공식 schema와 fixture의 모양이 여전히 맞는지 본다.
계약 확인은 매 테스트마다 돌릴 필요가 없다. 릴리스 전, API client 변경 시, 스키마 버전 갱신 시처럼 경계가 움직이는 시점에 한 번이면 충분하다. 중요한 건 완료 보고에서 mock 기반 PASS와 실제 경계 확인을 다른 줄로 받는 것이다. “테스트 24개 통과”만 있으면 그 24개가 같은 잘못된 fixture를 공유하는지 알 수 없다.
검증 결과도 단순 성공 여부보다 비교 대상을 남긴다. 예를 들어 “fixture의 필수 경로 6개가 schema에 존재, 추가 필드 2개는 파서가 무시, nullable 차이 1개 발견”처럼 적으면 무엇이 맞았는지 다시 볼 수 있다. 반대로 schema 파일을 갱신한 뒤 테스트도 같이 통과했다는 말만 있으면 기준과 구현이 동시에 움직였는지 판정하기 어렵다. 나는 계약 파일이 바뀐 날에는 이전 버전과의 구조 차이를 별도 줄로 받는다.
실제 호출이 막혀도 비교할 것은 남는다
개발 환경에서 운영 API를 부를 권한이 없거나 호출 자체가 위험할 때도 있다. 이때 에이전트에게 임의의 응답을 상상하게 하면 안 된다. 공급자가 제공한 schema, 저장소에 이미 들어 있는 익명화 응답, API gateway의 검증 로그처럼 현재 접근 가능한 근거를 먼저 찾는다. 근거가 하나도 없으면 “실제 응답 미확인”을 완료 문장 안에 숨기지 않고 별도 상태로 남긴다.
이 표시는 실패 선언이 아니다. 검증 범위를 정확히 말하는 것이다. mock 기준 파서 수정은 끝났지만 실제 API 호환성은 미확인이라고 적으면 배포 전 다음 행동이 선명해진다. 반대로 권한이 없는데도 “API 연동 완료”라고 닫으면 다음 사람은 장애가 난 뒤에야 계약 검증이 없었다는 사실을 알게 된다. 확인하지 못한 경계를 적는 것이 근거 없는 완성도를 높이는 것보다 낫다.
완료 기준은 응답 모양의 출처까지
이제 코딩 에이전트가 API 파서를 고쳤다고 하면 세 가지를 본다. 어떤 응답 모양을 기준으로 수정했는지, 그 fixture가 실제 응답이나 공식 schema와 언제 맞춰졌는지, mock 테스트 밖에서 한 번이라도 경계를 확인했는지다. 세 칸이 채워지면 작은 파서 diff도 믿을 만해진다.
mock은 외부 시스템을 떼어 내 빠르게 테스트하려는 도구이지, 외부 시스템을 대신 상상하는 도구는 아니다. 테스트가 통과했는데 실제 호출에서만 깨졌다면 에이전트의 코드부터 다시 쓰기 전에 fixture의 계보를 본다. 내가 원하는 완료 보고도 “모든 테스트 PASS” 한 줄이 아니라 fixture 출처, 단위 테스트 결과, 실제 경계 확인 세 줄이다. 이 정도면 초록색 로그가 어디까지 유효한지 다음 세션에서도 다시 판단할 수 있다.
'[개발 깨알 상식_Tips] > [바이브코딩 Tips]' 카테고리의 다른 글
| 이전 서버를 검사한 API 테스트의 가짜 PASS (0) | 2026.07.16 |
|---|---|
| 잘린 검색 결과를 전체 목록으로 믿은 코딩 에이전트 (0) | 2026.07.15 |
| 기본값 패치를 무력화한 호출부의 명시적 인자 (0) | 2026.07.13 |
| 코딩 에이전트가 넓힌 .gitignore 규칙 (0) | 2026.07.12 |
| 빌드 뒤 사라진 코딩 에이전트 수정 (0) | 2026.07.10 |