搭建基于UE的lua测试框架失败复盘
我给 LyraStarterGame 搭过一套 Lua 测试框架,从C++交接Lua组织API,7 个测试脚本都写好了,但最后还是放弃了。这篇文章讲清楚为什么。
为什么想搭
蓝图功能测试在项目迭代里暴露了 5 个痛点:
- .uasset 是二进制,diff、merge,难以管理,而且对AI不友好
- 对于复杂逻辑实现困难,比如测试中需要大量使用协程,广播。
- CI 拿文本化报告难
- 多步断言和循环等待的节点连线冗长
Lua 方案的目标是把测试编写主力从蓝图换成文本:80% 的测试用 Lua DSL,20% 用 C++ CQTest,蓝图退居辅助。主要目的是热重载、文本 diff,CI,AI 友好。
方案长什么样
C++ 造轮子,Lua 用轮子。底层引擎一开始选 UnLua,因为不兼容 UE 5.8 换成了 LuaMachine。C++ 桥接层注册了 32 个函数,Lua 侧是 TestRunner、TestContext、Assertion 组成的 DSL Runtime,写了 7 个测试脚本和 5 个 Helper。
三个没做对的地方
复盘下来,失败可以归结成三条。
调研不足。 设计方案没有回答 DSL 的语义模型:测试语法描述的是”给武器上弹、射击、角色复活”这类游戏行为,还是”调用引擎 API”?没想清楚就开工了。IDE 补全这种验收目标写进了 spec,但从来没去验证 LuaMachine 到底支不支持。
抽象层没有语义厚度。 Lua API 直接连 C++ 桥接层,TestContext.lua 几乎是桥接函数的别名表:
1 | function ActorNS:GetPC(index) return GetPC(index or 0) end |
约定为内部 API 的 _Call、GetProperty 被各命名空间普遍使用,引擎概念一样被带进了测试框架,许多魔法数字(ECC_WorldStatic = 0)、硬编码 Tag、console 命令字符串。缺乏抽象层无法把引擎概念消化成测试框架需要的语义。
脚本并没有简化测试。 写测试的人仍然要懂 UE 反射体系、Lyra 内部组件、Lua 三样东西。一个用例的 before_each 要 15 行引擎层准备逻辑:
1 | function suite:before_each(ctx) |
比直接写 C++ 多了一层翻译成本,还丢了编译期检查。这跟弃用蓝图的理由完全同构:复杂逻辑在蓝图里难写,换成 Lua 只换了一种写法,没有解决任何问题。
最后决定放弃
PIE 集成时发现,由于UE的诸多限制,Lua无法直接接入UE的测试管理框架(拉起PIE,做日志收集,管理测试结果,测试报告),不像最初设想的Lua调C++这么简单,如果继续的话,要从底座重新搭起。
同一时期,C++ CQTest 的 EventSystem_Death.spec.cpp 证明死亡、重生这种复杂链路(事件捕获、状态轮询、真实伤害管线、重生完整性)在 C++ 下直接可行。两相对比,Lua 没有带来任何新能力,只带来新的断层。
根源
- 目标不可度量。”80% Lua””简化测试””CI 友好”没有量化验收标准,没法在早期发现测试并没有变简单。
- 用换语言解决编写方式问题。痛点是输入模拟、异步等待、断言、报告,这些 C++ 已经有轮子。
- 跳过语义模型设计,直接进桥接实现。
- 先搭框架、后写用例。验证路径是”语法跑通”,不是”测试编写者能不能快速写出真实用例”。
- 对 UE 测试的固有成本认识不足。CQTest + FMapTestSpawner 下几乎都是集成测试,地图加载、异步时序、状态轮询的成本不会因为换脚本语言消失。
重来我会怎么做
三条可执行的:
- 先做最小验证。拿 3 个金样用例(覆盖输入模拟、异步等待、状态断言),分别用 C++ CQTest 和候选 DSL 各写一遍,对比编写时间、调试体验、维护成本,再决定要不要立项。
- 真要做 DSL,先定义语义模型。语法描述游戏行为(”上弹””射击””等待弹药变化””复活”),不是引擎 API。用目标用例倒推语法。
设计文档和复盘全文在 LyraStarterGame 测试文档 里,13 号(测试辅助 API)、15 号(Lua DSL 设计)、16 号(API 参考)、17 号(复盘)。


