Godot GDScript 测试套件实战解析:集成测试脚本、输出对比与 Autocompletion 测试机制
2026/9/5 22:03:36 网站建设 项目流程

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

【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot

本文以 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.hAutocompletion 测试运行器(仅在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.cpp 与 L545-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 一一对应,并补充了源码中的取值细节):

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

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

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

关键规则是:只针对[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.gd、class_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.cpp 与 test_completion.h 中,文件名约定(.notest.gd.norun.gd.bin.gd#debug-only)、警告开关机制与--gdscript-generate-tests再生成流程,共同构成了一套对构建类型稳健、可离线复现的 GDScript 质量保障体系。

【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询