📖

StepCIで実APIをGitHub Actionsでテストしてみた

に公開

本記事は CyberAgent 26th Fresh Engineer's Advent Calendar 2025 の13日目の記事です。
https://qiita.com/advent-calendar/2025/ca-26th

TL;DR

  • StepCI を使って Connect RPC API のテストを CI で自動化
  • .http 手動叩きでは「壊れても気づかない」課題を解決。StepCI で YAML ベースの API テストを実現
  • JSONPath と JSON Schema でレスポンスの値と形を両面検証
  • GitHub Actions で MySQL → マイグレーション → サーバー起動 → StepCI でテスト実行の流れを構築

.httpファイルでの開発(開発中は便利だった)

開発中は .http ファイルで API を叩いていました。GoLand では Cmd + Enter、VSCode では REST Client などの拡張機能で、リクエストを直接実行できます。開発中の動作確認には十分便利でした。

### CreateUser
POST http://localhost:8080/user.v1.UserService/CreateUser
Content-Type: application/json

{
  "name": "test_user",
  "email": "test_user@example.com"
}

IDE で直接実行できるので、開発フローに自然に組み込めます。レスポンスもその場で確認できて、手軽に API の動作を検証できました。

.http ファイルでの実行結果

一方、.http ファイルで手動実行した場合は、レスポンスの JSON がそのまま表示されるだけです。ステータスコードやレスポンスの形を目視で確認する必要があり、CI で自動化するのは難しいです。

.httpファイル実行結果

どういう課題だったか

しかし、.http ファイルには以下の課題がありました:

  • CI/CD で自動化できない: IDE での手動実行が前提で、CI に組み込めない
  • アサーション機能が弱い: レスポンスの検証を目視で確認する必要がある
  • 壊れても気づかない: 手動実行なので、誰も実行しなければ壊れても発見できない
  • REST API しか対応していない: gRPC や GraphQL などのプロトコルには対応していない
  • ストリーミングに対応していない: Server-Sent Events (SSE) や WebSocket などのストリーミングレスポンスを検証できない

Note: Connect RPC は HTTP/JSON で叩けるため、.http ファイルでも開発中は便利でした。しかし、CI で自動化するには別のツールが必要でした。

単体テストのモックは実装ズレに鈍感で、実 API を叩ける CI の「見張り役」が欲しいという課題がありました。この課題を解決するため、CI/CD で使える API テストツールを比較検討し、StepCI を採用することにしました。

ツール比較で見たポイント

CI/CD で使える API テストツールとして、.httphurlstepci を比較しました。以下の4点を重視しました:

  • Docker/CLI でそのまま CI に積めること
  • ワークフロー(依存するリクエストの連鎖)を書けること
  • アサーションが表現力豊か(JSONPath など)であること
  • gRPC/HTTP の両面で Connect RPC を叩けること
項目 .http hurl StepCI
CI/CD 対応 ❌ IDE での手動実行が前提 ✅ CLI で実行可能 ✅ CLI/Docker で実行可能
ワークフロー ❌ 単一リクエストのみ ⚠️ 限定的(変数は使えるが連鎖は弱い) captures で値を受け渡し可能
アサーション ❌ 目視確認のみ ✅ JSONPath 対応 ✅ JSONPath + JSON Schema
プロトコル対応 ⚠️ REST API のみ ⚠️ REST API のみ ✅ REST / GraphQL / gRPC / WebSocket
学習コスト ✅ 低い(シンプルな記法) ✅ 低い(.http に近い記法) ✅ 低い(YAML で直感的)
ストリーミング ❌ 非対応 ❌ 非対応 ⚠️ リクエスト・レスポンス型前提

この4点を小コストで満たし、YAML で読みやすかったのが StepCI でした。

StepCI ざっくり紹介

  • 目的: 「実 API を CI で毎回叩いて検証する」を前提に設計された API テストランナー
  • 形式: YAML でテストを書く。version/name/env/tests を持ち、設定ファイルのように扱える
  • プロトコル: http / gql / grpc / ws をサポート(公式の Protocols セクション準拠)
  • 検証: checkstatusjsonpathschema(JSON Schema) を組み合わせ、値と形を両面で検証
  • データ受け渡し: captures でレスポンスから値を抜き、${{captures.xxx}} として次ステップで再利用
  • 実行形態: CLI と Docker イメージを公式が提供。Docker なら -v $(pwd)/tests:/tests でマウントし stepci/stepci run /tests/foo.yml
  • ログ: 失敗時にどのステップが落ちたかをテーブル形式で出力し、GitHub Actions でも追いやすい

サンプル tests/greet-service.yml

version: "1.1"
name: Greet Service Tests
env:
  host: http://localhost:8080

tests:
  greet_service:
    steps:
      - name: Greet
        http:
          url: ${{env.host}}/greet.v1.GreetService/Greet
          method: POST
          headers:
            Content-Type: application/json
          json:
            name: test
          check:
            status: 200
            schema:
              type: object
              properties:
                message:
                  type: string
              required:
                - message
  • env でエンドポイントをまとめ、tests 配下でステップを列挙する公式推奨の構造
  • check.status で HTTP ステータス、check.schema でレスポンス形を同時に担保
  • もっと複雑な API は check.jsonpathcaptures を追加していく(後述のレシピ参照)

5 分ハンズオン

公式の「Run via CLI/Docker」手順に合わせ、手元のリポジトリ構成に載せて動かします。

前提条件

  • Docker / Docker Compose が動く環境
  • Node/NPM があり npm install -g stepci できる(Docker でも可)

セットアップ手順

1. リポジトリをクローン

https://github.com/huavcjj/stepci-playground

git clone https://github.com/huavcjj/stepci-playground.git
cd stepci-playground

2. サービスを起動

make docker-up   # docker-compose で MySQL と Web サーバーを起動

3. 依存関係のインストールとコード生成

make setup-ci    # go mod / buf / protoc / sqlc / migrate up まで

4. StepCI テストを実行

# 1 ファイルずつ実行
stepci run tests/user-service.yml

# 全テストファイルをまとめて実行
make test

Note: make test は StepCI が無ければ npm でインストールし、tests/*.yml を順番に実行するだけのシンプル構成です。

実行結果の比較

StepCI での実行結果

StepCI を実行すると、各ステップの実行結果がテーブル形式で表示されます。成功したステップは緑色、失敗したステップは赤色で表示され、どの API が問題なのかが一目で分かります。

StepCI実行結果

StepCI の利点: テストファイルに書いた check の条件を自動で検証し、失敗時にはどのステップのどの検証が失敗したかを明確に示してくれます。

コードから読む StepCI レシピ

1. シンプルな API テスト(Greet Service)

29:tests/greet-service.yml
      - name: Greet
        http:
          url: ${{env.host}}/greet.v1.GreetService/Greet
          method: POST
          headers:
            Content-Type: application/json
          json:
            name: test
          check:
            status: 200
            jsonpath:
              $.message:
                - eq: "Hello, test!"
            schema:
              type: object
              properties:
                message:
                  type: string
              required:
                - message

リクエストを送って、ステータスコードとレスポンスの形を検証するだけのシンプルなパターン。サーバー起動やルーティングの初期不具合を拾いやすい。

2. JSONPath で値を検証する

46:tests/user-service.yml
      - name: CreateUser
        http:
          url: ${{env.host}}/user.v1.UserService/CreateUser
          method: POST
          headers:
            Content-Type: application/json
          json:
            name: "test_user"
            email: "test_user@example.com"
          check:
            status: 200
            jsonpath:
              $.user.name:
                - eq: "test_user"
              $.user.email:
                - eq: "test_user@example.com"
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                    email:
                      type: string
                  required:
                    - id
                    - name
                    - email
              required:
                - user
          captures:
            user_id:
              jsonpath: $.user.id

check.jsonpath でレスポンスの特定フィールドの値を検証できる。captures で値を拾って、後続のステップで ${{captures.user_id}} として使える。

3. JSON Schema でレスポンスの形を保証する

153:tests/user-service.yml
          check:
            status: 200
            jsonpath:
              $.users[0].id:
                - eq: ${{captures.user_id}}
              $.users[0].name:
                - eq: "test_user_updated"
              $.users[0].email:
                - eq: "test_user_updated@example.com"
            schema:
              type: object
              properties:
                users:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        type: string
                      name:
                        type: string
                      email:
                        type: string
              required:
                - users

check.schema は公式ドキュメントの Validation セクションで推奨されている書き方。JSONPath で「値」、Schema で「形」を担保する二段構えにしておくと、後続でフィールド追加・削除をしたときに落ちやすい。

4. 複数ステップでワークフローを書く

86:tests/user-service.yml
      - name: GetUser
        http:
          url: ${{env.host}}/user.v1.UserService/GetUser
          method: POST
          headers:
            Content-Type: application/json
          json:
            id: ${{captures.user_id}}
          check:
            status: 200
            jsonpath:
              $.user.id:
                - eq: ${{captures.user_id}}
              $.user.name:
                - eq: "test_user"
              $.user.email:
                - eq: "test_user@example.com"
            schema:
              type: object
              properties:
                user:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                    email:
                      type: string
                    createdAt:
                      type: string
                    updatedAt:
                      type: string
                  required:
                    - id
                    - name
                    - email
              required:
                - user

前のステップで captures した値を次のステップで使うことで、複数の API を連鎖させたテストが書ける。

GitHub Actions で StepCI をどう回しているか

.github/workflows/api-test.yml で StepCI を実行するための準備と実行を直列に並べただけ:

  1. MySQL サービスを起動: services で MySQL 8.0 コンテナを起動。ヘルスチェックで起動完了を待つ
  2. コード生成 & マイグレーション: make install-tools で buf、sqlc、migrate などをインストール → make generate で protobuf と SQL からコード生成 → make migrate-up でデータベーススキーマを適用
  3. API サーバーを起動して待機: make build でサーバーをビルド → バックグラウンドで起動(./server &)→ curl/greet.v1.GreetService/Greet にリクエストして起動完了を確認(最大60秒待機)
  4. StepCI でテスト実行: make test で StepCI が未インストールなら自動インストール → tests/ 配下の YAML ファイルを StepCI で順番に実行

Makefiletest ターゲットに「StepCI で YAML を全部回すだけ」を寄せておくと、ローカルと CI の入口を揃えられる。ログは StepCI がテーブルで出してくれるので追いやすい。

実行結果:

StepCI の実行方法

ローカルでは make test を実行すると、StepCI が未インストールなら自動でインストールされ、tests/ 配下の YAML ファイルを順番に実行する:

make test
# StepCI が未インストールなら自動インストール
# tests/*.yml を順番に実行

CI でも同じ make test を実行するため、ローカルと CI で同じテストが実行される。

StepCI の検証機能

StepCI では check セクションで以下の検証が可能:

  • ステータスコード: status: 200 で HTTP ステータスコードを検証
  • JSONPath: jsonpath でレスポンスの特定の値を検証(eqneexists など)
  • JSON Schema: schema でレスポンスの構造と型を検証
check:
  status: 200
  jsonpath:
    $.user.id:
      - eq: ${{captures.user_id}}
    $.user.name:
      - eq: "test_user"
  schema:
    type: object
    properties:
      user:
        type: object
        properties:
          id: { type: string }
          name: { type: string }

これにより、レスポンスの値と構造の両方を検証でき、API の変更を確実に検出できる。

使ってみて感じたハマりどころ

  • ストリーミング系(SSE / WebSocket)は不得意: リクエスト・レスポンス型前提
  • 依存関係がある API のテストでは事前準備が必要: Setup_ ステップが必要で YAML が伸びる
  • クラウド依存の API は実サービスが必要: 実サービスを立てないと検証できず、結局モックと併用になる場面がある
  • マイクロサービス全部乗せの統合テストは重い: 単一サービスの健全性チェックに割り切ると楽

まとめと所感

  • 「実 API を CI で毎回叩く」用途なら StepCI は手軽で十分強い
  • YAML で書くのでレビューしやすく、Connect RPC も HTTP モードで素直に叩けた
  • JSONPath と Schema の二段構えで、レスポンスの値と形の両方を検証できるのが安心
  • ストリーミングや大規模統合は別ツールを併用するのが現実的
  • ローカルは .http で素振り、CI は StepCI で自動検証、の二刀流が心地よかった

参考リンク

https://docs.stepci.com
https://github.com/stepci/stepci
https://connectrpc.com/

Discussion