Claude CodeでUnrealの自動テストを書き、ヘッドレスで回す手順
Unreal EngineのAutomation TestをClaude Codeに書かせ、UnrealEditor-Cmdでヘッドレス実行し、失敗を読ませて直させる流れを、権限設定とCLAUDE.mdの例つきで解説します。
Unreal EngineのAutomation Testは、コマンドラインから実行して終了コードと結果ファイルを取り出せます。この性質があるので、Claude Codeに「テストを書く、走らせる、失敗を読む、直す」までを任せる構成が作れます。
必要なのは3点です。テストを走らせる1本のコマンド、結果を書き出す場所、Claudeに渡す権限の範囲。この記事では、この3点をCLAUDE.mdとclaude -pでどう固めるかを順に説明します。
Automation Testはどんなテストで、Claudeに何を任せるか
Automation Testは、Unreal Engineの中でも低レベルの自動テストです。UObjectの仕組みの外にあるため、BlueprintやリフレクションからはAutomation Testは見えません。コードで書き、コンソールのコマンドラインか、OSのコマンドライン引数から実行します。
種類は2つあります。どちらもFAutomationTestBaseの派生クラスです。
| 種類 | 宣言マクロ | 向く用途 |
|---|---|---|
| Simple | 宣言マクロIMPLEMENT_SIMPLE_AUTOMATION_TEST | 向く用途単体テストや機能テスト。1つの検証で完結する |
| Complex | 宣言マクロIMPLEMENT_COMPLEX_AUTOMATION_TEST | 向く用途同じコードを多数の入力に流す。全マップのロードなど |
2つのマクロは、クラス名、画面に出る階層名(PrettyName)、EAutomationTestFlagsの組み合わせという同じ3引数を取ります。RunTestがtrueを返せば成功、falseなら失敗です。Complexだけは、入力の一覧を返すGetTestsも実装します。
この定型の多い構造が、Claudeに向いています。宣言マクロの形、Private/Testsという置き場所、[クラス名]Test.cppという命名の慣習は決まっているからです。ゲーム固有のロジックと、何を成功条件にするかは人が決めます。
Claudeに任せやすい仕事は次のとおりです。
- 既存クラスに対するSimple Testの雛形と検証コードを書く
- Complex Testの
GetTestsで入力を列挙する - 失敗したテストのログを読み、実装かテストのどちらが間違っているかを切り分ける
逆に、Editorを開いて目で確かめる種類の確認は、この方法では回りません。画面を使うテストを多く書くプロジェクトは、先にUnreal EngineのMCPをClaude Codeにつなぐ手順も確かめておくと見通しが立ちます。
Claudeに書かせる前に、回し方をCLAUDE.mdへ固定する
テストを書かせる前に、実行コマンドを決めます。コマンドが毎回変わると、Claudeは失敗の原因がテストなのか起動方法なのか分けられません。
コマンドラインからの実行は、EditorかClientの実行ファイルに引数を渡して行います。使う引数は次の4つです。
| 引数 | 動き |
|---|---|
-ExecCmds="Automation RunTest Test1+Test2;Quit" | 動きTest1とTest2を実行して終了する |
-ExecCmds="Automation RunTest MySet.MySubSet;Quit" | 動きMySet.MySubSet配下をすべて実行する |
-ExecCmds="Automation RunTest Group:MyGroup;Quit" | 動きグループMyGroupに絞って実行する |
-ReportExportPath="<出力先>" | 動き結果をJSONと関連するHTMLで書き出す |
この4つを組み合わせたCLAUDE.mdの断片を、次のように置きます。プロジェクト名とパスは例です。
## テストの回し方
- 実行は UnrealEditor-Cmd で行う。1回の起動に約30秒かかるので、
毎回全体ではなく、触ったテストの階層名だけを指定する
- コマンド:
UnrealEditor-Cmd.exe MyGame.uproject \
-ExecCmds="Automation RunTest MyGame.Inventory;Quit" \
-ReportExportPath="Saved/AutomationReports" -Unattended -NullRHI
- 結果は Saved/AutomationReports のJSONを読む
- テストの置き場は各モジュールの Private/Tests、ファイル名は [クラス名]Test.cpp
- Config/ 配下の ExcludeTest は変更しない起動に約30秒かかるという数字は、Epicが配布するUnreal Engine Skills for Claude Codeプラグインの説明にあります。-Unattended -NullRHIもそちらの例で使われている引数です。Epicの実行手順ページは、-ExecCmdsと-ReportExportPathだけを例に挙げています。
CLAUDE.mdには、Claudeがコードから読み取れない事柄だけを書きます。テストと型検査とlintのコマンドは、その代表です。長くすると毎回の注意を食うので、実行コマンドと禁止事項に絞ります。
Automation RunTestとAutomation RunTestsは、資料によって表記が分かれます。Epicの実行手順ページはRunTest、プラグインの例はRunTestsです。どちらを使うかは手元のバージョンで通ったほうに決め、CLAUDE.mdには通ったほうだけを書いてください。
C++のテストを書かせるときの指示と骨格
Simple Testの最小形は、Epicの例では次のとおりです。Claudeには、この骨格に沿って書かせます。
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FPlaceholderTest,
"TestGroup.TestSubgroup.Placeholder Test",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FPlaceholderTest::RunTest(const FString& Parameters)
{
// trueで成功、falseで失敗
return true;
}テストの形は、対象によって選び分けます。同じ検証を多数の入力に流したいときは、Complex Testにします。Epicの例は、プロジェクト内の全マップをロードするテストです。GetTestsが表示名とコマンドの配列を並べて返し、各コマンドがRunTestのParametersに渡されます。データ駆動の検証は、このParametersで表現します。
数フレームにまたがる処理は、RunTestの中でレイテントコマンド(Latent Command)を積んで書きます。マップのロードや、解像度変更を待つテストがこの形です。ADD_LATENT_AUTOMATION_COMMANDで積んだ処理は、フレームをまたいで順に実行されます。Claudeに頼むときは、待ちが要るかどうかを指示文に書いておきます。
指示文は、対象クラスと成功条件を具体的に書きます。次のような形です。
UInventoryComponent のスタック加算をテストして。
Private/Tests/InventoryComponentTest.cpp に Simple Test を1つ追加する。
階層名は MyGame.Inventory.AddStack。同じアイテムを2回加算したとき、
スロット数が増えず個数が合算されることを検証する。
書いたら CLAUDE.md のコマンドで MyGame.Inventory を実行し、
出力を貼ってから終わりにして。最後の1行が要です。Claudeは完了と判断した時点で止まります。確かめる手段が無ければ、「できたように見える」だけが根拠になります。コマンドと出力を求めておけば、誰も見ていない実行でも、読める証拠が残ります。
テストの中身は、Claudeが書いたものをそのまま信じません。return true;だけのテストは緑になりますが、何も守りません。バグの再現から先に書かせる手順は再現テストを先に書かせてバグを直す手順と同じ考え方で、赤くなるテストを先に見てから実装に進みます。
claude -pでテストの実行と修正を回す
手元で対話しながら進める代わりに、claude -pで非対話に回すこともできます。claude -pは終了コードで結果を返します。成功なら0、実行が失敗すれば非0です。スクリプトから分岐できます。
非対話の実行では、承認が必要な呼び出しは拒否されます。claude -pは、権限モードを指定しなければManualモードで始まります(v2.1.302以降)。テストを走らせて直させるには、必要な操作だけを許可します。
claude -p "MyGame.Inventory のテストを実行し、失敗があれば
実装を直して再実行して。最後に実行コマンドと結果を示すこと" \
--permission-mode dontAsk \
--allowedTools "Read,Edit,Bash(UnrealEditor-Cmd.exe *)" \
--output-format json | jq -r '.result'dontAskは、承認が必要になる呼び出しをすべて拒否するモードです。許可リストにある操作は動くので、リストが権限の全体になります。Bash(UnrealEditor-Cmd.exe *)のように書くと、実行ファイルを固定したまま引数だけを自由にできます。
結果の読み方も決めておきます。--output-format jsonにすると、結果の本文がresultフィールドに入ります。jq -r '.result'で取り出せます。レポートはJSONで書き出されるため、ClaudeにはSaved/AutomationReportsの中身を読ませます。
CIで同じことをしたい場合の権限設計やPR化は、CIのテスト失敗を自動修正する手順で扱っています。ここでは、Unreal固有の差分だけを押さえます。
- 1回の起動が重い。範囲を絞った階層名で回し、全体の実行は最後の1回に寄せる
- 途中で致命的な失敗が出たとき、
-ReportExportPathと-ResumeRunTestを組み合わせると、JSONを読んで最初の未実行テストから再開できる。実行中だったテストは失敗として記録される - テストを足したら、C++なので先にモジュールのビルドが要る
失敗を「直す」のではなく「隠す」方向へ逃がさない
Claudeが失敗を最短で消す方法は、2つあります。実装を直す方法と、テストを外す方法です。後者を止める仕組みを、先に入れておきます。
Unreal Engineには、テストを一時的に除外する仕組みがあります。DefaultEngine.iniか、プラットフォーム別の設定ファイルに+ExcludeTest=(Test="<テスト名>",Reason="<理由>",Warn=False)と書きます。除外されたテストは「Skipped」と表示されます。理由は情報イベントとして残ります。
つまり、失敗が止まらないとき、ここに1行足すだけで緑にできます。望ましい除外もありますが、Claudeが黙ってやってよい操作ではありません。次のように、設定ディレクトリへの編集を拒否します。
{
"permissions": {
"deny": ["Edit(Config/**)"]
}
}拒否ルールは許可ルールより優先されます。許可リストにEditが入っていても、Config/の編集は通りません。
もう1つの近道は、テスト本体を書き換えて通すことです。直す対象をテストファイルから外したいときは、Editの許可をパスで絞ります。たとえば実装ディレクトリだけを許可します。テストの修正が必要な失敗は、Claudeが提案して人が承認する流れにします。
つまずきやすい点
落とし穴
実行したのに、テストが1件も走っていない
階層名のスペルが違うと、対象が見つからずに終わることがあります。CLAUDE.mdの階層名と、IMPLEMENT_*_AUTOMATION_TESTの第2引数が一致しているかを確かめます。グループで絞るときは、DefaultEngine.iniの+Groups=(Name="Group1", ...)で名前を定義してから、Group:<名前>で指定します。
テストが緑なのに何も守っていない、という問題もあります。return true;だけのテストに加えて、検証関数を呼ぶだけで結果を見ていないテストも同じです。Claudeには、実装を一時的に壊して赤くなることを確かめさせます。
テストを書くたびに実行させたい場合は、フックで自動化する手もあります。仕組みはhooksでテストを自動実行する手順にあります。ただしUnrealでは1回の起動が重いので、保存のたびに回す設計には向きません。区切りの1回に絞るのが現実的です。
Editorを起動したまま、unreal-mcp経由でテストを回す方法もあります。この経路はUnreal Engine Skillsの3スキルとフックの使い分けで説明しています。ヘッドレスは起動が遅い代わりに、Editorが動いていない環境でも使えます。
まとめ
Automation Testは、コマンドラインで階層名を指定して実行でき、結果をJSONで書き出せます。この2点が、Claude Codeに回させる前提です。
CLAUDE.mdに実行コマンドを1本固定し、claude -pには許可する操作だけを渡します。Config/への編集は拒否し、完了の報告には実行コマンドと出力を求めます。この3つが揃えば、Claudeが失敗をテストの除外で隠す道は塞がります。最初の1本は、return true;ではなく、わざと壊して赤くなるテストから始めてください。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。