首页
/ Godot GDScript 测试套件实战解析:集成测试脚本、输出对比与 Autocompletion 测试机制

Godot GDScript 测试套件实战解析:集成测试脚本、输出对比与 Autocompletion 测试机制

2026-09-04 20:49:45作者:房伟宁

本文以 Godot 仓库中的 GDScript 测试说明文档 为主体,完整讲解 modules/gdscript/tests/ 目录下两类测试的组织方式:一是 scripts/ 中的 GDScript 集成测试(源码文件 + 期望输出文件),二是 scripts/completion 中的 GDScript 代码补全测试(.gd 用例 + .cfg 期望配置)。读完本文,你可以理解 Godot 如何在不启动完整编辑器的情况下,对 GDScript 的解析、类型检查、编译、运行及编辑器补全结果做自动化断言,并知道如何按规范新增测试用例。

一、测试目录结构与配套环境

GDScript 模块的测试代码位于 modules/gdscript/tests/,其核心文件分工如下:

文件 职责
gdscript_test_runner.h / gdscript_test_runner.cpp 集成测试运行器:扫描 scripts/ 目录、执行脚本并比对输出
test_completion.h Autocompletion 测试运行器(仅在 TOOLS_ENABLED 下编译)
test_gdscript.h / test_lsp.h 分阶段测试入口(Tokenizer / Parser / Compiler / Bytecode / LSP)
scripts/ 测试脚本与期望输出,含 analyzer/parser/runtime/completion/lsp/ 等子目录
project.godot 测试专用工程配置

其中 project.godot 开头明确写着:

; This is not an actual project.
; This config only exists to properly set up the test environment.
; It also helps for opening Godot to edit the scripts, but please don't
; let the editor changes be saved.

它不是真正可运行的工程,而是为测试环境提供 ProjectSettings(如 test_input_action 输入动作)。运行器初始化时会调用 ProjectSettings::setup(p_base_path, ...) 加载该目录,并初始化 GDScript 语言与 Autoload,见 gdscript_test_runner.cpp

二、集成测试:脚本文件 + 输出文件

README 第一部分说明:scripts/ 目录中的集成测试以“GDScript 文件 + 输出文件”的形式存在。从源码可以确认其完整工作机制。

2.1 测试执行管线

每个测试脚本必须包含名为 test 的函数(运行器将 test_function_name 固定为 StringName("test"),见 gdscript_test_runner.cpp)。GDScriptTest::execute_test_code()gdscript_test_runner.cpp)按如下顺序处理每个脚本:

  1. 加载:读取脚本源码;若为二进制 token 模式,则用 GDScriptTokenizerBuffer::parse_code_string(code, COMPRESS_ZSTD) 压缩为 token 缓冲再 set_binary_tokens_source()
  2. 解析GDScriptParser::parse(),失败则记录第一条解析错误(后续错误可能是级联错误);
  3. 类型检查GDScriptAnalyzer::analyze(),错误按 start_line 排序后逐行输出为 >> ERROR at line N: ...
  4. 编译GDScriptCompiler::compile()
  5. 运行ClassDB::instantiate() 创建宿主对象、set_script() 挂载脚本,然后通过 instance->callp("test", ...) 调用测试函数。

执行过程中通过 add_print_handler() / add_error_handler() 接管标准输出与错误输出,print() 的内容逐行追加到结果中,脚本错误则格式化为 >> 类型: 错误信息 at 相对路径:行号 on 函数() 的形式(仅 ERR_HANDLER_SCRIPT 类型附带文件/行号,以保证输出跨平台稳定)。

最终的判定逻辑是全文比对:实际输出(去掉首尾空白、末尾补一个换行,以适配 CI 静态检查)与同名 .out 文件内容逐字符比较,见 check_output()

2.2 测试结果状态码

GDScriptTest 定义了 6 种状态,写入输出文件首行,可直观区分脚本卡在哪一阶段(gdscript_test_runner.h):

状态 含义
GDTEST_OK 成功执行到运行阶段
GDTEST_LOAD_ERROR 源码加载失败或找不到 test() 函数
GDTEST_PARSER_ERROR 解析阶段失败,随后输出第一条解析错误
GDTEST_ANALYZER_ERROR 类型检查阶段失败,输出按行排序的错误列表
GDTEST_COMPILER_ERROR 编译阶段失败
GDTEST_RUNTIME_ERROR 运行期发生脚本错误

2.3 文件名约定与构建差异

目录扫描逻辑 make_tests_for_dir() 实现了若干文件名约定,编写集成测试时必须遵守:

  • *.notest.gd:被完全跳过,用于存放测试辅助代码(例如 utils.notest.gd 中定义的 Utils 类,提供了静态 check() 断言函数并打印失败时的调用栈,供运行期测试复用);
  • *.norun.gd:只验证“解析 + 类型检查 + 编译”三个阶段,不要求存在 test() 函数,因此不执行运行期测试(gdscript_test_runner.cpp);
  • *.bin.gd:同一脚本会先以文本 tokenizer 模式跑一遍,再强制以 TOKENIZER_BUFFER(二进制 token)模式跑一遍,两种模式共享同一个 .out 期望文件;
  • *.textonly.gd:在启用 --use-binary-tokens 的测试运行中会被跳过;
  • 首行为 #debug-only 的脚本:仅在 release 构建(DEBUG_ENABLED 未定义)中被跳过。

调试/发布构建的行为差异还包括警告处理:

  • Debug 构建中,运行器会把所有 GDScriptWarning 的级别强制设为 Warn(便于测试原本默认是 Error 的警告),但 UNTYPED_DECLARATIONINFERRED_DECLARATION 两类默认保持关闭;若某个脚本需要测试这两类警告,可在源码中加入注释 # enable UNTYPED_DECLARATION# enable INFERRED_DECLARATION,运行器会据此动态打开对应设置(gdscript_test_runner.cppL545-L555);
  • 警告统一以 ~~ WARNING at line N: (警告名) 消息 的格式进入输出;在 release 构建中,期望文件里所有以 ~~ 开头的行会被 strip_warnings() 剔除后再比对,从而同一份 .out 文件能同时适配两类构建。

2.4 重新生成期望输出

集成测试采用“黄金输出(golden output)”模式:当 GDScript 行为变化导致输出变化时,可用命令行参数重新生成全部 .out 文件。handle_cmdline() 支持的用法为:

--gdscript-generate-tests [测试目录路径]
  • 省略路径参数时默认作用于 modules/gdscript/tests/scripts
  • 追加 --print-filenames 可逐个打印正在处理的文件名;
  • 生成模式(generate_outputs())下不要求 .out 文件已存在,执行完每个脚本后将实际输出写入同目录的 <脚本名>.out

三、Autocompletion 测试:➡ 光标标记与 .cfg 期望文件

README 第二部分(也是本文档的重心)说明:scripts/completion 目录存放 GDScript 代码补全测试,每个用例至少包含一个 .gd 文件(被测代码)和一个 .cfg 文件(期望结果与配置)。

3.1 光标位置标记

在 GDScript 文件中,字符 (U+27A1)表示触发补全时的光标位置。由于该字符不是合法 GDScript 词法符号,且补全往往发生在代码不完整时,这些脚本本身并不可解析。运行器(test_completion.h)的处理方式是:

  1. 读取脚本后,把第一个 (0x27A1)替换为哨兵字符 0xFFFF(使用 0x27A1 而非 0xFFFF 是为了让文件对人类可读),并要求脚本中必须存在该哨兵,否则 CHECK(location != -1) 失败;
  2. 当测试需要场景(owner 节点)时,删除包含哨兵字符的整行,重新 reload() 脚本并 set_script() 挂到节点上,因此除该行外脚本必须合法——必要时可补一个 pass 语句;
  3. 正由于脚本由运行器挂载到 owner 节点上,脚本不应再通过场景文件加载。

3.2 配置文件 [input] 段(测试环境配置)

.cfg 是标准 INI 风格配置,用 ConfigFile 加载。[input] 段的完整键位如下(与 README 一一对应,并补充了源码中的取值细节):

类型 默认值 作用
cs boolean false true 时,在非 C#(Mono)构建中跳过该测试。源码对应 #ifndef MODULE_MONO_ENABLED 下的判断(test_completion.h
use_single_quotes boolean false 为本次测试设置编辑器选项 text_editor/completion/use_single_quotes
add_node_path_literals boolean false 为本次测试设置编辑器选项 text_editor/completion/add_node_path_literals
add_string_name_literals boolean false 为本次测试设置编辑器选项 text_editor/completion/add_string_name_literals
scene String 指定补全时打开的场景;未设置时运行器会查找与 GDScript 文件同基名.tscn;两者都没有时,补全按“无场景打开”处理(test_completion.h
node_path String .(场景根节点) 持有当前脚本的节点在场景中的路径(test_completion.h

3.3 配置文件 [output] 段(期望结果断言)

类型 说明
include Array 结果中应当出现的建议列表(无序)。每个条目是一个字典,支持 displayinsert_textkindlocation 四个键,对应代码中建议结构 ScriptLanguage::CodeCompletionOption 的字段。运行器只测试条目中显式给出的键,因此多数情况下只写 display 即可
exclude Array 结果中不应出现的建议,条目格式与 include 相同
call_hint String 期望的调用提示(call hint)
forced boolean 补全是否预期强制打开补全窗口

关键规则是:只针对 [output] 中显式给出的条目做断言(“Tests will only test against entries in [output] that were specified”)。这与源码实现一致:match_option() 对字典里缺失的键取实际值参与比较(即视为“不校验”),而 call_hint / forced 的缺省值就是运行器实际返回的值。

校验流程(test_directory()):调用 GDScriptEditorLanguage::complete_code(code, res_path, owner, &options, forced, call_hint) 得到建议列表后,先检查任何结果项不得命中 exclude(命中则报 “Autocompletion suggests illegal option”),再把 include 中每一项逐一从实际结果中“认领”(未全部认领则 CHECK(include.is_empty()) 失败),最后比对 call_hintforced

3.4 一个完整用例:参数补全 play_typed

scripts/completion/argument_options/play_typed.gd 为例,脚本内容为:

extends Node

@onready var anim: AnimationPlayer = $AnimationPlayer

func test():
	anim.play(➡)
    pass

位于 anim.play( 的参数位置。配套的 play_typed.cfg 为:

[input]
scene="res://completion/argument_options/argument_options.tscn"
[output]
include=[
    {"display": "\"bounce\""},
]

含义是:补全时加载 argument_options.tscn 场景(其中提供 AnimationPlayer 节点),且由于 anim 是明确标注为 AnimationPlayer 类型的变量,play() 的第一个字符串参数应触发枚举/字面量建议,结果中必须出现 display"bounce" 的条目。argument_options/ 目录下还有 play_untyped.gdplay_inferred.gdconnect.gd 等用例,分别覆盖参数类型为未标注、可推断、以及信号连接等场景,正是 README 所要求“同一行为在多个上下文中反复测试”的实例。

3.5 补全测试的初始化流程

整个补全测试套件由 doctest 套件 [Modules][GDScript][Completion] 下的用例 [Editor] Check suggestion list 驱动(test_completion.h),流程为:

  1. text_editor/completion/use_single_quotes 复位为 false,保证起点状态一致;
  2. init_language("modules/gdscript/tests/scripts") 加载测试工程配置并初始化 GDScript 语言;
  3. setup_global_classes() 递归扫描 scripts/completion 目录,把带 class_name 的 GDScript 注册为全局类(并检查重名冲突,test_completion.h)。completion/ 目录下的 class_a.notest.gdclass_b.notest.gd 等辅助类即通过此机制进入全局类索引,从而被补全测试引用;
  4. test_directory() 递归遍历每个 .gd 用例(跳过 *.notest.gd),按 3.2/3.3 节规则执行断言,结束后 memdelete(scene) 释放实例化的场景;
  5. finish_language() 收尾。

四、编写 Autocompletion 测试的实践准则

README 最后强调:“为避免边缘用例失败,同一行为需要多次测试”,并给出两类必查维度。

覆盖所有可能的类型来源——针对被测试行为适用的每一种类型都要测一遍:

  • BUILTIN(内置类型);
  • NATIVE(原生类);
  • GDScript 类(含带 class_name 的与 preload 引入的两种形式);
  • C# 类(作为所有其他语言绑定的代表,同样区分 class_namepreload 两种形式);
  • Autoload 单例。

README 还特别提醒:对最后几类,测试 SCRIPT(GDScript 提供)与 CLASS(可由 C# 提供)两种来源即可;不要依赖“Autoload 一定是 SCRIPT 类型”,因为这一行为未来可能变化。

覆盖可能的上下文——补全位置可能出现在程序的不同位置,例如:

  • 类成员变量的初始化表达式中;
  • 语句块(suite)内直接书写;
  • 语句块内的赋值语句中;
  • 作为函数调用的参数(如 play_typed.gd 所示)。

scripts/completion/ 的实际目录布局也能印证这些准则已被落实:types/ 下按 local/member/hints/ 划分上下文,assignment_options/argument_options/index/filter/get_node/global_enum/enum_values_in_match/ 等目录分别对应不同的补全触发场景,types/ 中则覆盖本地变量、成员变量与类型提示的差异。

五、如何查看与运行这些测试

结合源码中可确认的信息,运行这些测试的途径是:

  • Autocompletion 测试:随测试构建(test build,需 TOOLS_ENABLED 使 test_completion.h 参与编译)一起通过 doctest 框架执行,用例标签为 [Modules][GDScript][Completion]
  • 集成测试:由 test_gdscript.h 定义的 TEST_TOKENIZERTEST_TOKENIZER_BUFFERTEST_PARSERTEST_COMPILERTEST_BYTECODE 各阶段分别驱动,统一入口为 GDScriptTests::test(TestType),内部构造 GDScriptTestRunner(其 --use-binary-tokens 构造参数对应二进制 token 模式);
  • 重新生成期望输出:在测试可执行文件的命令行中传入 --gdscript-generate-tests(可选指定测试目录)与 --print-filenames,运行器会把每个脚本的实际输出写回对应的 .out 文件。

需要说明的适用前提:警告相关输出(~~ WARNING ... 行)仅在调试构建中产生,发布构建下会自动剔除;cs 键标记的 C# 用例只有在启用 Mono 模块的构建中才会真正执行。

六、小结

modules/gdscript/tests/README.md 用简短篇幅定义了 Godot 中 GDScript 的两种自动化测试范式:集成测试以“脚本 + 黄金输出文件”验证解析、类型检查、编译与运行全链路,并通过 GDTEST_* 状态码定位失败阶段;Autocompletion 测试则以 光标哨兵加 .cfg 期望文件,对建议列表、call hint 与强制补全行为做精确断言,[input] 段的 6 个配置键可精细控制编辑器选项与场景上下文。两者的实现分别落在 gdscript_test_runner.cpptest_completion.h 中,文件名约定(.notest.gd.norun.gd.bin.gd#debug-only)、警告开关机制与 --gdscript-generate-tests 再生成流程,共同构成了一套对构建类型稳健、可离线复现的 GDScript 质量保障体系。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384