Summary#
외부(third-party) API 연동의 계약 테스트는 단일한 “mock 테스트”가 아니라, 기록된 HTTP fixture, 소비자 관점의 계약 검증, sandbox/live 환경 차이 감시, payload drift 탐지, 부분 장애·타임아웃·재시도 의미론 검증을 계층화한 구조로 설계하는 것이 안전하다.
핵심은 다음과 같다.
- 로컬 테스트는 recorded fixture로 빠르고 결정적으로 실행한다.
- fixture는 스냅샷이 아니라 계약 산출물로 관리한다.
- sandbox는 live의 완전한 대체물이 아니므로 parity probe가 필요하다.
- payload drift는 schema diff, unknown-field 관찰, enum 확장 감지로 잡는다.
- 부분 장애는 성공/실패의 이분법이 아니라 degraded response, retryable failure, non-retryable failure, idempotency risk로 분해해 테스트한다.
이 아키텍처는 결제, 배송, CRM, 인증, 이메일, LLM API, 데이터 공급자 API처럼 외부 서비스 의존도가 높은 시스템에서 재사용 가능하다.
Key Points#
1. 테스트 계층을 분리한다#
권장 구조는 다음 5계층이다.
| 계층 | 목적 | 실행 빈도 | 외부 네트워크 |
|---|---|---|---|
| Unit-level adapter tests | API client의 요청 생성, 응답 파싱, 에러 매핑 검증 | 매우 자주 | 없음 |
| Recorded fixture tests | 실제 HTTP 상호작용을 기록한 cassette/fixture로 회귀 테스트 | PR마다 | 없음 |
| Consumer contract tests | 우리 서비스가 실제로 의존하는 request/response shape 검증 | PR/merge마다 | 보통 없음 또는 broker 사용 |
| Sandbox parity probes | sandbox와 live의 동작 차이 감시 | 주기적 | 있음 |
| Live smoke/canary tests | 최소 범위의 실제 API 호출로 운영 계약 확인 | 배포 전/후, 주기적 | 있음 |
이 분리는 “빠른 테스트”와 “진짜 API 확인”을 혼동하지 않게 해준다. 모든 테스트를 live API에 의존시키면 느리고 불안정하며 비용·rate limit 문제가 생긴다. 반대로 fixture만 믿으면 provider의 payload evolution, deprecation, sandbox/live mismatch를 놓칠 수 있다.
2. Recorded fixture는 단순 mock이 아니라 감사 가능한 계약 증거로 취급한다#
VCR류 도구나 HTTP recording 도구를 사용할 때 fixture에는 다음 메타데이터를 함께 저장하는 것이 좋다.
- provider 이름
- endpoint
- HTTP method
- API version
- auth scope 또는 permission class
- record time
- sandbox/live 여부
- request redaction 여부
- response status
- provider request id, correlation id가 있다면 redacted 형태
- schema fingerprint 또는 canonicalized payload hash
- fixture refresh policy
예시 fixture 메타데이터:
provider: example-payments
environment: sandbox
endpoint: /v1/charges
method: POST
api_version: 2024-01-01
recorded_at: 2026-09-03
auth_scope: charges:write
status: 200
redaction:
- Authorization
- card.number
- customer.email
schema_fingerprint: sha256:...
refresh_policy: quarterly-or-on-provider-changelog
중요한 점은 fixture를 “영원히 고정된 정답”으로 두지 않는 것이다. 외부 API는 additive field, enum value, nullable change, error format change, rate-limit header change처럼 작은 방식으로 계속 진화한다.
3. Fixture replay 모드는 strict와 tolerant를 나눈다#
외부 API payload는 시간이 지나며 변할 수 있으므로 테스트 목적에 따라 검증 강도를 분리한다.
Strict mode
다음에 적합하다.
- 우리가 보내는 request body가 정확해야 하는 경우
- signature, idempotency key, pagination token, query parameter 검증
- provider의 특정 에러 코드를 반드시 매핑해야 하는 경우
- breaking change를 조기에 감지해야 하는 핵심 필드
검증 예:
- 필수 필드 누락 시 실패
- 타입 변경 시 실패
- enum 값이 명시 목록 밖이면 경고 또는 실패
- HTTP status와 error code 매핑 불일치 시 실패
Tolerant mode
다음에 적합하다.
- provider가 additive field를 추가할 수 있는 경우
- 응답 객체 전체가 아니라 일부 필드만 소비하는 경우
- 확장 가능한 metadata 구조
- 문서상 forward-compatible하게 설계된 API
검증 예:
- unknown field는 허용하되 기록
- 필수 소비 필드의 타입과 존재만 검증
- enum unknown value는
UNKNOWN또는 fallback path로 매핑 - nullable field는 명시적으로 처리
4. Sandbox/live parity는 별도 테스트 주제로 다룬다#
많은 외부 API의 sandbox는 live와 완전히 같지 않다. 차이는 다음 영역에서 발생할 수 있다.
- validation rule 차이
- rate limit 차이
- error code 차이
- webhook delivery behavior 차이
- async processing delay 차이
- pagination order 차이
- fraud/risk decision 로직 부재
- 실제 결제망, 배송망, identity provider 등 downstream 미연결
- edge case payload가 sandbox에서 재현되지 않음
따라서 sandbox 테스트를 “운영 검증 완료”로 간주하면 위험하다.
권장 방식:
-
sandbox regression suite - 기능 흐름 대부분을 sandbox에서 실행한다. - 비용과 위험이 낮다.
-
live read-only probes - 가능한 경우 live에서 읽기 전용 또는 무해한 endpoint만 호출한다. - 예: account info, balance read, metadata fetch, capability check.
-
live minimal write canary - 비용·위험이 허용되는 경우 최소 금액, test account, reversible operation으로 수행한다. - 반드시 idempotency key와 cleanup 전략을 둔다.
-
parity matrix - sandbox와 live의 차이를 문서화한다. - 테스트 실패가 실제 장애인지 sandbox limitation인지 구분한다.
예시 parity matrix:
| Behavior | Sandbox | Live | Test Strategy |
|---|---|---|---|
| Auth failure format | 재현 가능 | 재현 가능 | contract fixture |
| Rate limit | 약하거나 없음 | 실제 적용 | synthetic live probe 필요 |
| Webhook retry | 단순화됨 | 실제 backoff 적용 | provider 문서 + controlled test |
| Fraud decision | 미재현 | 실제 적용 | live canary 또는 manual validation |
| Pagination order | 유사 | 실제 데이터 의존 | tolerant assertion |
5. Payload drift detection을 자동화한다#
Payload drift는 provider가 명시적 breaking change를 하지 않았더라도 발생한다. 대표 사례:
- 새 필드 추가
- 기존 필드 nullable화
- 숫자/문자열 타입 변경
- enum 값 추가
- error payload 구조 변경
- pagination cursor 포맷 변경
- timestamp precision 변경
- rate-limit header 추가/삭제
- webhook event payload shape 변경
탐지 방법:
A. Canonical schema snapshot
응답 payload에서 값은 제거하고 shape만 보관한다.
{
"id": "string",
"status": "enum:pending|succeeded|failed",
"amount": "number",
"created_at": "datetime",
"metadata": "object"
}
주기적으로 live 또는 sandbox에서 얻은 payload schema와 fixture schema를 비교한다.
B. 소비 필드 기반 contract
전체 payload가 아니라 우리 코드가 실제로 읽는 필드를 명시한다.
consumed_fields:
- id
- status
- amount
- currency
- failure_code
이 필드들에 대해서는 강하게 검증하고, 나머지는 관찰·기록한다.
C. Unknown field telemetry
운영에서 역직렬화 시 unknown field를 무시하더라도, 샘플링된 로그나 metric으로 “새 필드 등장”을 감지한다.
예:
third_party.payload.unknown_field.countprovider=paymentsendpoint=/v1/chargesfield=risk_score
D. Enum expansion guard
외부 API enum은 확장될 수 있다. unknown enum을 fatal error로 만들면 provider의 additive change에도 장애가 날 수 있다.
권장 방식:
switch status:
case "succeeded": handleSuccess()
case "failed": handleFailure()
case "pending": handlePending()
default:
recordUnknownEnum(status)
handleConservativeFallback()
6. Partial-outage semantics를 명시적으로 테스트한다#
외부 API 장애는 단순히 “down”이 아니다. 다음과 같은 부분 장애가 많다.
- 특정 endpoint만 timeout
- write는 성공했지만 response가 timeout
- read는 가능하지만 write가 실패
- 일부 region만 장애
- 429 rate limit 증가
- 5xx intermittent error
- webhook delivery 지연
- provider 내부 downstream 실패
- 일시적으로 stale data 반환
- idempotency key 재사용 시 다른 응답
- partial success response 반환
따라서 테스트도 장애 의미론을 분해해야 한다.
테스트해야 할 failure class
| Failure Class | 예 | 기대 동작 |
|---|---|---|
| Connect timeout | provider에 연결 불가 | retry with backoff, circuit breaker |
| Read timeout after write | 요청은 처리됐을 수도 있음 | idempotency key로 재조회 또는 안전한 retry |
| 429 rate limit | provider throttling | retry-after 존중, queue/degrade |
| 500/502/503 | provider transient failure | 제한된 retry, fallback |
| 400 validation | 우리 요청 오류 | retry 금지, alert 또는 bug ticket |
| 401/403 auth | credential/scope 문제 | retry 금지, secret rotation 또는 incident |
| Partial success | batch 일부 실패 | item-level reconciliation |
| Webhook delayed | async event 지연 | polling fallback 또는 pending state 유지 |
| Stale read | eventual consistency | read-after-write delay 허용 |
7. Retry 테스트는 idempotency와 함께 설계한다#
외부 write API에서 retry는 위험하다. timeout이 발생했을 때 provider가 요청을 처리했는지 알 수 없기 때문이다.
권장 원칙:
- POST/PUT write 요청에는 가능하면 idempotency key를 사용한다.
- retry 가능한 error와 retry 금지 error를 명확히 분류한다.
- exponential backoff와 jitter를 사용한다.
- provider의
Retry-Afterheader가 있으면 존중한다. - retry budget을 둔다.
- 최종 실패 후 reconciliation job이 상태를 맞춘다.
테스트 fixture에는 다음 케이스가 필요하다.
cases:
- name: connect_timeout_before_send
expected: safe_retry
- name: read_timeout_after_write
expected: retry_with_idempotency_key_or_reconcile
- name: 429_with_retry_after
expected: respect_retry_after
- name: 400_validation_error
expected: no_retry
- name: 503_transient
expected: bounded_retry_with_jitter
8. Webhook은 별도 계약으로 본다#
많은 third-party API는 request/response API와 webhook API를 함께 제공한다. 이 둘은 계약 특성이 다르다.
Webhook 테스트에서 확인할 항목:
- signature verification
- timestamp tolerance
- replay attack 방지
- event type unknown fallback
- event version
- duplicate delivery
- out-of-order delivery
- delayed delivery
- missing delivery 후 polling reconciliation
- partial payload 또는 expanded payload 차이
- idempotent event handler
Webhook fixture는 단일 JSON 샘플만 두기보다 event lifecycle을 시나리오로 기록하는 것이 좋다.
예:
scenario: payment_succeeds_after_pending
events:
- type: payment.created
status: pending
- type: payment.updated
status: processing
- type: payment.succeeded
status: succeeded
delivery:
duplicates: true
out_of_order: possible
expected:
final_internal_state: paid
handler_idempotent: true
9. CI/CD에서는 live 의존 테스트를 격리한다#
PR마다 live API를 호출하면 flaky test, 비용, rate limit, secret exposure 문제가 커진다.
권장 pipeline:
PR:
- unit adapter tests
- fixture replay tests
- schema/contract diff
- error mapping tests
Merge to main:
- sandbox smoke
- selected contract refresh check
Nightly:
- sandbox full regression
- live read-only probes
- payload drift detection
- provider changelog scan if available
Pre-release / Post-deploy:
- live canary
- webhook verification
- rollback/degrade readiness check
테스트 실패 정책도 분리한다.
- PR fixture test 실패: merge block
- nightly live probe 실패: alert 또는 quarantine 판단
- provider sandbox flaky: soft fail + trend tracking
- live canary 실패: deploy block 또는 rollback
10. 계약 테스트 결과를 운영 관측성과 연결한다#
테스트만으로는 외부 API drift와 부분 장애를 완전히 잡을 수 없다. 운영 관측성과 연결해야 한다.
필요 metric:
- provider별 latency
- status code 분포
- timeout count
- retry count
- retry success ratio
- circuit breaker open count
- idempotency conflict count
- unknown enum count
- unknown field count
- webhook duplicate count
- webhook delay
- reconciliation mismatch count
- sandbox/live parity probe failure count
필요 log field:
- provider
- endpoint
- operation
- request id
- idempotency key hash
- external correlation id
- internal trace id
- retry attempt
- classified error type
- degraded mode 여부
11. 추천 private capsule 초안#
---
title: Third-party API Contract Test Architecture
kind: private-capsule
topic: third-party-api-contract-testing
status: draft
tags:
- testing
- contract-testing
- third-party-api
- fixtures
- payload-drift
- reliability
- partial-outage
---
## Claim
Third-party API contract tests should be implemented as a layered architecture combining recorded HTTP fixtures, consumer-driven contracts, sandbox/live parity probes, payload drift detection, and explicit partial-outage semantics.
## Rationale
A fixture-only approach is fast but can hide provider drift. A live-only approach is slow, flaky, costly, and unsafe. Sandbox-only validation is insufficient because sandbox behavior often differs from live behavior in validation, rate limiting, async processing, webhooks, and downstream integrations.
## Architecture
1. Local adapter tests validate request construction, parsing, and error mapping.
2. Recorded HTTP fixtures provide deterministic regression coverage.
3. Consumer contracts define the request/response fields the application actually depends on.
4. Sandbox probes validate common integration flows.
5. Live canaries validate a minimal subset of real production behavior.
6. Payload drift detection compares observed payload shape against stored schema fingerprints.
7. Partial-outage tests model timeout, retry, rate limit, stale read, partial success, and webhook delay semantics.
## Implementation Notes
- Treat fixtures as contract artifacts with metadata, not static mocks.
- Redact secrets and personal data before committing fixtures.
- Separate strict assertions for consumed fields from tolerant handling of unknown fields.
- Use idempotency keys for retried write operations when the provider supports them.
- Classify errors into retryable, non-retryable, ambiguous-write, and degraded-mode paths.
- Maintain a sandbox/live parity matrix.
- Run fixture tests in PRs, sandbox tests after merge, and live canaries on schedule or around deployments.
- Connect contract test failures with production telemetry for provider drift and partial outage detection.
## Failure Semantics
- `connect_timeout`: safe to retry if request was not sent.
- `read_timeout_after_write`: ambiguous; retry only with idempotency or reconcile.
- `429`: respect provider retry policy.
- `5xx`: bounded retry with backoff and jitter.
- `4xx_validation`: do not retry; treat as client bug or contract mismatch.
- `401/403`: do not retry blindly; investigate credential, scope, or provider policy.
- `partial_success`: reconcile at item level.
- `webhook_delay`: maintain pending state and use polling fallback if available.
## Evidence Base
Public documentation and industry practice around VCR-style HTTP recording, consumer-driven contract testing, API simulation, idempotency, retry/backoff, and resilience patterns support this layered approach.
## Cautions
- Sandbox/live parity varies substantially by provider and must be measured, not assumed.
- Recorded fixtures can become stale and should have refresh policies.
- Additive provider changes should not break consumers unless they affect consumed fields.
- Unknown enum values require conservative fallback behavior.
- Live canaries must be designed to avoid irreversible side effects, excessive cost, and rate-limit impact.
Cautions#
- 이 초안은 특정 provider 하나에 대한 보증이 아니라, 외부 API 연동 전반에 적용 가능한 아키텍처 패턴이다.
- sandbox와 live의 차이는 provider별로 다르므로, 실제 적용 시 provider 문서와 실측 결과로 parity matrix를 작성해야 한다.
- recorded fixture는 시간이 지나면 낡는다. fixture refresh policy와 drift detection 없이 사용하면 잘못된 안정감을 줄 수 있다.
- consumer-driven contract testing은 provider가 협력하거나 contract broker를 운영할 때 가장 강하다. 협력이 어려운 public third-party API에서는 소비자 측 schema assertion과 live probe로 보완해야 한다.
- partial outage semantics는 비즈니스 도메인에 따라 달라진다. 결제, 주문, 의료, 금융처럼 write side effect가 큰 도메인에서는 retry 정책을 특히 보수적으로 설계해야 한다.
- 공개 자료는 일반 원칙과 도구 문서 중심이다. “모든 third-party API sandbox가 live와 불일치한다” 같은 일반화는 피해야 한다.
Sources#
- https://docs.pact.io/
- https://martinfowler.com/articles/consumerDrivenContracts.html
- https://vcrpy.readthedocs.io/
- https://github.com/vcr/vcr
- https://docs.wiremock.io/
- https://docs.hoverfly.io/
- https://docs.stripe.com/api/idempotent_requests
- https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/
- https://sre.google/sre-book/handling-overload/
- https://www.rfc-editor.org/rfc/rfc9110.html
Related#
- Core API Bulk Mutation Contracts: Per-Item Status Envelopes, Partial-Failure Semantics, Async Escalation, and Retry-Safe Idempotency
- Core API Rate Limiting Contracts: RFC 9333 Headers, Quota Semantics, Distributed Counter Drift, and Retry-After Failure Modes
- Core API Webhook Delivery Contracts: Signature Verification, Retry Semantics, Idempotent Consumers, and Clock-Skew Failure Modes
Sagwan Revalidation 2026-09-03T17:52:16Z#
- verdict:
ok - note: 최신 API 계약 테스트 관행과 부합하며 갱신 필요성이 낮습니다.
Sagwan Revalidation 2026-09-09T18:46:37Z#
- verdict:
ok - note: [chatgpt HTTP 404] {
Sagwan Revalidation 2026-09-12T11:42:59Z#
- verdict:
ok - note: 5계층 분리·VCR fixture·consumer contract·drift 탐지 원칙 모두 2026 현시점 best practice와 일치하며 낡은 수치·deprecated 도구 언급 없음.
Sagwan Revalidation 2026-09-16T04:35:51Z#
- verdict:
ok - note: 일반 아키텍처 권장안으로 최근 관행과 충돌하는 내용이 없다.