본문으로 건너뛰기
중급20

API 계약 테스트: OpenAPI 스펙으로 약속을 검증하기

API 문서(OpenAPI 스펙)와 실제 API 동작이 일치하는지 자동으로 검증하는 계약 테스트의 개념을 이해한다.

  • #계약테스트
  • #OpenAPI
  • #Swagger

개념

API 문서와 실제 동작이 다를 때

"API 문서에는 email 필드가 필수라고 되어 있는데, 실제로는 안 보내도 요청이 성공한다" — 이런 문서와 실제 동작의 불일치는 실무에서 흔하다. 이 API를 쓰는 다른 팀(프런트엔드, 다른 마이크로서비스)은 문서를 믿고 코드를 짜기 때문에, 이 불일치가 연쇄적인 결함으로 이어진다.

OpenAPI 스펙이란

OpenAPI(예전 이름 Swagger)는 API의 엔드포인트, 요청·응답 형식, 필수/선택 필드, 데이터 타입을 표준화된 형식(보통 YAML이나 JSON)으로 기술하는 명세다.

paths:
  /orders:
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [productId, quantity]
              properties:
                productId:
                  type: string
                quantity:
                  type: integer
                  minimum: 1
      responses:
        '201':
          content:
            application/json:
              schema:
                type: object
                properties:
                  orderId:
                    type: string
                  totalPrice:
                    type: number

이 명세는 "주문 생성 API는 productIdquantity가 필수이고, quantity는 1 이상이어야 하며, 성공 시 orderIdtotalPrice를 포함한 응답을 준다"는 **약속(Contract)**을 기계가 읽을 수 있는 형태로 적어둔 것이다.

계약 테스트(Contract Testing)란

이 명세(계약)와 실제 API 응답이 정확히 일치하는지 자동으로 검증하는 것이 계약 테스트다.

계약 테스트 = "명세에 적힌 대로 실제 API가 동작하는가?"를 자동 검증

레슨 2의 어서션(totalPrice가 정확히 9000인지 확인)이 개별 값의 정확성을 확인한다면, 계약 테스트는 응답의 구조 자체가 명세와 일치하는지(필드가 다 있는가, 타입이 맞는가)를 확인한다는 점에서 다르다.

test("주문 생성 API 응답이 OpenAPI 스펙과 일치한다", async () => {
  const response = await request(app).post("/orders").send(validOrder)
  expect(response.status).toBe(201)
  expect(response.body).toMatchSchema(orderResponseSchema)  // 스펙 기반 스키마 검증
})

계약 테스트가 잡아내는 결함

  • 개발자가 API를 수정하면서 문서(스펙) 업데이트를 깜빡했다
  • 필드 이름을 실수로 바꿨는데(totalPricetotal_price) 아무도 눈치 못 챘다
  • 필수였던 필드를 선택으로 바꿨는데, 이걸 쓰는 다른 팀에 미리 알리지 않았다

이런 결함은 모듈 6(레슨 1)의 결함 생명주기 관점에서 보면, API를 사용하는 쪽(프런트엔드, 다른 서비스)에서 늦게 발견될수록 더 큰 비용이 든다 — 계약 테스트는 이걸 API가 배포되는 시점에 즉시 잡아낸다.

스키마 검증과 값 검증은 함께 쓰인다

계약 테스트가 "구조가 맞는지"를 확인한다고 해서 레슨 2·3에서 배운 "값이 정확한지" 확인이 필요 없어지는 건 아니다. 두 검증은 서로 보완하는 관계다 — 구조가 맞아도 계산이 틀릴 수 있고, 계산이 맞아도 구조가 깨질 수 있다.

실무에서 왜 필요한가

여러 팀이 API를 공유하는 조직(마이크로서비스 환경)에서, "이 API를 쓰는 다른 팀에 미리 알리지 않고 응답 구조를 바꿔서 장애가 났다"는 사고는 매우 흔하다. 계약 테스트를 CI(모듈 14)에 포함시켜 두면, API 제공자가 계약을 어기는 변경을 하려는 순간 배포 전에 자동으로 걸러진다 — 이건 모듈 8(레슨 4)에서 배운 "테스트 완료 단계에서 배운 점을 조직 전략에 반영한다"는 원칙을, 아예 자동화된 안전장치로 만든 것이다.

실습 과제

과제 1 — 계약 위반 찾기 (10분)

이 레슨의 OpenAPI 스펙 예시를 기준으로, 다음 실제 응답이 계약을 위반하는 부분이 어디인지 찾는다.

{
  "order_id": "ORD-123",
  "totalPrice": "9000"
}

과제 2 — 계약 테스트와 값 검증의 역할 나누기 (10분)

"주문 생성 API"를 테스트할 때, 계약 테스트로 확인할 항목과 개별 어서션(레슨 2·3)으로 확인할 항목을 각각 2가지씩 나눠 적는다.

자가 체크리스트

  • OpenAPI 스펙이 무엇을 기술하는 문서인지 설명할 수 있다
  • 계약 테스트가 값 검증과 어떻게 다른 것을 확인하는지 설명할 수 있다
  • 계약 테스트가 왜 마이크로서비스 환경에서 특히 중요한지 설명할 수 있다
  • 스키마 검증과 값 검증이 서로 보완적이라는 것을 설명할 수 있다

흔한 실수

  • 계약 테스트만 있으면 값 검증은 필요 없다고 오해한다. 구조가 맞아도 계산 로직이 틀릴 수 있다 — 두 검증 모두 필요하다.
  • API 문서(스펙)를 업데이트하지 않고 코드만 바꾼다. 계약 테스트가 실패로 잡아주긴 하지만,애초에 스펙과 구현을 함께 관리하는 습관이 먼저다.
  • 필드 이름의 대소문자·표기법 차이(totalPrice vs total_price)를 사소하다고 넘긴다. 이런 불일치가 실제로 연동 장애의 흔한 원인이다.

참고 자료