BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

継続的な負荷テスト環境をBASEに構築しました 〜 第3回: モックツールの構築と運用

はじめに

こんにちは、Checkout Reliabilityチームでバックエンドエンジニアをしているかがの(@ykagano)です!

こちらは、「継続的な負荷テスト環境をBASEに構築しました」の第3回の記事です。

先に第1回、第2回を読んでいただくのをおすすめします。

こちらは第1回から紹介しているBASEの負荷テスト環境の構成図です。

cart-load-test-system
負荷テスト環境の構成図

本記事では、モックツールとして採用したWireMockの選定理由から、実際の構築・運用方法までを紹介します。

モックツールの選定

モックツールも要求事項は負荷生成ツールと基本的には同じでした。

以下は第2回の記事からの要求事項の引用です。

  • OSSである

    • クラウドはコストとセキュリティの両面でハードルが上がるため
  • 分散実行が可能である

    • 継続的負荷テスト環境としてスケーリングは必要
  • Web UIがある

    • レポート品質が高い
  • 学習コストが低い

    • メンバーの誰もが利用できるようにしたい
  • 実績が豊富である

    • OSSが今後も保守される可能性が高い

こちらもOSSの比較表を参考として整理しました。

ツール 分散実行可能 Web UI 学習コスト 実績が豊富 特徴
WireMock △(自前スクリプトが必要) △(標準UIなし) ○(機能が豊富) 業界標準。機能は強いが、UIと分散は独自実装が必要
MockServer △(状態は共有できない) ○(機能が豊富) UIあり・機能も豊富
Smocker △(分散は想定されていない) ○(YAML/JSON定義が直感的) UI特化・軽量。個人/小規模チーム向け

モックツールは他にも多数あるのですが、実績や保守性がWireMockとMockServerが突出しているように感じたため、まずこの2つを候補としました。

そして他のツールもWeb UIをそれぞれ実際に触ってみた結果、SmockerのUIが使いやすく思えたため候補に加えた形です。

しかし保守性を考えると実績の豊富なWireMockが良いと思っており、UIの課題と分散実行の部分をクリアできないかと調査を進めました。

継続的な負荷テストを実施するためには、複数のモックサーバーに対して、スタブの設定を容易かつ即時で変更できるUIが必要と考えていたためです。

WireMockのサードパーティのUIも探しましたが、複数のWireMockインスタンスに即時で反映できるようなUIはありませんでした。

そのため、個人的にWireMock HubというOSSを作ることにしました。

ykagano.github.io

スタブの編集がUIから行え、複数のWireMockインスタンスに即時で反映できる機能をメインに実装しています。

WireMockと組み合わせることで、使い勝手の良い負荷テスト用のモックツールとなります。

wiremock.org

WireMockの構築

ECSに構築しました。

下図では負荷テスト用にWireMockを2台起動しています。

Nginxは外部APIのRate-Limiter(流量制限)を再現するために使用しています。

wiremock-system-in-ecs
ECSでのWireMockの構成

またその後ろにWireMock HubのAll-in-One版を起動しています。

1つのコンテナの中にNginx + WireMock Hub + WireMockを内包しています。

負荷テストのために、Web UIを持つWireMock Hubがスタブデータの管理とWireMockへの同期を行います。

負荷テスト用のWireMockは負荷テスト利用時にだけ起動していますが、WireMock Hubだけを日常的に起動しておくこともできます。

こうすることで負荷テスト以外の場面でも、外部APIの異常系のテストなどでWireMock Hubの内包するWireMockをUI付きで日常的に使用することができます。

今回負荷テスト環境を構築しましたが、副産物として、日常的にWireMockを使用できるようになったものとなります。

WireMock Hubの構成

WireMock HubはSQLiteのDBを持っています。

プロジェクトの配下にWireMockのインスタンスとスタブが紐付く構造にしています。

そのため、プロジェクトの持つスタブをインスタンスに対して入れ替えることも可能になっています。

プロジェクト
├─ インスタンス
└─ スタブマッピング

実際にWireMock Hubに登録しているプロジェクトです。

project-page-of-wiremock-hub
WireMock Hubのプロジェクト画面

次はインスタンスです。

instance-page-of-wiremock-hub
WireMock Hubのインスタンス画面

例えば、過去のプロジェクトで使用したスタブをもう一度使いたいという時に、プロジェクトの持つスタブを同期させて入れ替えることも、追記して他のプロジェクトで同期したスタブと同居することもできます。

このように、WireMock HubはWireMockを1台だけ利用したい場合にも便利に使えるようになっています。

WireMock Hubからのスタブ登録

WireMock Hubからのスタブの登録方法は以下の3種類があります。

  1. UIでのスタブ編集(下図 Create Newボタンや編集アイコンボタン)
  2. スタブファイルのインポート機能(下図 Importボタン)
  3. プロキシ機能を使ったレコーディング機能(下図サイドメニューのRecording)

stub-mapping-page-of-wiremock-hub
WireMock Hubのスタブマッピング画面

このうち、負荷テスト環境の構築時は、主にレコーディング機能とインポート機能を使っていました。

レコーディング機能

レコーディング機能はWireMockが標準搭載している機能です。

まだ何もスタブに登録していない場合、レコーディング機能はとても役に立ちます。

recording-page-of-wiremock-hub
WireMock Hubのレコーディング画面

レコーディング画面でTarget URLを指定して開始すると、WireMockが対象のURLに対するプロキシ(中継サーバ)になります。

この状態でBASEシステムと外部システムの間にWireMockを挟んで通常のリクエストを通すことができます。

図にするとこのように右上から通常の購入リクエストがBASEシステムに入ってきますが、 BASEシステムが左のWireMockに外部サービスへのリクエストを投げて、結果、外部サービスにリクエストが到達します。

communicate-external-service-from-wiremock-proxy
WireMockがプロキシとして外部サービスに通信している図

こうすることで、WireMockに実リクエストが記録されます。

レコーディングを停止するとWireMockには記録された実リクエストがスタブとして登録されます。

WireMockのリクエストログにも記録されているため、リクエストログからWireMock Hubにスタブ登録することも可能です。

その後、汎用的なスタブにするための調整は必要ですが、レコーディング機能を使えば、スタブデータを手で書くことなく登録できます。

インポート機能

インポート機能はWireMock HubからWireMockのJSON形式とOpenAPI形式の両方がインポートできます。

WireMock形式のJSON例

{
    "mappings": [
        {
            "request": {
                "method": "GET",
                "url": "/test"
            },
            "response": {
                "status": 200,
                "headers": {
                    "Content-Type": "application/json"
                },
                "body": "{\"message\": \"success\"}",
                "fixedDelayMilliseconds": 2000
            },
            "priority": 5,
            "persistent": true,
            "name": "テストだよ",
            "scenarioName": "test",
            "requiredScenarioState": "Started",
            "newScenarioState": "Step_2",
            "metadata": {
                "hub_isActive": true
            }
        }
    ],
    "meta": {
        "total": 1
    }
}

例えばレコーディング機能で記録したスタブを、WireMockのJSON形式でエクスポートしておけば、以降はClaude Codeで編集することができます。

例えばモニタリングツールに記録された外部APIの通信時間の平均をスタブの遅延時間(fixedDelayMilliseconds)に一律で設定するといったこともできますし、実際にこうしたスタブの設定調整をClaude Codeを通して行っています。

WireMock HubからWireMockへの自動同期

WireMock HubはWireMockとは疎結合なアプリケーションとして設計しています。

そのため、ECSでWireMockを再デプロイした場合、WireMock Hubに登録していたWireMockインスタンスのIPアドレスが変わってしまうことになります。

毎回、WireMockインスタンスのIPアドレスを調べて再登録せずに済むように、WireMock Hubにはインスタンスの再登録APIを用意しています。

下図が同期処理のシーケンス図となります。

LambdaのAPI経由でインスタンスIPリストを取得し、WireMock HubのAPIに渡すことで、インスタンスIPアドレスを更新し、WireMock Hubのプロジェクトに紐付くスタブをWireMockに自動同期します。

sequenceDiagram

participant gha as GitHub Actions
participant tf as Terraform
participant ecs as ECS Fargate<br/>(WireMock Worker)
participant sd as Service Discovery<br/>(Cloud Map)
participant lambda as Lambda<br/>(wiremock-sync)
participant hub as WireMock Hub

%% Phase 1: Terraform デプロイ
Note over gha, ecs: Phase 1: Terraform デプロイ

gha ->> tf : wiremock-hub apply
activate tf
tf ->> hub : WireMock Hub デプロイ
activate hub
tf -->> gha : apply 完了
deactivate tf

gha ->> tf : wiremock apply
activate tf
tf ->> ecs : WireMock Worker デプロイ
activate ecs

ecs ->> sd : タスクIP自動登録(HEALTHY)
note right of sd : wiremock-worker.cart-load-test.local<br>→ 172.20.10.x

tf -->> gha : apply 完了
deactivate tf

%% Phase 2: GitHub Actions による Lambda invoke
Note over gha, lambda: Phase 2: Lambda invoke

gha ->> lambda : aws lambda invoke
activate lambda

%% Phase 3: Lambda 処理
Note over lambda, sd: Phase 3: Service Discovery からインスタンス取得

lambda ->> sd : discover_instances(HEALTHY のみ)
activate sd
sd -->> lambda : インスタンスIPリスト返却
deactivate sd

%% Phase 4: Hub API → スタブ配信
Note over lambda, hub: Phase 4: Hub API 呼び出し → スタブ配信

lambda ->> hub : POST /hub/api/projects/{id}/instances/bulk-update
note right of hub : {"instances": [...], "syncStubs": true}

hub ->> hub : インスタンスIP更新
hub ->> hub : プロジェクトのスタブ定義を取得

hub ->> ecs : POST /__admin/mappings(各 Worker)
ecs -->> hub : スタブ登録完了

hub -->> lambda : 同期結果
lambda -->> gha : 完了

deactivate lambda
deactivate ecs
deactivate hub

この仕組みによって、WireMock側の再デプロイをしても、負荷テストに必要なスタブは常に担保されます。

おわりに

WireMockはノーコードでスタブが作れますので、開発中APIの代替や異常系の通信テストなどにも便利です。

今回作ったWireMock環境は、カート機能だけでなく、社内で誰でも使えるモックツールとして日常的に使っていければと思います。

全3回の記事を最後までお読みいただきありがとうございました!

モックを使ったテストに興味がありましたらぜひ採用情報をご覧ください!

binc.jp