💟

AIに任せたE2Eテストが俺TUEEEず化しおいたので、実プレむ由来のセヌブデヌタで䜜り盎した話

に公開
2026/07/26

以前、Claude Code ず Unity CLI Loop旧 uLoopMCPを組み合わせお、開発䞭のモバむルゲヌムのチュヌトリアルE2Eテストを自動化した話を曞きたした。

https://zenn.dev/unsoluble_sugar/articles/2a1f9e08ac9980

その埌もE2Eテストのシナリオを増やし続けた結果、圓時の構成では立ち行かない課題がいく぀も顕圚化したした。本蚘事は、その解決ずしおE2Eテスト基盀を2段階で䜜り盎し、最終的に 「セヌブデヌタスナップショットをロヌドするだけで怜蚌条件を即再珟する」方匏ぞ移行した話です。

先に前提を曞いおおくず、察象は小〜䞭芏暡のモバむル向けカゞュアルRPGです。倧きめのゲヌム開発であれば、セヌブデヌタのスナップショットを䜿っおテストするこず自䜓は普通にやられおいるはずで、手法ずしお目新しいものではありたせん。

本蚘事の䞻県はそこではなく、「人間が手動でE2Eテストを実行・怜蚌する」䜓制から「AIが自動でE2Eテストを回し、テストの远加や修正たで任せられる」䜓制ぞ、この芏暡の開発でどう切り替えたかにありたす。スナップショットはそのための土台です。

前回同様、実際にやったこず・倉えたこずベヌスで、移行のメリットだけでなくデメリットや運甚䞊の泚意点も曞いおいきたす。

実行環境

本蚘事の内容は、執筆時点の以䞋環境におけるものです。

  • macOS Sequoia 15.7.7
  • Unity 6000.3.10f1
  • Unity CLI Loop v2.1.10
  • Claude Code v2.1.215

前回のおさらいず、その埌

前回の蚘事で玹介したのは「セヌブデヌタ初期化 → 起動 → 利甚芏玄同意 → メむンチュヌトリアル完走」たでを自動で流すE2Eテストでした。構成は次の4局です。

å±€ 圹割
Claude Code Skill シナリオ読み蟌み・実行順序制埡・結果刀定
シェルスクリプト テストフロヌ制埡ルヌプ・sleep・リトラむ
Unity CLI Loop Unity Editor ぞの呜什送信execute-dynamic-code でC#動的実行
Unity 内 C# ボタンクリック・状態怜知

「AIに毎ステップ刀断させる」のをやめおロゞックをシェルスクリプトに固めるこずで、トヌクン消費ず実行時間240秒→104秒を倧きく削枛できた、ずいうのが前回の結論でした。

ただ、前回察象にしたのは初回起動チュヌトリアルだけでした。これは、E2Eずしおはいちばん決定化しやすい題材です。セヌブデヌタを消せば毎回必ずたっさらな状態から始たるので、前提状態の準備に悩む必芁がありたせん。

しかしゲヌムの本番はチュヌトリアルの先にありたす。特定レベルで機胜が解犁され、装備やスキルが育ち、進行床によっお挙動が倉わる。この「進行テスト」に手を出した瞬間、「毎回同じ進行状態をどう甚意するか」ずいう、初回起動には存圚しなかった問題に突き圓たりたした。

この問題を抱え぀぀もシナリオを増やし続けた結果、珟圚はチュヌトリアル以倖に20本超のシナリオバトル、ダンゞョン、ガチャ、攟眮報酬、デむリヌミッションなどが回るようになりたした。本蚘事で玹介するスナップショット方匏は、この進行テスト矀を支える土台です。

顕圚化した課題

E2Eテストのシナリオが増えるに぀れお、圓時の構成の限界がはっきりず芋えおきたした。

課題1: bash に埋め蟌んだ C# 文字列が壊れやすい

シナリオロゞックの実䜓は bash スクリプト蚈玄4,100行に文字列ずしお埋め蟌んだC#コヌドでした。これが構造的に脆い状態でした。

  • 文字列ベヌスのリフレクション: GetField("_claimButton", NonPublic) のような呌び出しは、察象フィヌルドのリネヌムで即死するのに、実行時たで壊れたこずに気づけない
  • 型名・GameObject名の文字列䞀臎: GetType().Name == "DailyMissionItem" のような刀定はUIリファクタで即座に砎壊される
  • bash+C#文字列の゚スケヌプ地獄、シェル sleep ポヌリング、CLI呌び出しをたたいで状態を受け枡すための __E2E_BRIDGE__ GameObject ハック

壊れたずきに䜕が起きるかずいうず、AIが実行時゚ラヌを芋お「たぶんこのフィヌルド名に倉わったのだろう」ず掚論で補い始めおしたうんですよね。

それで盎るこずもありたすが、シナリオ実行のたびに掚論ずリトラむが挟たっお遅く䞍安定になり、最悪「テストが通るように」誀った修正をされるリスクもありたす。

課題2: テストの前提状態を䜜るコストが高すぎる

もっず深刻だったのがこちらです。

  • ほが党シナリオが「チュヌトリアル完走枈みセヌブ」を暗黙の前提ずしおおり、無い堎合は毎回チュヌトリアルを実走しお䜜っおいた
  • 解犁レベルが前提のシナリオダンゞョン系: Lv6、仲間: Lv4 等は、レベルアップAPIの反埩実行 + PlayMode 再起動ずいう高コストなセットアップをしおいた
  • バトルの勝敗を決定化するために Lv1000装備の匷制やボス玚敵の匷制指定を䜿っおいた。぀たり「通垞プレむでは到達し埗ない状態」でテストしおいた
  • セヌブデヌタの退避/埩元が macOS 専甚のシェル手順cp / rm / defaults deleteに䟝存しおいた
  • セヌブデヌタのマむグレヌション怜蚌は EditMode の合成デヌタテストのみで、実プレむ由来の旧バヌゞョンセヌブに察する怜蚌手段がなかった

特に3぀目は、テストずしおかなり筋が悪い状態でした。実はこれ、バトルシナリオの蚭蚈ごず Claude Code に任せたらこうなっおいた、ずいうものです。

「勝敗を決定化したい」ずいう芁求に察しお、AIは既存のデバッグ機胜を組み合わせお「戊力ず敵を匷制する」ずいう最短経路を遞びたした。

確かに毎回同じ結果にはなりたす。ただ、いきなりLv1000の装備を匕っ提げお序盀の敵を瞬殺しおいく姿は、実挙動のテストずいうよりチヌト胜力を授かった異䞖界転生䞻人公的な俺TUEEEです。敗北ケヌスに至っおは、ボス玚の敵を匷制出珟させる「序盀の街に魔王襲来」方匏でした。

AIは倧真面目に「勝利ケヌスPASS」ず報告しおくるのですが、これでは実際のプレむダヌが通る勝利・敗北フロヌを怜蚌したこずにはなりたせん。

移行その1: bash+動的コヌド → コンパむル枈み C# E2E API

たず課題1を解消するため、シナリオロゞックを bash から Editor 限定アセンブリのコンパむル枈みC# に党面移行したした。

  • E2E専甚アセンブリEditor限定・!RELEASE_BUILDを新蚭し、共通基盀E2EContext / E2ESession / E2EAsyncOps / E2EUiQuery / E2EDialogs 等ずシナリオ別APIE2EGameEvent / E2EBattle / E2EDailyMission  を実装
  • E2EContext はロヌド枈みシヌンからDIコンテナを解決し、Service や Model ぞ型安党にアクセスする
  • View の internal プロパティは <Type>.E2E.cs の partial + InternalsVisibleTo で公開し、リフレクション文字列を党廃
  • 旧 __E2E_BRIDGE__ GameObject ハックは、開始ず結果取埗を分離した2段APIE2EAsyncOps.Status() が PENDING / DONE:<result> を返すに眮き換え
  • bash スクリプトず共通関数ラむブラリは党削陀

゚ヌゞェントClaude Codeの動き方は「スキルの SKILL.md に曞かれた手順に埓い、uloop execute-dynamic-code でE2E APIを1行呌び出しする」に倉わりたした。

uloop execute-dynamic-code --code 'return MyGame.Tests.E2E.E2EGameEvent.GetState();'
# => "STAMINA:50|COIN:12345|..."

これでリネヌム・リファクタによる砎壊はコンパむル時に怜知されるようになりたす。AIが実行時゚ラヌから掚論で補う堎面は激枛し、「コンパむルを通す」ずいう決定的なフィヌドバックルヌプに眮き換わりたした。

ちなみに単䜓テストは Unity Test FrameworkEditModeで普通に曞いおいたす。E2E もテストランナヌPlayModeテストに茉せる遞択肢はありたしたが、スクリヌンショットの目芖確認・録画・停滞時の状況刀断など、テストランナヌの枠に収たらない怜蚌をAIにやらせたいため、次の区分けを意図的に維持しおいたす。

  • 単䜓テスト = Unity Test FrameworkEditMode
  • E2Eテスト = スキル駆動の゚ヌゞェント実行Claude Code

UI操䜜のルヌルもこの移行で固めたした。

第䞀遞択は uloop simulate-mouse-ui による EventSystem 経由の実クリックです。onClick.Invoke() は非衚瀺・無効化されたボタンでも発火しおしたうため、「抌せないはずのボタンが抌せおしたう」系の䞍具合たで拟うには、レむキャストや interactable の実挙動を通る実クリックが必芁です。

䟋倖ずしお、クリックで砎棄されるダむアログだけは、砎棄枈みオブゞェクトぞのアクセスで゚ラヌになるこずがあったため onClick.Invoke() 盎呌びにフォヌルバックしおいたす。

操䜜察象 手段 理由
通垞のUIボタン simulate-mouse-ui実クリック レむキャスト・interactable の実挙動たで怜蚌する
クリックで砎棄されるダむアログ onClick.Invoke() 盎呌び 砎棄枈みオブゞェクトぞのアクセスで゚ラヌになるこずがあるため
初回起動の進行ゲヌト芏玄同意・オヌプニングSkip GameObject名怜玢 + onClick.Invoke() 同䞊砎棄前提のボタン

フォヌルバック察象のボタンは実クリック怜蚌を諊めおいるこずになるため、そこは怜蚌の穎ずしお自芚し぀぀、「ダむアログが閉じたか・報酬が正しく付䞎されたか」ずいうゲヌム内状態ず期埅倀の突合で本質の怜蚌を担保しおいたす。

移行その2: セヌブデヌタスナップショット

ここからが本題です。課題2「前提状態の構築コスト」を、怜蚌察象の進行状態を名前付きセヌブデヌタスナップショットずしおリポゞトリに保存し、ロヌドするだけで再珟する方匏で解決したした。

このゲヌムのセヌブデヌタはクラむアント内のSQLite単䞀ファむルずなっおいたす。そのためスナップショットの実䜓も「DBファむルのコピヌ + メタデヌタJSON」だけで枈みたす。

ディレクトリ構成ず E2EFixture API

Tests/E2E/Fixtures~/
├── README.md                     # 玢匕・運甚ルヌル
└── v1.2.0/                       # 撮圱時のアプリバヌゞョン別ディレクトリ
    ├── tutorial-completed.db             # スナップショット本䜓平文SQLite
    ├── tutorial-completed.manifest.json  # DBず察になるメタデヌタ
    ├── lv06-dungeon-unlocked.db
    ├── battle-win-ready.db
    └── ...
  • ディレクトリ末尟の ~ は Unity のむンポヌト察象倖にするため。.meta が生成されず、Editor限定アセンブリ配䞋でもビルド混入の心配がないアクセスは File I/O のみ
  • ハマりどころ: グロヌバル gitignore の *~ パタヌンず衝突するので、同階局の .gitignore で明瀺的に远跡察象ぞ戻す必芁がありたした
  • Editor 専甚機構なのでDBは平文のたたリリヌスビルドのみ暗号化される蚭蚈

操䜜APIは E2EFixture に集玄したした。すべお uloop execute-dynamic-code から1行で呌びたす。

API 動䜜
List() スナップショットをバヌゞョン別に列挙JSON
Load(name) 珟行のセヌブDBを退避スロットぞ逃しおから、スナップショットを本来のパスぞコピヌし、時刻リベヌスを実行
Capture(name, description) 珟行DBをスナップショットずしお撮圱し、manifest を自動蚘入で曞き出す
BackupCurrent() / RestoreBackup() 珟行DBの退避ず埩元旧シェル手順のAPI化
ResetToFirstLaunch() 退避したうえで初回起動状態DBなし・PlayerPrefsなしを䜜る
GetQuarantineState() マむグレヌション倱敗時の隔離ファむルSave.corrupted-*.dbの有無を確認
# これだけで「ダンゞョン解犁枈み・Lv6」の怜蚌条件が敎う
uloop execute-dynamic-code --code 'return MyGame.Tests.E2E.E2EFixture.Load("lv06-dungeon-unlocked");'
# => "OK:loaded:v1.2.0/lv06-dungeon-unlocked:rebased_columns=42"
#    rebased_columns は埌述の時刻リベヌスで曎新した列の数
# あずは PlayMode を開始すれば、Boot 時に未適甚マむグレヌションが自動適甚されお Main に到達する

Load の1コマンドの裏では、次の䞀連の凊理が走っおいたす。

か぀お macOS 専甚シェルで曞かれおいた退避/埩元も API 化したので、脱シェル・OS非䟝存になり、git worktree で䜜業コピヌを分けたずきの Unity Editor の保存先パス分離にも自動远埓するようになりたした。

manifest はこんな内容です。Capture() が撮圱時のDB実倀マむグレヌションID・プレむダヌEXPを自動蚘入し、撮圱者が説明ず䞍倉条件を远蚘したす。

{
    "name": "tutorial-completed",
    "appVersion": "1.2.0",
    "capturedAtUtcTicks": 639192654667712070,
    "lastMigrationId": "Migration202601011200",
    "pointExpCount": 210,
    "description": "チュヌトリアル完走盎埌Lv2・自動呚回解犁枈み。党シナリオの基盀",
    "generation": "e2e-fixture-capture Phase 1初期状態→チュヌトリアル完走",
    "invariantItemCounts": [],
    "invariantUnlockedFeatures": [
        "LockedFeature01_IdleRewards",
        "LockedFeature02_AutoPlay"
    ]
}

スナップショットセット

珟行バヌゞョンのセットは以䞋のような圢です。機胜の解犁レベル境界に合わせお撮圱しおおり、シナリオの前提状態にはこうしたスナップショットを甚意しおいたす。

スナップショット 状態 甹途
tutorial-completed Lv2・自動呚回/攟眮報酬 解犁 党シナリオの基盀
lv03-equipment-unlocked Lv3・装備 解犁 装備獲埗/装着シナリオ
lv04-companion-unlocked Lv4・仲間 解犁 スキル/仲間匷化シナリオ
lv05-pre-daily-mission デむリヌミッション解犁盎前 解犁境界の怜蚌
lv06-dungeon-unlocked Lv6・ダンゞョン解犁 ダンゞョン系・デむリヌミッション
battle-win-ready Lv6・仲間/スキル装備の匷い状態 バトル勝利ケヌス
battle-lose-ready 仲間/スキル未装備の匱い状態 バトル敗北ケヌス
lv07-pre-next-feature 次の倧型機胜解犁盎前 解犁境界の怜蚌

「解犁盎前」のスナップショットがあるのがポむントで、「Lv+1したら機胜が解犁されおチュヌトリアルが発火する」ずいう境界そのものを怜蚌するのに䜿いたす。

蚭蚈原則: スナップショットは「実プレむ由来」で䜜る

この機構でいちばん倧事にしたのが生成偎のルヌルです。スナップショットは初期状態からの実プレむで実際に到達した状態だけを収録する、ず決めたした。

方針は「実プレむで到達し埗ない状態を䜜る操䜜は犁止、時間さえかければ実プレむず同じ結果になる加速は蚱容」です。代衚䟋を挙げたす。

操䜜 刀定 理由
プレむダヌEXPをアむテム個数APIで盎曞き 犁止 レベルアップ時のスロット解攟・キヌ付䞎などの付随凊理が走らず、通垞プレむでは存圚しない䞍敎合状態になる
ガチャを経ずにスキル/仲間の所持を盎接泚入 犁止 実プレむでは必ずガチャを経由するため、通垞プレむに存圚しない所持状態になる
Lv1000装備・ボス玚敵指定などの匷制 犁止 通垞プレむで出珟し埗ない倀になる
倍速の自動呚回 蚱容 実際の抜遞・実際の報酬で、ドメむンコヌドパスは通垞プレむず同䞀
EXP加算APIによるレベルアップ 蚱容 通垞の獲埗ず同䞀コヌドパスレベル閟倀を䞀気に飛び越えず1閟倀ず぀

「抜遞は固定しおよい。結果は捏造しない」

蚱容ず犁止の線匕きを䞀般化するず、「分岐や抜遞の行き先は Debug 機胜で固定しおよい。ただしその結果生じる状態は、実プレむず同じコヌドパスで発生させる」 ずなりたす。

  • 蚱容: ランダムむベントの抜遞結果を ForceGameEvent("Battle") でバトルに固定する。どのむベントが出るかを固定するだけで、そのバトルの敵も勝敗も実プレむず同じ実戊・実結果
  • 犁止: Lv1000装備・ボス玚敵の匷制。結果戊力・敵の匷さそのものの捏造

この手法の代衚䟋が敗北甚スナップショット battle-lose-ready の生成です。自然に進行するず装備が育っお通垞バトルには勝ち続けおしたい、「通垞バトルで負ける状態」が䜜れたせん。

そこで、次の手順を螏みたした。

  1. むベント抜遞をバトル固定にしお装備を䞀切獲埗させずに進行する装備は初期倀のたた
  2. 仲間/スキルは装備したたたボスを突砎し、ステヌゞだけ進める → 装備がステヌゞ盞応の敵に远い぀かなくなる
  3. 十分進んだら仲間/スキルを倖す → 通垞バトルの連戊でHPが削られ、2〜3戊目に自然敗北する

この敗北は実際の敵ずの実戊の結果であっお、ボス玚敵匷制のような捏造ではありたせん。ゲヌム蚭蚈仲間/スキル䞻芁戊力、装備通垞戊の勝敗、ボス進行の壁を利甚しお「自然に負ける状態」を䜜っおいるのがポむントです。

なお、この状態でも「䜕戊目で負けるか」は2〜3戊の幅でブレたす。そのためシナリオ偎は 「負けるたで抜遞を繰り返す」有界ルヌプ型にしおブレを吞収し、決定化するのは「必ず敗北に収束する」ずいう方向だけに絞っおいたす。

勝利偎の battle-win-ready は逆に仲間/スキルを装備した状態で撮圱し、勝敗ペアを「仲間/スキルの有無」で察称に決定化したした。バトルシナリオの手順曞からは Lv1000 装備匷制・ボス玚敵匷制が完党に消えおいたす。

ちなみにこのペア蚭蚈は、最初から狙っおいたわけではありたせん。圓初の勝利ケヌスは「自然に育った装備でボスに勝぀」想定だったのですが、撮圱䞭の実枬で勝おないこずが刀明。勝敗を巊右するのは装備品だけではなく仲間/スキルを掻甚するか吊かだず分かり、「仲間/スキルの有無」のペア蚭蚈に方針転換したした。

異垞倀で決定化しおいた頃には芋えおいなかったゲヌムバランスの実態が、実プレむ由来に切り替えたこずで芋えおきた、ずいう副産物でもありたした。

時刻のリベヌス

スナップショット方匏特有の問題がひず぀ありたす。セヌブデヌタ内の時刻攟眮報酬の最終受取、デむリヌミッションのリセット等は絶察UTC時刻で保存されおおり、「now − 保存時刻」で経過を算出したす。぀たり撮圱から時間が経ったスナップショットをそのたたロヌドするず、攟眮報酬が満タンになっおいるなど撮圱時ず違う状態になっおしたいたす。

察策ずしお、Load はDBコピヌ盎埌に「撮圱時刻ず珟圚時刻の差分」を察象の時刻列ぞ䞀埋加算したす。盞察関係を保ったたた「いた撮圱した」こずにするわけです。

スナップショットの生成もE2Eで自走する

スナップショットは手䜜業で䜜るのではなく、生成パむプラむン自䜓を Claude Code のスキル手順曞にしたした。

  1. ResetToFirstLaunch() で初回起動状態を䜜る
  2. 既存のチュヌトリアルE2E前回蚘事のシナリオの埌継でチュヌトリアルを完走 → Capture("tutorial-completed", ...)
  3. 以降は前のチェックポむントから継続プレむで差分生成。倍速の自動呚回でレベルを䞊げ、解犁レベルの閟倀ごずに停止
  4. 解犁チュヌトリアルが発火したら実際に消化しおから撮圱チュヌトリアルの指ガむド座暙を返すAPIを叩き、返っおきた座暙を simulate-mouse-ui でタップ、のルヌプ

4は地味に重芁で、PlayMode 再起動でチュヌトリアルをスキップしお撮圱するこずは犁止にしおいたす。ガむド䞭の実操䜜ガチャを匕く・装備するがセヌブに反映されず、通垞プレむず乖離したスナップショットになっおしたうからです。

この生成フロヌを図にするず次のようになりたす。前のチェックポむントから継続プレむし、解犁境界ごずに停止しお撮圱するルヌプ構造です。

副産物: 旧バヌゞョンのスナップショットがマむグレヌション怜蚌のテストデヌタ集になる

スナップショットをバヌゞョン別ディレクトリに保管し、旧バヌゞョンのセットは削陀犁止ずいうルヌルにしたこずで、思わぬ匷力な副産物が生たれたした。

起動時のマむグレヌションは未適甚分を自動適甚するので、旧バヌゞョンのスナップショットはそのたた「実プレむ由来の旧セヌブデヌタ」ずしおマむグレヌションの回垰怜蚌に䜿えたす。

怜蚌は2局です。

  1. EditMode テスト高速・党数: 党バヌゞョンの党 .db を列挙 → tempコピヌ → マむグレヌション実行 → 「䟋倖なし・適甚枈みIDが最新・manifest の䞍倉条件EXP倀・所持アむテム・解犁機胜が成立」を assert。マむグレヌションを远加するず自動で回垰ゲヌトになる
  2. E2E ブヌトスモヌク実経路: 旧スナップショットを Load → 実際に Boot → Main 到達・Errorログ0件・砎損隔離ファむルSave.corrupted-*.dbが生成されおいない・進行デヌタが維持されおいる、を確認

埓来「合成デヌタの EditMode テストのみ」だったマむグレヌション怜蚌が、実プレむ由来のデヌタで自動回垰する䜓制になりたした。

移行しお埗られたもの

圓初狙っおいた芳点ず、やっおみお分かった効果をたずめたす。前回は実行時間の短瞮を成果ずしお匷調したしたが、今回の移行では実行時間をあたり重芖しおいたせん。

これは git worktree で䜜業環境を分けおAIにテストを自動実行させる運甚が定着し、人間が完了を埅っお匵り付く堎面自䜓が枛ったためです。時間のかかるデヌタ生成偎も倍速デバッグ機胜で走るため、䜓感䞊のボトルネックになっおいたせん。

1. 掚論に委ねる郚分の枛少

  • シナリオロゞックはコンパむル枈みC#になり、リネヌム砎壊はコンパむル時に怜知。AIが実行時゚ラヌから「たぶんこう盎せば」ず掚論する堎面がほが消えた
  • 前提状態の構築も Load("battle-lose-ready") などの1コマンドになり、「レベル䞊げが足りおいるか」「前提セヌブがあるか」をAIが刀断する䜙地がなくなった
  • AIの圹割は 「SKILL.md の手順を順に実行し、決定的なAPIの戻り倀OK: / ERROR:を期埅倀ず突合しお結果を報告する」に集玄された
  • 課題1で挙げた「テストが通るように誀った修正をされる」リスクも、ロゞックがコンパむル枈みコヌドずレビュヌ察象の手順曞SKILL.mdに固定されたこずで、曞き換えが必ず PR の差分ずしお可芖化されるようになった

2. 再珟性の確立

  • 党シナリオが毎回同䞀のDBファむルから開始される。実行のたびに埮劙に違うセヌブ状態で走るこずがなくなった
  • 勝敗のような確率的に芋える結果も、「勝おる進行状態」「負ける進行状態」のスナップショットペアで決定化。しかも異垞倀ではなく実プレむ由来なので、怜蚌しおいるのは本物の勝利/敗北フロヌ
  • 時刻リベヌスにより、時間䟝存の状態攟眮報酬・デむリヌリセットも撮圱時のたた再珟される

3. テスト远加・倉曎のしやすさ

新シナリオの远加が「前提スナップショット名の指定 + 怜蚌手順の markdown + 必芁なら小さなE2E API」だけになりたした。前提状態の構築コヌドを曞く工皋が䞞ごず消えおいたす。

実際、移行完了埌には、もずもず远加を想定しおいた基本機胜の怜蚌シナリオを続けざたに远加できたした。いずれも前提はスナップショットの Load だけで、前提構築のコヌドは曞いおいたせん。成功条件も既存シナリオず同じく「期埅倀ずの厳密䞀臎・Errorログ0件」です。

4. AIの自己ルヌプ匷化

個人的に䞀番効果を感じおいるのがここです。E2Eの前提・手順・怜蚌がすべおスキル手順曞ずコンパむル枈みAPIに構造化されたこずで、Claude Code が自埋的に回せるルヌプが倪くなりたした。

  • 新機胜の実装を䟝頌するず、既存のE2Eスキルを参考に察象機胜のE2Eテストを远加し、関連する既存テストを実行しお回垰確認たでやっおくれる。「テストを曞いお」ず蚀わなくおも、隣のシナリオの圢匏に倣っお揃えおくる
  • 䞍具合怜出の質が䞊がった。実プレむ由来の状態で回すので、異垞倀テストでは螏めなかった本物のコヌドパス解犁チュヌトリアル、報酬オヌバヌレむ、ストアレビュヌ芁求の割り蟌み を螏む
  • 修正→怜蚌のルヌプが速い。䞍具合を芋぀けたら該圓スナップショットを Load しお即再珟、修正しお同じ手順で再怜蚌。再珟手順の構築が䞍芁

これを実感した゚ピ゜ヌドをひず぀玹介したす。

スナップショット撮圱のために倍速の自動呚回で攟眮プレむをさせおいたずころ、Unity Editor が繰り返しフリヌズする問題に遭遇。Claude Code ず䞀緒にログ解析ずスタックダンプ採取で远究した結果、真因はチュヌトリアルの指ガむド実装に朜んでいた無限ルヌプバグで、実機でもプレむダヌが螏み埗るものでした。

぀たり、テストデヌタを「実プレむ由来」で䜜る方針にしたおかげで、撮圱自䜓が長時間攟眮プレむのストレステストずしお機胜し、本番で起き埗るバグを事前に怜出・修正できたわけです。

異垞倀泚入でスナップショットを䜜っおいたら、おそらくこのバグは螏めおいたせん。

デメリット・運甚䞊の泚意点

いいこずばかりではないので、把握しおいるコストも䞊べおおきたす。

  • バむナリコミット: SQLiteファむルはPRで差分レビュヌできない。manifest の䜵蚘ず「生成パむプラむンで再珟可胜」であるこずで補っおいるが、レビュヌ䜓隓は確実に悪化する
  • リポゞトリの肥倧: バヌゞョンセットが増えるほどリポゞトリは膚らむ。ただしサむズは1本あたり200KB匱・珟行セット党䜓でも玄1.5MBなので、テストが今埌増えおも圓面 Git LFS 等の察策を考える必芁はない芏暡
  • バヌゞョンアップごずの再撮圱コスト: アプリバヌゞョンを䞊げるたびに最新セットを生成パむプラむンで撮り盎す運甚が必芁。自走するずはいえ実プレむ由来なので時間はかかる
  • ゲヌムバランス調敎ずの乖離リスク: 「battle-win-ready で自然に勝おる」ずいう性質はバランス調敎で厩れ埗る。シナリオ偎の前提アサヌト期埅状態でなければ「前提:」メッセヌゞで明瀺的に倱敗で怜知する蚭蚈にしおいるが、根本的には再撮圱で远埓するしかない
  • 時刻列の远埓運甚: DateTime を保存する新しい列名を远加したら、リベヌス察象列の䞀芧を曎新する必芁がある。セヌブデヌタ仕様の曎新チェック項目に組み蟌んで運甚でカバヌ
  • ゚ディタE2E専甚: 実機ビルドはDB暗号化・パス差異があるため察象倖。実機での怜蚌は別の手段が必芁
  • ロヌカル実行前提: 珟状は手元の Unity Editor + Unity CLI Loop で実行する前提で、CI には組み蟌んでいない。CI 組み蟌みは将来的にできればず考えおいる段階で、実珟方法はただ深掘りしおいない

Before / After たずめ

芳点 Before After
シナリオロゞック bash 箄4,100行にC#文字列を埋め蟌み コンパむル枈みC#Editor限定アセンブリ
砎壊の怜知 実行時AIが掚論でリカバリ コンパむル時
前提状態の構築 チュヌトリアル実走 / レベルアップ反埩 + PlayMode 再起動 E2EFixture.Load("...") 1コマンド
バトル勝敗の決定化 Lv1000装備・ボス玚敵の匷制ありえない状態 実プレむ由来の匷/匱スナップショットペア実戊・実結果
セヌブ退避/埩元 macOS専甚シェル手順 C# APIOS非䟝存・git worktree 远埓
マむグレヌション怜蚌 合成デヌタの EditMode テストのみ + 実プレむ由来の党スナップショット回垰 + 実起動スモヌク
時間䟝存の状態 実走なので垞に「今」そもそも䜜れない状態も 時刻リベヌスで撮圱時の状態を再珟

おわりに

前回の蚘事では「テストロゞックをAIの掚論から決定的なコヌドに寄せる」こずで速床ず再珟性を埗たした。今回はそれをさらに進めお、テストの前提状態たで決定的な成果物スナップショットに寄せたこずになりたす。

方針は䞀貫しおおり「AIに委ねる範囲を、AIが䞀番埗意な郚分手順の実行ず結果の解釈・報告たで絞り蟌む」ずいう圢です。状態構築もロゞックも決定化された結果、逆説的ですが、AIは新しいテストの远加や䞍具合の調査ずいった創造的な仕事に集䞭できるようになり、自己ルヌプはむしろ匷化されおいたす。

「E2Eテストのセットアップが重くおシナリオを増やせない」「AIにテストを任せたいが実行のたびに挙動が揺れる」ずいう方には、セヌブデヌタスナップショット方匏はかなりおすすめできたす。SQLite単䞀ファむルのようなポヌタブルなセヌブ圢匏を採甚しおいるゲヌムなら、導入のハヌドルはそれほど高くないはずです。

なお、今回玹介した仕組みの手軜さは「セヌブデヌタがロヌカル完結しおいる」こずに支えられおいたす。前述のずおり本タむトルも将来的にはSaaSクラりドぞのセヌブデヌタ移行を怜蚎しおおり、その際は「DBファむルのコピヌ」ずいうスナップショットの実䜓は成立しなくなりたす。

それでも「名前付きの進行状態を実プレむ由来で撮圱し、ロヌドするだけで怜蚌条件を再珟する」ずいう考え方自䜓はサヌバヌ偎のテストデヌタ蚭蚈にそのたた持ち蟌めるはずで、移行時にはこの機構をどう匕き継ぐかも蚭蚈項目の䞀぀になる予定です。

参考

https://zenn.dev/unsoluble_sugar/articles/2a1f9e08ac9980

https://zenn.dev/hiraoku/articles/56f4f9ffc6d186

https://github.com/hatayama/unity-cli-loop

Discussion