OpenAPI와 TypeScript를 활용한 컨트랙트 테스팅
원문은 Alex O'Callaghan님이 에 게재했습니다. 이 블로그 구독하기
규모가 큰 조직에서 여러 소프트웨어 개발 팀이 함께 작업할 때 시스템 간의 경계를 넘나드는 상호작용은 종종 실패 지점이 된다. 명확하게 정의된 API를 구축하고 그 동작 방식에 대해 합의하면 상당수의 통합 오류를 줄이는 데 도움이 된다. OpenAPI와 TypeScript 같은 도구를 사용하면 자동화된 테스트로 검증할 수 있는 이러한 명확한 정의를 제공할 수 있다.
시나리오
엔지니어 팀이 Python 백엔드 서비스를 구현하는 상황을 상상해 보자. 이들은 이 서비스의 일부 기능을 사용할 웹 애플리케이션을 만드는 팀과 긴밀하게 협업하고 있다. 양 팀은 시스템 요구사항과 API의 동작 방식에 합의하지만, 시간이 지나면서 여러 팀이 이 서비스의 컨슈머가 되고 기능이 추가·변경되면서 버그가 생겨난다.
통합 문제를 잡아내기 위해 자동화된 테스트를 어떻게 추가할 수 있을까? 엔드 투 엔드 통합 테스트는 좋은 아이디어이지만 단점도 있다. 전체 스택을 띄우려면 툴링에 대한 합의가 필요하고 테스트 실행 속도도 느릴 수 있다. 여기에 여러 컨슈머까지 더해지면 이러한 구성은 유지하는 데 많은 엔지니어링 시간과 노력이 든다.
컨트랙트 테스팅, 즉 서비스에 대한 요청이 예상한 상태 코드와 데이터 구조를 반환하는지 검증하는 방식은 기술 스택에 대한 최소한의 합의만으로도 놓치기 쉬운 버그와 브레이킹 체인지를 잡아내는 데 도움이 될 수 있다.
OpenAPI와 TypeScript 활용하기
OpenAPI는 API를 문서화하기 위한 널리 쓰이는 표준으로, JSON Schema 및 다양한 프레임워크와 호환된다. TypeScript는 JavaScript 애플리케이션에 정적 타이핑을 도입하기 위한 대중적인 선택지다.
백엔드 서비스가 OpenAPI YAML 또는 JSON 스키마를 제공하면 프론트엔드 애플리케이션은 openapi-typescript를 사용해 해당 서비스 API에 대한 타입 정의를 생성할 수 있다:
$ npx openapi-typescript https://service.dev/openapi.json -o ./schema.d.ts이 타입들은 요청을 보내고 응답을 처리할 때 직접 참조할 수도 있고, openapi-fetch 같은 클라이언트와 함께 사용해 타입 안전한 요청을 제공할 수도 있다.
tsc --noEmit을 실행하면 타입 검사를 수행해 문제를 잡아낼 수 있다.
CI와 통합하기
프론트엔드 프로젝트에서는 비교적 간단하다. 프로덕션 서비스에서 최신 OpenAPI 스펙을 가져와 openapi-typescript로 타입 정의를 생성한 뒤 tsc로 타입 검사를 실행하는 CI 단계를 추가하면 된다.
백엔드 프로젝트에서는 CI 플랫폼이 지원한다면 컨슈밍 프로젝트에서 파이프라인을 트리거할 수 있다. 예를 들어 GitLab CI의 multi-project pipelines를 이용하면 다음과 같다:
# Backend service
## Generate `openapi.json` and store as an artifact
generate-openapi:
stage: test
artifacts:
paths:
- openapi.json
when: always
script:
- make generate-openapi
## Trigger a downstream pipeline in consumer
validate-openapi:
stage: test
needs: ["generate-openapi"]
variables:
UPSTREAM_REF: $CI_MERGE_REQUEST_REF_PATH
trigger:
project: path/to/consumer
branch: master
strategy: depend
# Frontend service
## Generate types from `openapi.json` schema and use `tsc` to type check
validate-openapi:
stage: test
needs:
- project: path/to/service
job: generate-openapi
ref: $UPSTREAM_REF
artifacts: true
rules:
- if: $CI_PIPELINE_SOURCE == "pipeline"
script:
- npx openapi-typescript openapi.json --output path/to/schema.ts
- tsc --noEmit이제 백엔드 서비스에 가해진 모든 변경 사항은 컨슈밍 애플리케이션 전반에서 브레이킹 체인지 여부가 검증된다.
정리하며
이상적인 세상이라면 크로스펑셔널 팀이 전체 스택을 온전히 유지·관리할 것이다. 하지만 규모가 큰 조직에서는 종종 그렇지 못하고, 팀 간 협업을 조율하는 일도 쉽지 않다. 원활한 소통이 핵심이지만, 자동화된 컨트랙트 테스팅은 그렇지 않으면 프로덕션까지 넘어갈 문제를 잡아내는 데 도움이 된다. 단일 팀이 단일 서비스/컨슈머를 유지하는 경우에도 시스템 복잡도가 커지면 API 변경 사항을 추론하기 어려워질 수 있다. OpenAPI와 TypeScript는 개발자 경험을 개선하는 훌륭한 도구이며, 시스템 간에 견고하고 테스트 가능한 컨트랙트를 매우 간단하게 구성하는 데 활용할 수 있다.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기