关于 roblox-testing
roblox-testing 是面向 Roblox 开发者的测试与调试指南,介绍如何复现问题、区分客户端和服务器,并选择合适的日志、测试与性能分析工具。内容覆盖内存、网络、脚本性能及常见故障排查。
主要功能
- 01Developer Console 日志与运行状态检查
- 02TestEZ 单元测试与 Luau 类型检查
- 03Studio 调试器和错误堆栈追踪
- 04MicroProfiler 与 Script Profiler 性能分析
- 05内存快照和 Scene Analysis 排查
- 06网络模拟与常见问题定位
适用场景
- 01区分只在客户端或服务器出现的故障
- 02用 TestEZ 验证模块逻辑并检查类型
- 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)))
避免记录机密、玩家数据或个人身份信息。
pcall 和断言
对可能失败的调用使用封装,尤其是云服务、HTTP、DataStores 和 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:捕获服务端转储。
切换客户端/服务端视图,查看输出来自哪一端。
使用 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 文件夹包含一个精简的 TestRunner.lua 兼容层,仅用于无法使用 TestEZ 的环境。
Luau 类型检查
使用 Luau language server(luau-lsp)或 Studio 中的 Luau 类型检查器,在运行前发现错误。
最佳实践:
- 为模块导出和纯函数添加类型注解。
- 在 CI 中或提交前运行
luau-lsp analyze。 - 为新模块启用
--!strict。 - 不要对未声明类型的引擎 API 过度添加类型;优先在边界处使用类型转换。
Studio 调试器
使用 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
- 对只需触发一次的事件处理程序使用
:Once()。 - 使用
Instance.Destroying,在实例被移除时触发清理:
obj.Destroying:Connect(function()
cleanup()
end)
- 始终为
:Connect()配对调用:Disconnect()或销毁操作。
优先使用 task,而非 spawn/delay
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,并在保存前验证序列化结果。
内存快照对比
使用开发者控制台的 Memory 选项卡或 Scene Analysis 对比快照:
- 在稳定状态下捕获基线快照。
- 执行可能导致泄漏的操作(生成/移除敌人、打开/关闭 UI)。
- 返回稳定状态并捕获第二个快照。
- 对比 PlaceMemory、Luau 堆和实例数量。
- 调查未恢复到基线的类别;在 Scene Analysis 中查找未设父级的实例。
MicroProfiler
在 Studio 或桌面客户端中按 Ctrl+F6(⌘+F6)打开。
使用它来:
- 查找帧时间峰值。
- 判断瓶颈来自脚本计算、物理还是渲染。
- 从开发者控制台捕获服务器转储。
- 使用
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 |
| Remote 无法正常工作 | 端侧错误、处理程序未连接、参数不匹配 |
| 延迟峰值 | 每帧进行路径规划、粒子过多、循环未批处理 |
| 物理抖动 | 网络所有权错误、装配体拆分、约束冲突 |
| NPC 卡住 | 路径受阻后未重新计算、Agent 参数错误、流式加载问题 |
| UI 未更新 | 属性未复制、父级错误、布局顺序错误 |
| 内存持续增长 | 连接泄漏、未设父级的实例、缓存资源 |
网络调试
- 在 Developer Console 的 Network 选项卡中检查 HTTP/DataStore 故障。
- 在游戏中使用
Shift+F3查看网络调试统计信息。 - 区分网络 ping(往返时间)和数据 ping(复制队列)。
- 使用 Studio 的 Beta Network Simulator 分别模拟入站/出站延迟、抖动和丢包。在 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:精简的 TestEZ 替代方案,支持嵌套套件、生命周期钩子、匹配器、异步支持和 TestService 集成。scripts/Logger.lua:支持级别筛选和安全格式化的结构化日志记录器。scripts/DebugDraw.lua:用于在 3D 场景中绘制射线、点和方框,以进行可视化调试的工具。
后续步骤
- 稳定地复现问题。
- 在 Developer Console 中检查日志和错误。
- 确定问题发生在客户端还是服务器端。
- 对于性能问题,使用 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:编写单元测试、模拟依赖项或测试客户端-服务器流程。