Claude CodeでLocust負荷テストのシナリオを書く手順
Claude Codeにシナリオ設計とlocustfile.pyの実装を任せ、Web UIでの確認からヘッドレス実行・CI組み込みまでを手順化します。
Claude CodeにLocustのシナリオ設計とlocustfile.pyの実装を任せると、Web UIでの動作確認からヘッドレス実行の設定までを一貫した手順で進められます。ポイントは、シナリオ設計・実装・検証を別々の指示に分けて進めることです。この記事では、インストールからCIで使うヘッドレスモードの設定までを手順化します。
Locustは専用DSLではなくPythonのプログラム
Locustの負荷テストは専用のDSLではなく、通常のPythonプログラムです。公式ドキュメントも「テスト対象のシステムへリクエストを送るだけのPythonプログラム」と説明しています。仮想ユーザーはgeventのグリーンスレッド(greenlet)として並行実行されるため、複雑なユーザーフローも1つのPythonクラスとして書けます。
この性質はClaude Codeとの相性がいい領域です。シナリオ設計(どのエンドポイントを、どんな比率で、どんな待ち時間で叩くか)と、locustfile.pyへの実装は別の指示に分けます。既存コードにテストを足すときの一般的な進め方と同じ型に落とし込めるからです。Claude Codeに既存コードのテストを書かせる手順でも、対象の特定→雛形生成→ケース追加→実行して直す、という順序が精度を左右すると述べています。Locustのシナリオ設計でも、この順序がそのまま有効です。
始める前に確認すること
着手前に3点を確認しておきます。
1点目はPython環境とLocustのインストールです。
pip install locust
locust --version2点目は、テスト対象のホストと代表的なエンドポイントです。Locustは-H(--host)でベースURLを指定し、以降のリクエストはそこからの相対パスで書きます。
3点目は、Bashコマンドの実行許可です。Claude Codeがlocustコマンドを何度も実行して結果を読むたびに確認が入ると、検証のテンポが落ちます。Bash(locust:*)のように対象コマンドを絞って許可リストに追加しておくと、以降は確認なしで実行と結果読み取りを任せられます。
手順1: シナリオをClaude Codeに設計させる
最初に、テストしたいユーザーフローと、タスクごとの実行比率をClaude Codeに伝えます。ファイル名や既存のAPIルーティング定義を指定すると、対象が絞られて提案が具体的になります。
routes/api.pyを見て、/api/products一覧取得と/api/cart追加をシナリオにしたLocustのUserクラスを設計して。一覧取得を3倍の頻度で叩くLocustのタスクは@taskデコレータを付けたメソッドとして定義し、@task(3)のように整数を渡すと選ばれる頻度の比率になります。上の指示であれば、一覧取得のタスクに@task(3)、カート追加のタスクに@task(1)を割り当てる設計になります。タスク実行後の待機時間はwait_time属性で指定し、between(1, 5)なら1〜5秒のランダム待機、constant(1)なら常に1秒の固定待機です。秒間の実行回数を一定に保ちたい場合はconstant_throughput、逆に一定間隔で1回ずつ実行したい場合はconstant_pacingも使えます。
一般ユーザーとは別に、管理者操作のような特定の1件だけを独立して流したい場合はfixed_count属性が使えます。fixed_count = 1を設定したUserクラスは、weightによる比率計算とは無関係に、指定した数だけ先に生成されます。全体のユーザー数を増減させても、管理者役のリクエスト数は変わらないので、集計時に一般ユーザーの負荷と混ざりません。
手順2: locustfile.pyを生成させて、レスポンスを検証する
雛形が固まったら、実際のPythonコードを生成させます。ログイン処理はon_startメソッドに書くと、各仮想ユーザーの開始時に1度だけ実行されます。
from locust import HttpUser, task, between
class ShopUser(HttpUser):
wait_time = between(1, 5)
def on_start(self):
self.client.post("/api/login", json={"username": "demo", "password": "demo"})
@task(3)
def list_products(self):
with self.client.get("/api/products", catch_response=True) as response:
if response.status_code != 200:
response.failure(f"unexpected status: {response.status_code}")
@task(1)
def add_to_cart(self):
self.client.post("/api/cart", json={"product_id": 1, "qty": 1})self.clientはHttpUserが持つHttpSessionで、リクエストの成否や応答時間をLocustの統計に自動で記録します。既定ではHTTPステータスが400未満なら成功扱いです。catch_response=Trueとwith文を組み合わせると、response.failure()で任意の条件を失敗として扱えます。逆にresponse.success()を呼べば、404のような通常は失敗扱いのレスポンスを成功として記録することもできます。動的なIDを含むURLを叩くタスクではname引数を渡すと、統計上のエントリを1つにまとめられます。
こうした命名規則や検証の書き方は、プロジェクトごとに固まっていくものです。毎回同じ方針を説明し直したくない場合は、Claude CodeモノレポのテストをSKILL.mdで教える手順と同じ要領で、Locust用のSKILL.mdに規約をまとめておくと再現性が上がります。locustfile.py自体は通常のPythonモジュールです。シナリオが増えてきたらcommon/ディレクトリにログイン処理や設定を切り出し、import common.authのように読み込む構成にもできます。
手順3: Web UIで動作確認する
シナリオができたら、まずWeb UIで挙動を目視確認します。
locust -f locustfile.py -H https://staging.example.comhttp://localhost:8089を開くと、同時ユーザー数とスポーンレート(1秒あたりに増やすユーザー数)を入力する画面が出ます。実行を開始すると、Chartsタブでリクエスト数(RPS)・応答時間・実行中ユーザー数の推移をリアルタイムで確認できます。応答時間が伸び続けてRPSが頭打ちになったら、対象システムが飽和点に達しているサインです。
ここで1つ注意点があります。HttpUserは実際のブラウザではないため、HTMLレスポンスを解析して画像やCSS・JSを自動では取得しません。ページの表示を丸ごと模したい場合は、必要な静的リソースへのリクエストをタスクに明示的に追加する必要があります。Cookieの保持だけは自動で行われます。
手順4: ヘッドレスモードでCI向けに実行する
Web UIでの確認が終わったら、--headlessフラグでUIなしの実行に切り替えます。
locust -f locustfile.py --headless -u 100 -r 10 -t 5m \
-H https://staging.example.com \
--csv results --html report.html-u(--users)がピーク時の同時ユーザー数、-r(--spawn-rate)が1秒あたりのスポーン数、-t(--run-time)が実行時間です。--run-timeを省略すると、テストは無期限に実行され続けます。--csvを指定するとresults_stats.csvなど3種類のCSVが、--htmlを指定するとHTMLレポートがそれぞれ出力されます。タスクの途中終了を許容したくない場合は、-s(--stop-timeout)で終了猶予秒数を指定します。
CIで合否判定に使うには、終了コードを利用します。既定では失敗リクエストが1件でもあると終了コード1を返します。@events.quittingイベントでenvironment.process_exit_codeを自分で設定すれば、失敗率や応答時間のしきい値をもとに合否を決められます。GitHub Actionsなら、次のようにジョブへ組み込めます。
- run: pip install locust
- run: locust -f locustfile.py --headless --run-time 5m -H https://staging.example.com1台では負荷が足りない場合は、--masterと--workerで複数プロセス・複数マシンに分散できます。マスター側で--expect-workersを指定すると、指定数のワーカーが接続するまで開始を待機します。GitHub Actionsのようなホスト型CIだけでなく、Claude Code自身のセルフホスト環境からヘッドレス実行をトリガーする構成も作れます。その場合はClaude Codeセルフホスト環境をCIでE2Eテストするで扱うRunnerの構成が参考になります。
時間指定ではなく、独自の条件でテストを打ち切りたい場合はenvironment属性からrunner.quit()を呼び出せます。タスク内でself.environment.runner.quit()を実行すると、スタンドアロン実行では全体が、ワーカーノード上ではそのノードだけが停止します。エラー率が閾値を超えた時点で即座に打ち切りたいときなど、-tだけでは表現しにくい終了条件に使えます。
Web UIとヘッドレス、どちらを使うか
| 用途 | おすすめ | 理由 |
|---|---|---|
| シナリオの初期調整 | おすすめWeb UI | 理由グラフを見ながらユーザー数やスポーンレートをその場で変えられる |
| CI/CDへの組み込み | おすすめヘッドレス | 理由引数だけで完結し、終了コードで合否を判定できる |
| 長時間の耐久試験 | おすすめヘッドレス + --csv | 理由統計をファイルに残し、後から傾向を分析できる |
| 結果をチームで共有 | おすすめヘッドレス + --html | 理由レポートを1ファイルとして配布できる |
よくあるつまずき
wait_timeの指定漏れは、想定より高い負荷を生みます。指定がない場合はタスクが終わるとすぐ次のタスクが実行されるため、実際のユーザーの操作間隔より高頻度でリクエストが飛びます。
--run-timeを省いたヘッドレス実行は止まりません。CI環境でこれを忘れると、ジョブがタイムアウトするまでLocustが動き続けます。CIの設定側にもタイムアウトを別途入れておくと二重に安全です。
nameを付け忘れた動的パラメータ付きリクエストは、統計が分散します。/item?id=1と/item?id=2が別エントリとして記録され、集計時の見通しが悪くなります。
複数のUserクラスを持つlocustfileは、コマンドラインでクラスを指定しない限り均等にスポーンされます。比率を変えたい場合は各クラスにweight属性を設定するか、locust -f locustfile.py WebUserのように対象クラスを明示します。
まとめ
Claude Codeにシナリオ設計・locustfile.pyの実装・Web UIでの確認・ヘッドレス化を役割ごとに分けて任せます。Locustの負荷テストも他のPythonテストと同じ進め方で書けます。wait_timeと@taskの重みでシナリオの現実感を高めます。catch_responseでアサーションの精度を、--headlessとCSV/HTML出力でCIへの組み込みやすさを、それぞれ別の指示として詰めていくのが安定した進め方です。
Claudeにテストケースの生成そのものを自動で広げさせる別のアプローチとして、ClaudeとProperty-Based TestingでPythonのバグを見つける仕組みもあります。Locustが負荷という軸を探索するのに対し、Property-Based Testingは入力ドメインを探索する手法です。負荷試験と合わせて、テストの網羅性を広げる選択肢として参考になります。