ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MongoDB 仓库 JavaScript 集成测试代码规范与 mochalite 实践指南

MongoDB 仓库 JavaScript 集成测试代码规范与 mochalite 实践指南 MongoDB 仓库 JavaScript 集成测试代码规范与 mochalite 实践指南【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo导读MongoDB 服务器仓库中的jstests/目录承载了数千个 JavaScript 集成测试这些测试通过 mongo shell 驱动真实运行的 mongod / mongos单机、副本集或分片集群来验证服务器行为。本文基于仓库 jstests/AGENTS.md由 jstests/CLAUDE.md 引用的 JavaScript 测试代码规范展开并结合 jstests/libs/mochalite.js、src/mongo/shell/assert.js 等核心源码系统讲解断言库的正确用法、mocha 风格的测试组织方式、结构化日志的输出规范以及资源清理要求。读完本文你将掌握在 MongoDB 源码仓库中编写符合社区标准、可读可调试、确定性强的 JavaScript 集成测试的完整方法。一、规范总览从 CLAUDE.md 到 AGENTS.mdjstests/CLAUDE.md本身只包含一行引用AGENTS.md它指向同目录下真正的规范文档 jstests/AGENTS.md。这份规范共四大主题是 MongoDB 服务器 JavaScript 测试开发的“军规”断言库Assertion Library必须使用项目自有的断言库并区分同步命令断言与异步条件断言。新测试文件采用 mocha 风格使用describe/it/ 各类钩子组织用例。日志中使用 JSON 兼容的对象序列化通过attr参数传递结构化数据而不是把tojson()拼进字符串。资源与设置清理用完后立即释放游标、集合等资源并复位服务器参数与TestData全局配置。同时jstests/README.md 这份更完整的《Javascript Test Guide》从“最小化测试用例、可调试性、确定性、尽早失败、测试隔离”等原则对上述规范做了系统展开可作为补充阅读。二、断言库统一使用项目自有断言规范第一条是ALWAYS 使用项目自有的断言库。该库定义在 src/mongo/shell/assert.js共 2200 行在每次测试运行前被自动加载进全局作用域因此测试文件里无需 import 即可直接使用assert。2.1 通用断言与错误抛出的底层机制通用断言assert(b, msg, attr)接收布尔值b为假时抛出错误。其底层由doassert()完成msg可以是字符串、函数运行时求值或对象自动tojson序列化若传入attr则通过_getErrorWithCode构造带结构化属性的错误对象见 assert.js。失败时若TestData.logFormat json会以 JSON 形式输出结构化日志错误对象上会附带extra字段保存attr内容。2.2 命令断言commandWorked / commandFailedWithCode规范明确要求除非预期失败并显式检查否则 ALWAYS 用assert.commandWorked()包裹命令。其实现位于 assert.js核心逻辑对写结果类型_isWriteResultType递归调用assert.writeOK()检查写错误对WriteCommandError或带ok字段的原始命令响应检查ok 1且无写错误失败时构造包含{res, originalCommand, connection}的失败消息便于定位“哪条命令、在哪个连接上失败”遇到写关注超时或非瞬态锁超时会自动调用MongoRunner.runHangAnalyzer()触发挂起分析器辅助定位。与之配套的还有assert.commandFailed()不校验错误码以及更严格的assert.commandFailedWithCode()。项目建议优先使用后者以免测试在意外错误码上“假通过”。从源码看commandFailedWithCode会断言返回的错误码与期望码一致见 assert.js 附近assert._kAnyErrorCode的用法。2.3 遗留批量写 APIassert.writeOK()规范要求在使用遗留的 bulk write API时使用assert.writeOK()。其实现见 assert.js默认检查ok与writeErrors支持通过{ignoreWriteConcernErrors}选项忽略写关注错误。当assert.commandWorked()接收到写结果对象时也会内部转发到assert.writeOK()二者在写操作路径上是同一套检查逻辑。2.4 异步条件assert.soon()assert.soon()只用于异步或最终一致eventually-consistent的条件。其签名见 assert.jsassert.soon(func, msg, timeout, interval 200, {runHangAnalyzer true} {}, attr)func返回真值即成功否则每interval毫秒重试直到超过timeout后失败msg可为函数以便失败时动态生成消息。assert.soonNoExcept()则吞掉函数抛出的异常继续重试适合在副本集主从切换等场景下轮询状态。使用这类断言时要注意硬编码的超时值应设置合理上限实践中常取 10 分钟而不要随意选一个 30 秒的“魔数”。2.5 常用断言族除上述命令断言外src/mongo/shell/README.md 总结了完整断言族写作时按需选用比较类assert.eq/neq、docEq、setEq、sameMembers、close、closeWithinMS、between等包含类hasFields、contains、doesNotContain、includes等重试超时类soon、soonNoExcept、retry、retryNoExcept、time异常类throws、throwsWithCode、doesNotThrow命令类commandWorked、commandFailed、commandFailedWithCode、writeOK、writeError、writeErrorWithCode。所有专用断言都接受msg与attr两个可选参数因此都遵循下文的 JSON 日志规范。三、Mocha 风格新测试文件的标准组织方式规范要求新添加的测试文件使用 mocha 风格组织用例。mongo shell 无法直接运行标准 Mocha因此仓库提供了 jstests/libs/mochalite.js一个面向 shell 的 Mocha 子集实现提供了describe、it、before、beforeEach、afterEach、after六种 API。3.1 基本结构按需导入所需 API规范强调“只导入需要的”保持测试上下文干净import {before, describe, it} from jstests/libs/mochalite.js; describe(feature under test, function () { before(function () { /* one-time setup */ }); it(does X, function () { /* ... */ }); });jstests/README.md中给出了更完整的示例展示了四个钩子与多个it的组合import {after, afterEach, before, beforeEach, describe, it} from jstests/libs/mochalite.js; describe(simple inserts and finds, () { before(() { this.fixtureDB startupNewDB(); }); beforeEach(() { this.fixtureDB.seed(); }); afterEach(async () { await this.fixtureDB.clear(); }); after(() { this.fixtureDB.shutdown(); }); it(should do something, () { this.fixtureDB.insert({name: test}); assert.eq(this.fixtureDB.find({name: test}).count(), 1); }); it(should error on invalid data, () { const e assert.throws(() this.fixtureDB.insert({notafield: undefined})); assert.eq(e.message, Field notafield not found); }); });3.2 源码级实现原理从 mochalite.js 的源码可以看清其运行模型组合模式的作用域树DescribeScope是复合节点可嵌套其他DescribeScope或叶子节点TestScope见 mochalite.js。每个Scope共享一个Context实例this贯穿整个套件。两阶段 discoveraddDescribe只把子作用域挂到树中真正执行fn()收集钩子与子用例发生在discover()Phase 1 收集当前层钩子、Phase 2 递归发现子 describe。这样保证嵌套 describe 能继承父级完整的beforeEach/afterEach包括在嵌套 describe 调用之后注册的钩子mochalite.js。钩子语义before/after围绕整个作用域执行一次beforeEach按“外层先、内层后”顺序执行afterEach按“内层先、外层后”执行。任一钩子失败都会记入 Reporter 并影响后续执行。异步支持before/beforeEach/afterEach/after与it的内容都可以是 async 函数或返回 Promise框架会await其完成mochalite.js。注意钩子与it的函数不能声明参数assertNoFunctionArgs会直接抛错需要回调式写法时请改用 async 函数。失败聚合Reporter不会在单个用例失败时立刻退出而是聚合所有通过/失败用例最后在report()中打印摘要只要有失败就抛出Error(N failing tests detected)让 shell 进程以失败退出并把每个断言的attr即error.extraAttr原样转发给jsTest.log.errormochalite.js。3.3 it.only / describe.only / it.skip / describe.skip调试单个用例时无需改文件结构直接使用限定符it.only(should do something, () { this.fixtureDB.insert({name: test}); assert.eq(this.fixtureDB.find({name: test}).count(), 1); });源码中的Scope.run()会对子节点做“only 过滤”优先保留直接it.only其次是与describe.only祖先匹配的作用域mochalite.jsit.skip/describe.skip则实现为空操作从执行树中剔除。更推荐的做法是不改文件通过 resmoke 的--mochagrep参数过滤。mochalite 在初始化时读取全局_mocha_grep并用正则匹配用例的完整标题fullTitle()由父子 describe 标题用 拼接而成见 mochalite.jsbuildscripts/resmoke.py run --suitesno_passthrough --mochagrep do something jstests/noPassthrough/mytest.js这镜像了 Mocha 的--grep能力便于在 CI 或本地精确定位特定用例。3.4 模块化最佳实践仓库已全面迁移到 ES 模块世界新测试必须使用模块与新风格相关要求如下详见 jstests/README.md只 import/export 所需符号避免命名冲突例如避免导出alphabet这种过于通用的名字变量声明优先let/const有助于尽早发现重复声明导出使用 ES6 风格export function ...让语言服务器支持代码导航注意作用域模块化后全局变量不会污染其他测试文件比旧的load()机制更安全。四、日志输出使用 attr 传递 JSON 兼容的结构化对象规范要求在断言与日志函数中把对象传给attr参数而不是把tojson()拼接进消息字符串。绝大多数assert.*函数都接受attr对象作为最后一个参数。4.1 为什么不能用 tojson 拼接tojson()的返回值并不总是合法 JSONREADME 明确说明其“不应被用于日志”拼接进字符串后结构化信息丢失、字段无法被日志系统索引检索、嵌套对象难以阅读。而attr参数会在 JSON 日志模式下作为结构化字段随日志输出在断言失败时随错误对象以extra字段保留支持msg中的{key}占位符替换formatErrorMsg实现见 assert.js。4.2 正反示例// Bad — tojson() in the message string assert(cursor.hasOwnProperty(metrics), metrics missing: tojson(cursor)); // Good — object passed via attr assert(cursor.hasOwnProperty(metrics), metrics missing, {cursor});日志同理// Bad jsTest.log.info(got cursor: tojson(cursor)); // Good jsTest.log.info(got cursor, {cursor});4.3 日志函数的完整形态src/mongo/shell/README.md 列出了完整日志函数族jsTestLog()、jsTest.log()、jsTest.log.error()、jsTest.log.warning()、jsTest.log.info()、jsTest.log.debug()全部接受msg与可选的attr对象。在 JSON 日志格式下一次失败的断言会输出带attr.extra字段的完整结构化日志含originalError、stack、extra等便于日志分析系统直接消费。另外注意mochalite 的 Reporter 使用jsTest.log.info/jsTest.log.error输出通过/失败信息并专门把断言的attr转发为结构化日志数据而非丢弃mochalite.js这保证 mocha 风格下的失败用例同样保留结构化上下文。五、资源与设置清理测试隔离的底线规范第四条ALWAYS 在资源不再需要时立即清理。示例资源包括服务器端的游标cursor、集合collection示例设置包括服务器参数server parameters与测试框架存储在全局TestData对象中的设置。5.1 为什么必须清理jstests/README.md的“Test Isolation”一节给出了根本原因你的测试文件通常与其他成百上千个测试文件共享同一套 fixtureCI 中 resmoke 使用--continueOnFailurefixture 直到套件结束才销毁因此测试必须从已知状态开始start from a known state并在结束时恢复该状态如果修改了 fixture即使测试失败也要尽量安全还原否则会污染后续测试文件。清理的推荐做法是使用 mocha 风格的after/afterEach钩子。例如after(() { this.fixtureDB.shutdown(); })、afterEach(async () { await this.fixtureDB.clear(); })。钩子即使在前置断言失败时也会按框架流程执行天然适配“尽力还原”的要求。5.2 环境前提用 tags 而非提前 return规范还提醒如果测试对环境有前置条件如需要特定 feature compatibility version应当使用tags声明而不是在测试里提前 return这样套件调度阶段就能排除不支持的环境比“测试运行时才发现不支持”高效得多。示例/** * Tests for the XYZ feature * tags: [requires_fcv_81] */5.3 相关的其他隔离原则配合上述清理要求仓库还强调了若干测试设计原则详见 jstests/README.md最小化用例只保留验证目标行为所必需的步骤让新人一眼看懂顶层注释文件头部用块注释清晰说明测试验证什么复杂测试可补充步骤说明可调试性断言错误消息应包含服务器响应等全部排障信息不要插入无关的相同文档便于溯源数据来源重复逻辑抽到公共库不要硬编码库名/集合名用描述性变量名如collectionToDrop优于collName确定性优先能用 failpoint 固定事件顺序就不要依赖时序fuzzer 与并发套件是例外尽早失败每个命令都要包裹assert.commandWorked或assert.commandFailedWithCode避免错误后置放大避免间接断言断言最具体的属性如集合精确名称存在而不是集合计数等于 1。六、实战检查清单综合 jstests/AGENTS.md 与 jstests/README.md写一个新的 jstest 时可对照以下清单自检文件头部有块注释说明测试目的复杂场景补充步骤概述使用 mocha 风格describe/it/ 钩子只 import 所需符号优先let/const每个命令都用assert.commandWorked()包裹预期失败用assert.commandFailedWithCode()遗留 bulk API 用assert.writeOK()异步/最终一致条件才用assert.soon()超时值设置合理日志与断言消息传递对象时走attr参数不用tojson()拼接游标、集合等资源与服务器参数、TestData设置用完立即清理优先用after/afterEach钩子环境前提用tags声明而非提前 return不做间接断言、不硬编码库名/集合名、保持用例最小化与确定性。按此清单产出的测试既符合 MongoDB 服务器仓库的长期维护要求也最容易在--mochagrep过滤、JSON 日志分析和失败排查等场景中被快速定位与调试。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表