계약 테스트로 마이크로서비스 API 호환성 지키기

계약 테스트의 CDC, 제공자 검증, 브로커 운영 방식을 통해 마이크로서비스 API와 이벤트 인터페이스의 배포 호환성을 관리하는 방법

2026-08-14 · 최초 발행 2025-12-24

서비스 경계에서 깨지는 변경을 먼저 잡는다

마이크로서비스, API, 이벤트 기반 아키텍처에서는 서비스 간 통신 오류가 곧 배포 실패로 이어진다. 계약 테스트는 소비자와 제공자 사이의 상호작용 기대치를 계약으로 명시하고 자동 검증하는 방식이다.

환경 의존도가 높고 실행 시간이 긴 통합 테스트만으로 서비스 경계를 확인하는 대신, 계약 단위에서 빠르고 결정적인 검증을 수행한다. 소비자 영향도를 확인하고 양방향 호환성을 평가하며, 배포 전 can-i-deploy 질의로 무중단 배포의 위험을 낮출 수 있다.

소비자의 기대를 제공자 구현과 맞춘다

계약은 API 또는 메시지 인터페이스의 요청·응답 형식, 이벤트 스키마, 필드 제약, 상태 전제조건을 정형화한 아티팩트다. 소비자가 실제 사용 시나리오를 기준으로 기대치를 작성하고, 제공자는 그 계약이 현재 구현에서 충족되는지 검증한다.

이 방식은 단위 테스트와 통합 테스트 사이에서 비어 있던 서비스 경계를 다룬다. 의존 서비스의 가용성이나 복잡한 테스트 환경 구성에 덜 기대면서 인터페이스 호환성을 확인할 수 있다.

Consumer-Driven Contracts(CDC)에서는 소비자가 계약을 만들고, 제공자가 이를 기준으로 하위 호환성을 검증한다. 계약에서 생성된 스텁과 모크는 소비자 테스트와 로컬 개발에도 재사용할 수 있어 실제 인터페이스와 테스트 대상을 맞추는 데 도움이 된다.

계약 브로커 또는 레지스트리는 계약 버전, 이력, latest·prod 태그를 관리하고 팀 간 공유 지점이 된다. 특정 버전 조합이 배포 가능한지 확인하는 can-i-deploy 질의도 이곳을 기준으로 수행한다.

계약이 배포 게이트까지 도달하는 흐름

소비자는 HTTP/JSON, gRPC/Protobuf, 메시지/Avro 같은 상호작용 정의와 버전·태그 메타데이터를 바탕으로 계약을 만든다. 소비자 테스트가 실행되면 계약이 생성되어 브로커에 게시되고, 제공자 파이프라인은 이를 가져와 엔드포인트나 메시지 핸들러를 검증한다. 결과는 다시 브로커에 기록되며, 배포 직전에 호환성 질의를 통해 승인 여부를 판단한다.

상태를 전제로 하는 상호작용은 provider state로 조건을 고정한다. 멱등성은 별도 검증 케이스로 분리하고, 동시성에 영향을 주는 version, etag 필드는 계약에 포함하는 방식을 고려할 수 있다.

스키마 축소 변경이 탐지되면 검증은 실패하며, 계약 재협상이나 호환성 계층 추가가 필요하다. 제공자 환경이 일시적으로 불가용한 경우에는 재시도와 검증 타임아웃을 설정한다. 소비자가 여럿이라 충돌 가능성이 있으면 태그와 환경별 매트릭스로 검증을 분리한다.

계약 아티팩트 생성검증 웹훅 트리거계약 풀링 상태 세팅검증 성공 리포트검증 실패 리포트배포 can-i-deploy 질의허용거부소비자 계약 테스트 실행(상호작용 정의)계약 브로커 게시제공자 검증 파이프라인 실행엔드포인트/메시지 검증 수행브로커 결과 업데이트롤백 또는 계약 재협상배포 결정프로덕션 배포

변경이 잦은 통합 지점에서의 활용

모놀리스를 마이크로서비스로 분해하거나 마이그레이션할 때 계약은 기존 API 상호작용을 안정화하고 병행 개발을 뒷받침한다. API 게이트웨이와 백엔드를 연결하는 환경에서는 라우팅·변환 규칙의 회귀를 막고, 하위 서비스 변경이 미치는 범위를 줄이는 데 쓸 수 있다.

Kafka, SNS/SQS 같은 이벤트 스트리밍에서도 Avro 또는 JSON 스키마를 계약으로 관리하면 생산자와 소비자 사이의 단절을 줄이고 리스너 배포를 더 안전하게 만들 수 있다. 외부 파트너 통합에서는 공개 계약과 샌드박스 스텁을 제공해 온보딩을 단축하고 변경 공지를 자동화하는 방식으로 활용한다.

계약을 작게 유지하고 버전 정책을 운영한다

계약에는 소비자가 실제로 쓰는 필드만 담는 편이 낫다. 선택 필드는 기본값이나 널 허용 정책을 명확히 정해야 한다. 필드 추가는 옵셔널 방식으로 처리하고, 필드 제거 또는 스키마 축소는 단계적 폐기 정책 아래에서 진행한다. 멀티버전 공존 전략과 계약 만료 일정도 함께 관리해야 한다.

태그는 dev·stage·prod 같은 환경 태그와 브랜치 태그를 함께 사용해 매트릭스 검증을 구성할 수 있다. provider state는 데이터 픽스처나 시드 스크립트로 표준화해 테스트 독립성을 유지한다. can-i-deploy를 통과하지 못한 배포는 차단하고 실패 원인을 자동으로 전달하는 게이트도 파이프라인에 둔다.

계약 테스트에는 도입 비용이 따른다. 브로커와 파이프라인을 연결하고 테스트 문화를 정착시켜야 한다. 또한 분산 트랜잭션이나 사가 전체를 계약 테스트만으로 보장할 수는 없으므로, 복잡한 크로스 서비스 시나리오는 최소한의 통합 테스트와 E2E 테스트로 보완해야 한다. 소비자별 시나리오가 과도하게 중복되지 않도록 공통 상호작용 템플릿을 두는 것도 필요하다.

Pact로 계약을 생성하고 제공자를 검증한다

대표 도구로는 Pact, Spring Cloud Contract, Pactflow 호스티드 브로커, 메시징용 AsyncAPI와 Avro Schema Registry가 있다.

아래 예시는 Node.js 18+, @pact-foundation/pact 최신 버전, 그리고 http://localhost:8080에서 실행 가능한 제공자 서비스를 전제로 한다.

소비자 테스트는 Pact JS로 상호작용을 정의하고 계약 파일을 생성한다.

// package.json: "test": "node consumer.pact.test.js"
// npm i @pact-foundation/pact node-fetch@2
const { Pact } = require('@pact-foundation/pact');
const fetch = require('node-fetch');

const provider = new Pact({
  consumer: 'checkout-ui',
  provider: 'orders-service',
  port: 1234,
  logLevel: 'warn'
});

(async () => {
  await provider.setup();
  await provider.addInteraction({
    state: 'order with id 123 exists',
    uponReceiving: 'a request for order 123',
    withRequest: { method: 'GET', path: '/orders/123' },
    willRespondWith: {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
      body: { id: 123, status: 'PAID', totalAmount: 45000 }
    }
  });

  const res = await fetch('http://localhost:1234/orders/123');
  const json = await res.json();
  if (json.status !== 'PAID') throw new Error('unexpected status');

  await provider.verify();
  await provider.finalize(); // ./pacts/checkout-ui-orders-service.json 생성
  console.log('Pact generated');
})();

제공자 측에서는 생성된 Pact 파일을 로컬 서비스에 대조해 검증할 수 있다.

# npm i -g @pact-foundation/pact@latest
pact-verifier \
  --provider-base-url=http://localhost:8080 \
  --pact-url=./pacts/checkout-ui-orders-service.json \
  --publish-verification-result

브로커를 사용하는 경우 계약을 게시한 뒤 대상 환경에 배포 가능한지 질의한다.

# 게시
pact-broker publish ./pacts --broker-base-url=$BROKER_URL --consumer-app-version=$GIT_SHA --tag=dev
# 배포 가능성 질의
pact-broker can-i-deploy --pacticipant orders-service --version $GIT_SHA --to-environment prod

테스트 범위에 따라 달라지는 운영 특성

지표 계약 테스트 통합 테스트 E2E 테스트
성능(실행 속도) 매우 빠름, 초·분 단위 중간, 분·십분 느림, 십분·시간
확장성(병렬화) 높음, 서비스 단위 병렬 용이 제한적, 환경 공유 이슈 낮음, 환경 병목
일관성(결정성) 높음, 스텁/상태 고정 중간, 데이터 드리프트 낮음, 비결정적 요소 많음
안정성(플레이크) 낮음, 원인 분석 용이 중간 높음, 원인 추적 어려움
운영 편의 브로커·버전 관리 필요 공용 환경 유지 필요 환경 비용·유지 부담 큼

계약 테스트를 도입하면 배포 실패율은 2040% 감소하고 결함 유입률은 3050% 감소할 것으로 추정된다. 릴리스 사이클 타임은 25~60% 단축되며 롤백 빈도도 줄어든다. 공용 통합 환경 의존을 최소화해 테스트 인프라 비용을 절감하고, 신규 파트너 온보딩 기간을 단축하는 효과도 기대할 수 있다.

서비스 경계의 기본 검증을 계약 테스트 중심으로 두고, 복잡한 시나리오만 소수의 통합·E2E 테스트로 보완하는 구성이 적합하다. 이를 위해서는 CDC, 브로커, 파이프라인 게이트를 함께 운영하고 하위 호환성과 버저닝을 변경 관리 원칙으로 삼아야 한다.

계약 테스트마이크로서비스PactCI/CDAPI 호환성