roblox-testing について
roblox-testingは、Roblox開発者向けのテストとデバッグのガイドです。問題の再現方法やクライアントとサーバーの切り分け、ログ・テスト・プロファイリングツールの選び方を説明し、メモリ、ネットワーク、スクリプト性能などの問題を扱います。
主な機能
- 01Developer Consoleでログと実行状態を確認
- 02TestEZによる単体テストとLuau型チェック
- 03Studioデバッガーとエラースタックトレース
- 04MicroProfilerとScript Profilerによる性能分析
- 05メモリスナップショットとScene Analysis
- 06ネットワークシミュレーションと問題診断
活用シーン
- 01障害がクライアント側かサーバー側かを特定
- 02TestEZでモジュールの動作と型を確認
- 03スクリプトCPUのボトルネックやフレーム時間の急増を特定
- 04メモリスナップショットを比較してリークの可能性を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nonlooped/ roblox-suite --skill roblox-testingインストール後、Claude Code で/roblox-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/common-bugs-and-fixes.mddebugging-tools.mdnetwork-simulation.mdperformance-profiling.mdtesting-patterns.mdscripts/DebugDraw.luaLogger.luaTestRunner.lua詳細
roblox-testing
公式ソース:
- https://create.roblox.com/docs/en-us/studio/developer-console
- https://create.roblox.com/docs/en-us/performance-optimization/identify
- https://create.roblox.com/docs/en-us/performance-optimization/microprofiler
- https://create.roblox.com/docs/en-us/performance-optimization/scene-analysis
- https://create.roblox.com/docs/en-us/studio/optimization/memory-usage
- https://create.roblox.com/docs/en-us/studio/optimization/scriptprofiler
まず再現可能な障害から始め、それがクライアント、サーバー、または両方で発生するかを特定します。その根拠に基づいて、デバッグツールまたはプロファイリングツールを選びます。
デバッグの心構え
- 再現します。 再現できなければ、修正できません。
- 切り分けます。 バグが消えるまでシステムを取り除きます。最後に取り除いたものが原因です。
- 測定します。 推測ではなく、ツールを使います。
- 一度に一つだけ修正します。 修正を検証し、可能であれば回帰テストを追加します。
- 慎重にログを記録します。 適切なログは、後のデバッグを効率化します。
ログ記録のルール
print、warn、errorを意図的に使います。
printは通常の診断に使います。warnは、気づく必要がある回復可能な問題に使います。errorは、実行を停止すべきプログラミングエラーに使います。
ログメッセージにはコンテキストを含めます。
warn(string.format("[DataStore] Save failed for %d: %s", userId, tostring(err)))
シークレット、プレイヤーデータ、個人を特定できる情報(PII)のログ記録は避けてください。
pcallとアサーション
失敗する可能性のある呼び出し、特にクラウドサービス、HTTP、DataStore、Marketplaceへの呼び出しはラップします。
local ok, result = pcall(function()
return someService:DoSomething()
end)
if not ok then
warn("DoSomething failed:", result)
end
決して失敗してはならない内部不変条件にはassertを使います。
assert(config.MaxSpeed > 0, "MaxSpeed must be positive")
Developer Console
ゲーム内またはStudioのプレイモードでF9を押して開きます。
タブ:
- Log:クライアント/サーバーの出力、エラー、警告。
- Memory:カテゴリ別のメモリ使用量。
- Network:HTTPおよびサービスのリクエスト。
- Server Stats:ハートビート、ping、データping。
- Script Profiler:スクリプトのCPU使用量を記録します。
- MicroProfiler:サーバーダンプをキャプチャします。
Client/Serverビューを切り替えて、どちら側から出力されたかを確認します。
TestEZによるユニットテスト
Robloxの標準テストフレームワークはTestEZです。ネストされたdescribe/itブロック、ライフサイクルフック、非同期テスト、豊富なマッチャーAPIに対応しています。
TestEZの例:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TestEZ = require(ReplicatedStorage.DevPackages.TestEZ)
local MathUtils = require(ReplicatedStorage.MathUtils)
describe("MathUtils", function()
it("clamps values", function()
expect(MathUtils.clamp(5, 0, 10)).to.equal(5)
expect(MathUtils.clamp(-1, 0, 10)).to.equal(0)
expect(MathUtils.clamp(11, 0, 10)).to.equal(10)
end)
end)
TestEZ.TestBootstrap:run({ game.ReplicatedStorage.Tests })
Studio内のレガシーテストでは、TestService:Error(msg)を使ってStudioの出力に失敗を明示できますが、ほとんどのプロジェクトではTestEZを優先してください。scriptsフォルダーには、TestEZが利用できない環境向けの最小限のTestRunner.luaシムのみが含まれています。
Luauの型チェック
Luau言語サーバー(luau-lsp)またはStudioのLuau型チェッカーを使って、実行前にエラーを検出します。
ベストプラクティス:
- モジュールのエクスポートと純粋関数に型注釈を追加します。
- CIまたはコミット前に
luau-lsp analyzeを実行します。 - 新しいモジュールでは
--!strictを有効にします。 - 型付けされていないエンジンAPIに過剰な型付けをせず、境界でのキャストを優先します。
Studio Debugger
Studioに組み込まれているデバッガーを使って、ステップ実行でデバッグします。
- 行番号の横にあるガターをクリックしてブレークポイントを設定します。
- Playモードで実行し、デバッガーの操作ボタンを使って、ステップオーバー、ステップイン、ステップアウトを行います。
- デバッガーパネルで、コールスタック、ローカル変数、アップバリューを確認します。
- Studioでクライアントとサーバーの両方を実行している場合、デバッガーは両方のコンテキストで機能します。
xpcallとdebug.traceback
xpcallとdebug.tracebackを使って、失敗時の完全なスタックトレースを取得します。
local ok, err = xpcall(function()
riskyOperation()
end, debug.traceback)
if not ok then
warn(err)
end
これは、pcallだけではスタックが失われる、トップレベルのエントリーポイント、スケジュールされたコールバック、接続ハンドラーで特に役立ちます。
接続のクリーンアップ
RBXScriptConnectionオブジェクトのリークは、メモリリークや古い状態の一般的な原因です。明示的なクリーンアップパターンを優先してください。
- Maid/Janitor形式のコレクターを使います。
local Maid = require(path.to.Maid)
local maid = Maid.new()
maid:GiveTask(workspace.ChildAdded:Connect(onChild))
maid:GiveTask(RunService.Heartbeat:Connect(onStep))
maid:Destroy() -- disconnects everything
- 1回限りのイベントハンドラーには
:Once()を使います。 - インスタンスが削除されたときにクリーンアップを実行するには、
Instance.Destroyingを使います。
obj.Destroying:Connect(function()
cleanup()
end)
:Connect()には、対応する:Disconnect()または破棄処理を必ず組み合わせます。
spawn/delay より task を優先する
task ライブラリは、従来の spawn、delay、wait よりも予測しやすくなっています。
-- good
task.wait(1)
task.delay(1, callback)
task.spawn(coroutineOrFn)
-- avoid
spawn(callback)
delay(1, callback)
wait(1)
task.defer は、新しいスレッドを作成せずに次の再開サイクルまで処理を譲る場合に便利です。
ログのレート制限とシークレットの取り扱い
- シークレット、キー、ユーザー識別子、個人情報は決してログに記録しないでください。
- ログに記録する前に値をサニタイズしてください。
local function sanitize(value)
if typeof(value) == "Instance" and value:IsA("Player") then
return "Player(" .. tostring(value.UserId) .. ")"
end
return value
end
- コンソールやネットワークがログであふれないよう、ノイズの多いログにはレート制限を設定してください。
local lastLog = 0
local function throttledLog(message)
local now = os.clock()
if now - lastLog > 5 then
lastLog = now
warn(message)
end
end
ネットワーク所有権
ネットワーク所有権が正しくないと、物理演算の揺れやレプリケーションの遅延が発生します。所有権を可視化し、明示的に割り当ててください。
-- Server
part:SetNetworkOwner(player)
-- Client-side visualization (debug only)
local ownershipLabel -- create BillboardGui or use DebugDraw
所有権を確認するには BasePart:GetNetworkOwner() を使用します。車両や手持ちアイテムは、通常、それを操作するプレイヤーが所有するようにします。
DataStore の再試行パターン
再試行に安全な読み取りには、指数バックオフとジッターを用いた上限付きの再試行を使用してください。この汎用ループを書き込みの再実行に使ってはいけません。応答が失敗しても書き込みが確定している場合があり、個別に再試行されたリクエストが順不同で実行されることもあります。書き込みの調整とキーごとの順序については、roblox-datastores を参照してください。
local function readWithRetry(fn, maxAttempts)
maxAttempts = maxAttempts or 5
for attempt = 1, maxAttempts do
local ok, result = pcall(fn)
if ok then
return true, result
elseif attempt == maxAttempts then
return false, result
else
warn("DataStore attempt " .. attempt .. " failed; retrying...")
task.wait(2 ^ attempt * 0.1 + math.random() * 0.5)
end
end
end
DataStore は必ずサーバーから呼び出し、保存前にシリアライズ可能であることを検証してください。
メモリスナップショットの比較
Developer Console の Memory タブまたは Scene Analysis を使って、スナップショットを比較します。
- 安定した状態でベースラインのスナップショットを取得します。
- メモリリークの可能性がある操作(敵の生成/消去、UI の開閉など)を行います。
- 安定した状態に戻り、2 つ目のスナップショットを取得します。
- PlaceMemory、Luau ヒープ、インスタンス数を比較します。
- ベースラインに戻らなかったカテゴリを調査し、Scene Analysis で親が設定されていないインスタンスがないか確認します。
MicroProfiler
Studio またはデスクトップクライアントで Ctrl+F6(⌘+F6)を押して開きます。
次の用途に使用します。
- フレーム時間の急増を見つけます。
- ボトルネックがスクリプトの計算、物理演算、レンダリングのどれにあるかを特定します。
- Developer Console からサーバーダンプを取得します。
debug.profilebegin/debug.profileendでカスタムラベルを追加します。
主な色:
- オレンジ:ワーカースレッド(スクリプト、物理演算、アニメーション)のボトルネック。
- 青:レンダースレッドのボトルネック。
- 赤:GPU 待機/レンダリングの複雑さ。
Scene Analysis
Studio の Window → Performance Summary → Scene Analysis から利用できます。
表示項目:
- スクリプトメモリ:スクリプトごとの Luau ヒープ。
- 親が設定されていないインスタンス:スクリプトが保持しているメモリリークの可能性があるインスタンス。
- インスタンス構成:カテゴリごとの数。
- オーディオ/アニメーションメモリ:アセットのメモリ使用量。
- 三角形構成:ドローコールの内訳。
Scene Analysis は Studio の UI ツールであり、公開されている SceneAnalysisService API はありません。
Script Profiler
スクリプトごとの CPU 時間を記録します。MicroProfiler でスクリプトが原因だと分かったものの、どのスクリプトかを特定する必要がある場合に使用してください。
よくあるバグのカテゴリ
| 症状 | 主な原因 |
|---|---|
| スクリプトが何も表示せずに失敗する | pcall がない、エラーが握りつぶされている、スクリプトのコンテキストが間違っている |
| データが保存されない | クライアントから DataStore を呼び出している、値がシリアライズできない、pcall がない |
| リモートが機能しない | 呼び出し元が間違っている、ハンドラーが接続されていない、引数が一致しない |
| 遅延が急増する | 毎フレームのパスファインディング、パーティクルが多すぎる、ループがバッチ処理されていない |
| 物理演算が揺れる | ネットワーク所有権が間違っている、アセンブリが分割されている、制約が競合している |
| NPC が動けなくなる | 遮られたパスが再計算されない、エージェントのパラメーターが不適切、ストリーミングの問題 |
| UI が更新されない | プロパティがレプリケートされていない、親が間違っている、レイアウト順序 |
| メモリが増え続ける | 接続のリーク、親が設定されていないインスタンス、アセットのキャッシュ |
ネットワークのデバッグ
- 開発者コンソールのNetworkタブで、HTTP/DataStore の障害を確認します。
- ゲーム内で
Shift+F3を押すと、ネットワークのデバッグ統計を確認できます。 - ネットワークの ping(往復時間)と、データの ping(レプリケーションキュー)を区別します。
- Studio のベータ版ネットワークシミュレーターを使うと、受信/送信それぞれの遅延、ジッター、パケット損失を個別に設定できます。Beta Features でNew Device Simulatorを有効にして Studio を再起動し、Test → Device Simulator → Networkを開きます。Applyを選択すると、設定した変更が適用されます。Ideal Fiberでも片道あたり8 msが追加されます。遅延を追加しない基準値には、カスタム値をすべてゼロにします。ネットワークシミュレーションを参照してください。
読み込み時間のデバッグ
読み込み時間を計測します。
local start = os.clock()
game.Loaded:Connect(function()
print("Loaded in", os.clock() - start)
end)
ReplicatedFirstのLocalScriptから実行してください。ReplicatedFirstのレプリケーション開始時に計測を始め、game.Loadedが発生した時点で計測を終了するため、サーバーへの接続とアバターの読み込みにかかる時間は含まれません。プレイヤーがPlayをクリックしてからワールドが描画されるまでの、実際に体験する読み込み時間はこれより長くなります。Studio Settings → Network でPrint Join Size Breakdownを有効にすると、レプリケーションされるインスタンスのうち、サイズが大きいものを確認できます。
スクリプト
scripts/TestRunner.lua:ネストされたスイート、ライフサイクルフック、マッチャー、非同期処理、TestService との統合に対応した、最小限の TestEZ 代替機能です。scripts/Logger.lua:レベルフィルタリングと安全なフォーマット処理に対応した、構造化ロガーです。scripts/DebugDraw.lua:ビジュアルデバッグ用に、3D 空間でレイ、点、ボックスを描画するユーティリティです。
次の手順
- 問題を確実に再現します。
- 開発者コンソールでログとエラーを確認します。
- クライアント側かサーバー側かを特定します。
- パフォーマンスの問題には、MicroProfiler/Script Profiler を使用します。
- メモリリークとシーン構成の分析には、Scene Analysis を使用します。
- 修正を確認するため、対象を絞ったログ出力やテストを追加します。
- 該当する場合は、低スペックのデバイスとネットワークシミュレーションで検証します。
- メモリリークが疑われる箇所の前後で、メモリスナップショットを比較します。
参考資料の一覧
- common-bugs-and-fixes.md:症状から考えられる原因と、焦点を絞った修正方法を特定します。
- debugging-tools.md:ログ、ブレークポイント、スタックトレース、実行時の状態を調査します。
- network-simulation.md:Studio で遅延、ジッター、パケット損失の問題を再現します。
- performance-profiling.md:CPU、GPU、メモリ、読み込み時間のボトルネックを特定します。
- testing-patterns.md:ユニットテストの作成、依存関係のモック、クライアント/サーバー間のフローのテスト方法を説明します。