Contract testing with OpenAPI & TypeScript

Alex O'Callaghan

OpenAPIとTypeScriptによるコントラクトテスト

原文は Alex O'Callaghan により に公開されました。 このブログを購読する

大規模な組織で複数の開発チームが連携して開発を行う場合、システム間の境界をまたがるやり取りはしばしば障害の原因になります。明確に定義されたAPIとその動作に関する合意を確立することで、多くのインテグレーションエラーを減らすことができます。OpenAPITypeScriptのようなツールを活用すれば、自動テストで検証可能な明確な定義を提供できます。

シナリオ

あるエンジニアチームが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のマルチプロジェクトパイプラインを使うと次のようになります。

# 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は開発者体験を向上させる優れたツールであり、システム間で堅牢かつテスト可能なコントラクトをシンプルに構築するために活用できます。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント