최근의 배포로 인해 모든 단위 테스트, 통합 테스트, 모크 서버 체크를 통과했음에도 불구하고 세 개의 마이크로서비스가 중단되었습니다. 팀은 API 모크 대신 계약 테스트(contract testing)를 도입했습니다. 6개월 만에 계약 수는 3개에서 47개로 늘어났고, 월간 통합 실패율은 두 건에서 0건으로 감소했습니다.

모크가 당신을 보호할 수 없는 이유

모크 서버는 컨슈머가 기대하는 형태만 흉내 낼 뿐, 프로바이더가 실제로 그 형태를 제공하는지는 확인하지 않습니다. 만약 프로바이더가 필드 이름을 name에서 display_name으로 변경한다면, 모크는 여전히 이전 페이로드를 반환하고 컨슈머의 테스트는 통과(green) 상태를 유지하지만, 실제 시스템은 충돌합니다. 화요일 오후 2시에 발생한 운영 환경의 장애는 바로 이것 때문이었습니다. 모크가 실제 계약에 대해 "거짓말"을 한 것입니다.

계약 테스트가 공백을 메워줍니다

계약 테스트는 코드가 운영 환경에 반영되기 전에 API의 양측이 공유된 정의에 합의하도록 강제합니다. 두 가지 일반적인 접근 방식이 있습니다:

  • 컨슈머 주도 계약 (Consumer-driven contracts) – 컨슈머 서비스가 기대 사항을 작성하면, 프로바이더 서비스가 이를 검증합니다. 이는 함께 진화하는 내부 마이크로서비스에 적합합니다.
  • 프로바이더 주도 계약 (Provider-driven contracts) – 프로바이더가 명세를 게시하면, 컨슈머가 자신의 코드를 이에 맞춰 확인합니다. 이는 공개 API에서 주로 사용하는 패턴입니다.

첫 번째 방식은 일반적으로 마이크로서비스 아키텍처 내부의 통합 오류를 방지합니다.

컨슈머 주도 계약의 작동 방식

  1. 컨슈머는 프로바이더로부터 정확히 무엇이 필요한지 설명하는 테스트를 작성합니다.
  2. 테스트를 실행하면 해당 기대 사항을 기록한 JSON 문서인 pact 파일이 생성됩니다.
  3. 프로바이더는 CI 파이프라인에서 pact 파일을 대상으로 실제 서비스를 실행합니다.
  4. 프로바이더가 필드를 변경하면 검증에 실패하고 빌드가 차단됩니다.

검증이 실제 프로바이더 코드를 대상으로 실행되기 때문에, 모든 파괴적인 변경 사항(breaking change)은 배포 후가 아니라 조기에 발견됩니다.

테스트 피라미드에서 계약 테스트의 위치

  • 단위 테스트 (Unit tests) – 빠르며, 격리된 로직을 테스트합니다.
  • 계약 테스트 (Contract tests) – 중간 속도이며, API 합의가 유지되는지 확인합니다.
  • E2E 테스트 (End-to-end tests) – 느리며, 전체 비즈니스 흐름을 실행합니다.

계약 테스트를 단위 테스트의 빠른 피드백과 E2E 테스트 세트의 광범위한 커버리지 사이를 잇는 가교로 취급하십시오. 가장 자주 깨지는 통합 지점을 대상으로 삼고, 두세 개의 핵심 엔드포인트부터 시작하십시오.

실제 도입 사례

이 글의 계기가 된 팀은 가장 취약한 호출을 다루는 3개의 계약으로 시작했습니다. 6개월 후, 그들은 서비스 간 트래픽의 대부분을 커버하는 47개의 계약을 보유하게 되었습니다. 그 기간 동안 API 장애 발생 건수는 월 2건에서 0건으로 줄어들었습니다.

계약 테스트가 가치가 없는 경우

  • 모든 서비스를 단일 저장소에서 관리하는 1인 개발자인 경우.
  • API가 매우 안정적이며 수년 동안 변경되지 않은 경우.
  • 곧 폐기될 일회성 프로토타입을 구축하는 경우.

이러한 시나리오에서는 계약을 유지 관리하는 오버헤드가 이점보다 클 수 있습니다.

잠재적인 단점 및 완화 방법

  • 계약을 설명하는 코드와 함께 버전을 관리하십시오.
  • 오래된 계약을 방지하기 위해 모든 CI 실행 시 검증을 자동화하십시오.
  • 실수로 인한 장애를 방지하기 위해 풀 리퀘스트(PR)에서 계약 변경 사항을 검토하십시오.

요약

서비스가 서로 통신할 수 있다는 것을 스스로 확신하기 위해 여전히 수동으로 만든 모크에 의존하고 있다면, 당신은 잘못된 약속에 도박을 하고 있는 것입니다. 계약 테스트는 그 도박을 검증 가능한 합의로 바꾸어, 파괴적인 변경 사항이 운영 환경에 도달하기 전에 잡아내며, 이 팀의 수치가 보여주듯 통합 실패를 완전히 제거할 수 있습니다.