mcpField
R
roblox-testing

roblox-testing

github.com/nonlooped测试与质量25372 次安装2 周前更新
代码仓库

关于 roblox-testing

roblox-testing 是面向 Roblox 开发者的测试与调试指南,介绍如何复现问题、区分客户端和服务器,并选择合适的日志、测试与性能分析工具。内容覆盖内存、网络、脚本性能及常见故障排查。

主要功能

  1. 01Developer Console 日志与运行状态检查
  2. 02TestEZ 单元测试与 Luau 类型检查
  3. 03Studio 调试器和错误堆栈追踪
  4. 04MicroProfiler 与 Script Profiler 性能分析
  5. 05内存快照和 Scene Analysis 排查
  6. 06网络模拟与常见问题定位

适用场景

  1. 01区分只在客户端或服务器出现的故障
  2. 02用 TestEZ 验证模块逻辑并检查类型
  3. 03定位脚本 CPU 瓶颈或帧耗时尖峰
  4. 04比较内存快照以查找泄漏迹象

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nonlooped/roblox-suite --skill roblox-testing

安装后在 Claude Code 中输入/roblox-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
common-bugs-and-fixes.md
debugging-tools.md
network-simulation.md
performance-profiling.md
testing-patterns.md
scripts/
DebugDraw.lua
Logger.lua
TestRunner.lua

详细介绍

roblox-testing

官方来源:

先复现问题,并确认问题发生在客户端、服务端还是两者都有。根据证据选择调试或性能分析工具。

调试思路

  1. 复现问题。 如果无法复现,就无法修复。
  2. 隔离问题。 逐步移除系统,直到问题消失;最后移除的内容就是问题根源。
  3. 测量问题。 使用工具,而不是猜测。
  4. 一次只修复一处。 验证修复,并尽可能添加回归测试。
  5. 谨慎记录日志。 良好的日志能加快今后的调试。

日志规范

有目的地使用 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 对比快照:

  1. 在稳定状态下捕获基线快照。
  2. 执行可能导致泄漏的操作(生成/移除敌人、打开/关闭 UI)。
  3. 返回稳定状态并捕获第二个快照。
  4. 对比 PlaceMemory、Luau 堆和实例数量。
  5. 调查未恢复到基线的类别;在 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 场景中绘制射线、点和方框,以进行可视化调试的工具。

后续步骤

  1. 稳定地复现问题。
  2. 在 Developer Console 中检查日志和错误。
  3. 确定问题发生在客户端还是服务器端。
  4. 对于性能问题,使用 MicroProfiler/Script Profiler。
  5. 使用 Scene Analysis 检查内存泄漏和场景构成。
  6. 添加针对性日志或测试,以确认修复有效。
  7. 在相关情况下,使用网络模拟在低端设备上进行验证。
  8. 对比疑似泄漏发生前后的内存快照。

参考索引

评论

支持 Markdown