尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SerenityOS test-js 工具完全指南:LibJS 测试套件运行与 Jest 风格断言框架深度解析

SerenityOS test-js 工具完全指南:LibJS 测试套件运行与 Jest 风格断言框架深度解析 SerenityOS test-js 工具完全指南LibJS 测试套件运行与 Jest 风格断言框架深度解析【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity本文是 SerenityOS 官方手册页 test-js(1) 的深度扩充版以该手册为主体骨架结合仓库中test-js的 C 实现、LibJS 测试套件与测试框架源码系统讲解如何运行 LibJS 测试、编写符合规范的测试用例、利用各类命令行选项以及理解底层执行原理。读完本文你将掌握在 SerenityOS 环境与 Lagom 构建中运行、过滤、调试 LibJS 测试的完整流程并能基于 Jest 风格断言框架自行编写高质量的 JavaScript 测试。1. 概述test-js 是什么test-js是 SerenityOS 中运行LibJS 测试套件的命令行工具。它负责加载、执行位于测试根目录下的大量 JavaScript 测试文件.js并汇总输出每个文件的执行结果。其功能定位与作用可以从 手册页 和 test-js.cpp 中清晰看到。这些测试使用的是一套自定义的 JavaScript 测试框架其 API 风格明显借鉴了流行的 Jest。除此之外test-js还支持运行 test262 解析器测试用于验证 LibJS 解析器对 ECMAScript 语法尤其是语法错误场景的符合程度。1.1 测试根目录test-js定位测试文件时遵循以下优先级环境默认测试根目录说明SerenityOS 系统内/home/anon/Tests/js-tests系统自带安装路径Lagom 构建宿主系统$SERENITY_SOURCE_DIR/Userland/Libraries/LibJS/Tests依赖SERENITY_SOURCE_DIR环境变量任意环境通过位置参数path显式指定覆盖以上默认值上述默认路径的逻辑在 JavaScriptTestRunnerMain.cpp 中实现在 SerenityOS 内会拼接出/home/anon/Tests/js-tests其中program_name取test-js去掉test-前缀后的js再拼上-tests后缀而在非 SerenityOS 环境Lagom下则要求设置SERENITY_SOURCE_DIR环境变量否则直接报错退出。对应地测试根目录的声明位于 test-js.cppTEST_ROOT(Userland/Libraries/LibJS/Tests);这条宏展开后生成g_test_root_fragmentLagom 构建下会与SERENITY_SOURCE_DIR拼接成完整的测试根目录。1.2 与 js(1) 的关系test-js与手册中See also指向的js(1) 互为补充js是 LibJS 的交互式/脚本执行 REPL用于运行任意 JS 脚本而test-js是批量运行测试套件并输出结构化结果的测试专用工具二者共享 LibJS 引擎内核。2. 运行方式与测试根目录解析2.1 基本调用语法$ test-js [options...] [path]其中path可选位置参数指定测试根目录不传则使用默认值见上表。options详见下文第 3 节命令行选项。2.2 Lagom 构建下的典型用法在宿主 Linux/macOS 上通过 Lagom 构建 LibJS 后需要显式导出SERENITY_SOURCE_DIR才能运行# 以仓库根目录为例 $ export SERENITY_SOURCE_DIR/path/to/serenity $ ./Build/lagom/bin/test-js从源码可见SERENITY_SOURCE_DIR同时用于定位测试根目录与公共测试框架文件 JavaScriptTestRunnerMain.cpptest_root ByteString::formatted({}/{}, serenity_source_dir, g_test_root_fragment); common_path ByteString::formatted({}/Userland/Libraries/LibJS/Tests/test-common.js, serenity_source_dir);即测试根目录为$SERENITY_SOURCE_DIR/Userland/Libraries/LibJS/Tests公共框架文件为同一目录下的 test-common.js。2.3 覆盖默认路径若传入位置参数path则直接以该目录作为测试根目录同时可用第二个位置参数common-path指定test-common.js的路径参数定义见 JavaScriptTestRunnerMain.cpp。例如$ test-js /home/anon/Tests/my-js-tests /home/anon/Tests/my-js-tests/test-common.js工具会对传入的路径做real_path规范化解析并校验测试根目录必须是一个真实存在的目录FileSystem::is_directory否则报错退出JavaScriptTestRunnerMain.cpp。2.4 递归收集测试文件测试根目录会被递归遍历凡是以.js结尾且文件名不是test-common.js的文件都会被视为一个测试文件参与执行JavaScriptTestRunner.hiterate_directory_recursively(m_test_root, { if (!file_path.ends_with(.jssv)) return; if (!file_path.ends_with(test-common.jssv)) paths.append(file_path); }); quick_sort(paths);收集到的文件路径还会经过快速排序保证执行顺序确定、可复现。这也意味着测试可以按功能放在任意深度的子目录中例如builtins/、classes/、functions/、operators/、syntax/等test-js都会自动发现。2.5 禁用调试输出dbgln()是 SerenityOS 内核及用户态广泛使用的调试打印宏。测试执行过程中dbgln()的输出默认会打印到调试端口。若希望屏蔽这些输出可设置环境变量$ DISABLE_DBG_OUTPUT1 test-js其底层实现位于 JavaScriptTestRunnerMain.cppif (getenv(DISABLE_DBG_OUTPUT)) { AK::set_debug_enabled(false); }设置该变量后AK::set_debug_enabled(false)会全局关闭调试输出使测试日志更干净、更利于在 CI 中解析结果。3. 命令行选项详解test-js的所有选项均由Core::ArgsParser解析JavaScriptTestRunnerMain.cpp。下表汇总了手册中列出的全部选项并补充了源码中的实现细节选项长选项作用源码行为-t--show-time显示每个测试的执行耗时设置print_times输出 PASS/FAIL 行附带毫秒或秒级耗时JavaScriptTestRunner.h-p--show-progress用 OSC 9 转义序列显示进度取值true/falseSerenityOS 内默认开启宿主环境默认关闭。该选项必须带true或false参数否则解析失败-j--json以 JSON 格式输出结果设置print_json跳过人类可读的逐文件输出—--per-file输出更详细的逐文件 JSON 结果隐式启用-j设置per_file随后print_json trueJavaScriptTestRunnerMain.cpp-g--collect-often每次分配后都执行垃圾回收设置g_collect_on_every_allocation通过heap().set_should_collect_on_every_allocation()生效JavaScriptTestRunner.h-b--run-bytecode使用字节码解释器执行源码中已默认走字节码解释器g_vm-bytecode_interpreter().run()见下节说明-d--dump-bytecode转储生成的字节码设置JS::Bytecode::g_dump_bytecodeJavaScriptTestRunnerMain.cpp-f glob--filter glob只运行匹配该 glob 的测试文件内部会将 glob 包装为*{glob}*再做匹配JavaScriptTestRunnerMain.cpp—--test262-parser-tests运行 test262 解析器测试由TESTJS_PROGRAM_FLAG注册为自定义 flagtest-js.cpp说明关于-b/--run-bytecode在当前版本源码中LibJS 的执行路径已统一为字节码解释器bytecode_interpreter().run()见 JavaScriptTestRunner.h手册页保留该选项主要是为了兼容旧版行为。当前仓库中未再发现独立的AST 解释器开关因此该选项在最新代码中实际为历史遗留兼容项。3.1 进度显示与 OSC 9-p/--show-progress使用终端 OSC 9 转义序列报告进度即\033]9;...\007这类由终端模拟器识别的控制序列通常用于在支持该协议的终端中渲染进度条。该选项在 SerenityOS 系统内默认开启在 Lagom/宿主环境下默认关闭JavaScriptTestRunnerMain.cppbool print_progress #ifdef AK_OS_SERENITY true; // Use OSC 9 to print progress #else false; #endif3.2 过滤器 glob 的匹配规则--filter接受一个 glob如builtins、classes/*等内部处理为test_glob ByteString::formatted(*{}*, test_glob);即两侧自动补*通配符再传给TestRunner::run(test_glob)对收集到的全部文件路径做匹配。因此--filter string实际是路径包含 string 即匹配的子串式过滤适合快速圈定某一类测试。3.3 JSON 输出模式-j/--json与--per-file面向机器消费场景如 CI 汇总、结果上传。启用 JSON 后TestRunner::do_run_single_test不再调用print_file_result打印人类可读行而是把测试结果以 JSON 形式输出JavaScriptTestRunner.h。--per-file会进一步输出更细粒度的逐文件含 suite、case 层级JSON 结果其背后依赖needs_detailed_suites()与ensure_suites()机制。4. 测试框架Jest 风格的断言与组织 APItest-js并非简单逐个执行脚本而是先注入一套JavaScript 测试框架即 test-common.js再执行被测文件。该框架暴露三个全局函数describe(message, callback)定义测试套件suitetest(message, callback)定义单个测试用例另有test.skip、test.xfail、test.xfailIf变体expect(value)创建断言器Expector链式调用各类 matcher。4.1 一个最简单的测试手册给出的经典示例来自 Gary Bernhardt 的 Wat 演讲describe(Examples from Gary Bernhardts Wat talk, () { test(Na na na na na na na na na na na na na na na na Batman!, () { expect(Array(16).join(wat - 1) Batman!).toBe( NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman! ); }); });这个测试断言Array(16).join(wat - 1)由于wat - 1得到NaNArray(16).join(NaN)会把NaN转成字符串填充 15 个分隔符最终拼出 15 个NaN加上 Batman!。它同时验证了类型强制转换、Array.prototype.join的字符串化行为等多个 LibJS 语义细节。4.2 describe 与 test 的组织方式从 test-common.js 源码可以看到describe内部把当前套件名写入内部变量suiteMessage执行回调后恢复为顶层默认名__$$TOP_LEVEL$$__test把结果pass/fail 耗时微秒数写入全局对象__TestResults__同名测试若重复注册会直接判定为 fail提示Another test with the same message did already run每个测试的耗时通过Temporal.Now.instant().epochNanoseconds计时单位为微秒。4.3 特殊测试变体框架还提供跳过与预期失败机制test.skip(message, callback)标记为跳过result: skip不计入失败test.xfail(message, callback)预期失败测试——若回调抛出异常则记为xfail通过若意外通过则记为失败Expected test to fail, but it passedtest.xfailIf(condition, message, callback)根据条件在xfail与普通test之间切换。C 侧在收集结果时分别处理pass/fail/xfail/skip四种结果其中xfail对应Test::Result::ExpectedFailJavaScriptTestRunner.h。4.4 完整 matcher 列表expect(value)返回的Expector支持以下 matcher均为test-common.js中真实实现Matcher说明toBe(value)基于Object.is的严格相等断言可区分0/-0、NaNtoEqual(value)递归深比较数组、普通对象、原始值toBeCloseTo(value, precision 5)数值近似比较epsilon 为10 ** -precision / 2toHaveLength(n)断言target.length ntoHaveSize(n)断言target.size n针对 Set/Map 等toHaveProperty(prop, value?)支持点分路径如a.b.c与数组路径的属性断言toBeDefined()/toBeUndefined()/toBeNull()/toBeNaN()类型/值判定toBeTrue()/toBeFalse()严格布尔断言 true/ falsetoBeLessThan(v)/toBeLessThanOrEqual(v)/toBeGreaterThan(v)/toBeGreaterThanOrEqual(v)数值比较要求两侧同为 number 或同为 biginttoContain(item)/toContainEqual(item)容器包含断言后者做深比较toThrow(value?)断言回调抛出异常value可为字符串匹配消息子串、错误类、错误对象toThrowWithMessage(class_, message)同时校验错误类型与消息包含jest-extended 风格pass(message)/fail(message)显式通过 / 强制失败toEval()语法级断言目标字符串能否被解析底层调用 C 注入的canParseSourcetoEvalTo(value)目标字符串执行后结果深比较toHaveConfigurableProperty/toHaveEnumerableProperty/toHaveWritableProperty基于Object.getOwnPropertyDescriptor的属性特性断言toHaveValueProperty(prop, value?)/toHaveGetterProperty(prop)/toHaveSetterProperty(prop)属性描述符取值/访问器断言toBeIteratorResultWithValue(v)/toBeIteratorResultDone()迭代器结果对象断言所有 matcher 都支持通过.not取反例如expect(x).not.toBe(y)。其取反实现位于__doMatchertest-common.js当inverted为真时捕获ExpectationError并反转判定。5. 底层执行流程从 C 到 JS 的桥接理解test-js的执行流程有助于排查测试问题。核心流程如下主逻辑在 JavaScriptTestRunner.h新建 Realm为每个测试文件创建独立的JS::Realm与TestRunnerGlobalObject后者把 C 注入的全局函数注册为 JS 全局函数见TestRunnerGlobalObject::initialize解析并执行 test-common.js先把测试框架脚本解析为JS::Script并由字节码解释器执行注入describe/test/expect等全局 API解析并执行被测文件同样走parse_scriptbytecode_interpreter().run()若被测文件存在语法错误则直接返回带ParserError的失败结果读取结果通过__TestResults__全局对象序列化为 JSONJS::JSONObject::stringify_impl再把 JSON 反序列化为Test::Suite/Test::Case结构统计tests_passed/tests_failed/tests_skipped/tests_expected_failed汇总输出人类可读模式打印PASS/FAIL行与失败详情JSON 模式输出结构化结果退出码只要存在失败测试进程退出码为 1JavaScriptTestRunnerMain.cpp。5.1 C 注入的测试辅助全局函数在 test-js.cpp 中通过TESTJS_GLOBAL_FUNCTION宏向 JS 环境注入了若干专供测试使用的全局函数JS 全局函数C 实现用途isStrictMode()is_strict_mode查询当前 VM 是否处于严格模式供program-strict-mode.js等测试使用canParseSource(source)can_parse_source用JS::Parser(JS::Lexer(source))解析源码并返回是否无错误toEval的底层实现runQueuedPromiseJobs()run_queued_promise_jobs手动冲刷 VM 中排队等待的 Promise 任务异步测试使用getWeakSetSize(obj)/getWeakMapSize(obj)对应实现读取WeakSet/WeakMap内部条目数JS 层无法直接获取markAsGarbage(varName)mark_as_garbage将指定变量标记为可回收配合 GC 测试如gc-deeply-nested-object-graph.jsdetachArrayBuffer(buffer, key?)detach_array_buffer模拟ArrayBuffer分离detach操作用于测试分离后访问行为setTimeZone(tz|null)set_time_zone设置/清除TZ环境变量并调用tzset()返回旧时区用于时区相关测试这些函数通过TESTJS_GLOBAL_FUNCTION宏注册到s_exposed_global_functions表最终在TestRunnerGlobalObject::initialize中定义为全局 native 函数JavaScriptTestRunner.h。5.2 GC 压力测试模式-g/--collect-often通过heap().set_should_collect_on_every_allocation(true)让 GC 在每次分配后都触发用于暴露与对象生命周期、弱引用、回收时机相关的 bug。测试套件中的gc-deeply-nested-object-graph.js等用例正是配合该模式验证 GC 正确性的典型场景。6. test262 解析器测试模式test-js的另一重要能力是运行test262 解析器测试test262-parser-tests用于校验 LibJS 解析器对 ECMAScript 语法特别是应当报错的语法的处理是否符合规范。6.1 触发方式$ test-js --test262-parser-tests /path/to/test262-parser-tests该 flag 通过TESTJS_PROGRAM_FLAG注册test-js.cppTESTJS_PROGRAM_FLAG(test262_parser_tests, Run test262 parser tests, test262-parser-tests, 0);6.2 目录约定与判定逻辑test262 解析器测试按目录名表达期望结果test-js.cpp目录名期望判定early文件不应解析成功test_passed !parse_succeededfail文件不应解析成功test_passed !parse_succeededpass文件应解析成功test_passed parse_succeededpass-explicit文件应解析成功test_passed parse_succeeded其他目录—跳过该文件SkipFile对于每个文件还会根据扩展名区分解析方式以.module.js结尾的文件按 ES ModuleJS::SourceTextModule::parse解析其余按普通 ScriptJS::Script::parse解析。每个文件生成一个名为Parse file的测试用例并给出对应期望的说明File should not parse/File should parse。auto program_type path.basename().ends_with(.module.jssv) ? JS::Program::Type::Module : JS::Program::Type::Script; ... parse_succeeded !Test::JS::parse_module(test_file, realm).is_error(); // 或 parse_script这一模式让 LibJS 团队可以方便地将 tc39/test262-parser-tests 仓库直接挂接进来持续追踪解析器对规范语法边界的符合情况。7. 输出结果解读7.1 人类可读输出默认非 JSON模式下test-js对每个文件输出一行结果JavaScriptTestRunner.hPASS path/to/test-file.js (12ms) FAIL path/to/failing-test.jsPASS前带有绿色背景配合-t时显示耗时小于 1 秒显示毫秒否则显示秒FAIL前带有红色背景随后会列出失败套件、失败用例及其details断言失败消息或异常堆栈若文件解析失败会输出❌ The file failed to parse及解析器提示与错误信息测试内console.log(...)的输出会被收集并显示在ℹ Console output:之下console.log被重绑定到__UserOutput__数组见 test-common.js。7.2 JSON 输出-j模式输出结构化 JSON 结果--per-file输出更详细的逐文件 JSON含 suite/case 层级与耗时。这两类模式适合 CI 或脚本解析失败时进程退出码为 1便于在流水线中直接判定构建是否通过。8. 编写与运行测试的实战建议8.1 测试文件放哪里在 Lagom 构建下把.js测试文件放入Userland/Libraries/LibJS/Tests/或其任意子目录即可被自动发现在 SerenityOS 系统内则放入/home/anon/Tests/js-tests。注意文件名不要与test-common.js冲突。8.2 常用运行组合# 只跑 builtins 相关测试并显示耗时 $ test-js -t -f builtins # 跑 classes 目录输出 JSON 供 CI 解析 $ test-js --per-file -f classes # 启用 GC 压力测试模式 $ test-js -g # 转储字节码辅助调试 $ test-js -d -f syntax # 屏蔽 dbgln 调试输出 $ DISABLE_DBG_OUTPUT1 test-js8.3 实践要点严格相等优先toBe基于Object.is可精确区分NaN、0/-0比语义更严格异步测试需要手动调用注入的runQueuedPromiseJobs()冲刷微任务队列GC 相关测试配合markAsGarbage与-g模式验证弱引用与回收语义语法错误测试用expect(...).not.toEval()断言某段源码应产生语法错误或使用toEvalTo断言执行结果预期失败标注已知缺陷用test.xfail标注避免 CI 红而不掩盖进度一旦意外修复会自动转为失败以提醒移除标注。9. 参考链接手册原文test-js(1) 手册页可执行文件实现Tests/LibJS/test-js.cpp测试框架 JS 实现Userland/Libraries/LibJS/Tests/test-common.js测试运行器 C 实现Userland/Libraries/LibTest/JavaScriptTestRunner.h主入口与参数解析Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp测试套件目录Userland/Libraries/LibJS/Tests/关联命令js(1)【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表