news 2026/10/3 17:37:13

Jasmine Core 2.0 破坏性变更与架构演进深度解析:异步规格、Spy 语法、Reporter 接口与相等性测试重构全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jasmine Core 2.0 破坏性变更与架构演进深度解析:异步规格、Spy 语法、Reporter 接口与相等性测试重构全记录
  • 测试
  • 质量保障

【免费下载链接】jasmine

Simple JavaScript testing framework for browsers and node.js

项目地址:https://gitcode.com/gh_mirrors/ja/jasmine
点击查看免费下载

本文基于 Jasmine 仓库 release_notes/20.md 发布说明撰写。Jasmine 2.0 是框架历史上一次"推倒重来"式的大版本:它重写了异步测试语法、Spy 与自定义 Matcher 接口,替换了相等性测试引擎,重构了 Reporter 协议,并把 Node.js 提升为一级支持对象。读完本文,你将系统掌握 2.0 版本每一项破坏性变更的前因后果、新接口的用法,以及这些设计如何在当前仓库源码中落地与延续,从而在升级或阅读新旧代码时准确判断接口语义。

一、2.0 版本定位:一次面向接口一致性的重构

Jasmine Core 2.0的发布说明(release_notes/20.md)开篇即明确:这一版本的核心不是新增功能,而是接口的全面重设计。官方列出的破坏性变更(不兼容 1.x)共八项:

  1. 异步规格(async specs)的新语法
  2. Spy 的新语法
  3. Reporter 的新接口
  4. 更好的相等性(Equality)测试
  5. 自定义 Matcher 接口替换
  6. toThrowmatcher 行为变更
  7. Clock 在规格结束后保持安装
  8. 更多内部变量/函数被移入闭包

下面逐项展开,并对照当前仓库源码给出实现印证。

二、破坏性变更逐项详解(附源码印证)

2.1 异步规格新语法:done回调驱动的串行队列

1.x 时代异步测试依赖waitsFor/runs这类显式调度原语,写法繁琐。2.0 参考了 Mocha 的做法:before/it/after系列函数可以接受可选的done回调,框架会等待done被调用或超时后,才继续执行下一个before、spec或after。

这套语义的底层实现正是当前仓库的 src/core/QueueRunner.js:

  • 在attempt()中,QueueRunner 检查queueableFn.fn.length:若函数声明了形参(即接收done),则以fn.call(this.userContext, next)方式调用并标记为"未同步完成"(completedSynchronously = false);
  • 若函数未声明形参但返回了 thenable,则通过wrapInPromiseResolutionHandler(next)桥接 Promise;
  • 每个 queueableFn 都会注册超时定时器:queueableFn.timeout || j$.DEFAULT_TIMEOUT_INTERVAL,超时后产生"Timeout - Async function did not complete within Nms"错误(见 QueueRunner.js);
  • next被once()包裹,重复调用done会触发onMultipleDone告警,防止异步回调被调用多次导致队列混乱。

这一设计沿用至今,只是后来(3.x 起)Jasmine 进一步支持直接返回 Promise 的 async/await 风格。从源码结构看,2.0 引入的"回调串行队列 + 超时保护"是整套异步模型的地基。

2.2 Spy 新语法:and链式配置与属性保留

2.0 重构了 Spy 的配置语法,核心动机是保留被 spy 函数上已有的属性,并形成更清晰的测试模式。发布说明提到 "preserve any of the properties on a spied-upon function",这一点在 src/core/Spy.js 中仍有直接体现:

for (const prop in originalFn) { if (prop === 'and' || prop === 'calls') { throw new Error("Jasmine spies would overwrite the 'and' and 'calls' properties ..."); } wrapper[prop] = originalFn[prop]; }

Spy 包装函数会复制原函数的所有自有属性,同时保留两个专用接口:wrapper.and(策略配置入口)与wrapper.calls(调用追踪器)。新语法形态为:

spyOn(someObj, 'func').and.returnValue(42); spyOn(someObj, 'func').and.callThrough(); spyOn(someObj, 'func').and.callFake(function() { return 'fake'; }); spyOn(someObj, 'func').and.throwError('boom'); spyOn(someObj, 'func').and.stub();

这些策略方法在 src/core/SpyStrategy.js 中均有定义,其中returnValue与throwError正是发布说明 Bug 修复条目中提到的"更佳命名"("A spy's strategy now has propertiesreturnValueandthrowErrorbecause they are better names")。此外,Spy#withArgs允许按参数精确匹配策略(Spy.js),调用记录则通过CallTracker以callData(object / invocationOrder / args / returnValue)结构化保存(Spy.js),配合toHaveBeenCalledWith等匹配器使用。

2.3 Reporter 新接口:面向结果数据的事件协议

2.0 将 Reporter 接口整体替换为更一致的回调集合,并且"传入的对象只提供报告结果所需的数据",从而让自定义 Reporter 与 Jasmine 内部实现解耦。Reporter API 还新增了timer对象的槽位(对应发布说明中 "Reporters get execution time" 这条合并的 PR)。

当前仓库 src/core/reporterEvents.js 定义了完整的事件集合:

事件触发时机
jasmineStarted所有规格加载完毕、执行开始前
jasmineDone整个套件执行结束
suiteStarted/suiteDone每个describe开始 / 结束(含子套件)
specStarted/specDone每个it(含其beforeEach/afterEach)开始 / 结束

事件对象通过 src/core/ReportDispatcher.js 分发到所有注册的 Reporter,Env#addReporter(src/core/Env.js)负责登记。这套"六个事件 + 结构化结果对象"的协议自 2.0 定型后基本保持稳定,成为社区编写 Jasmine 自定义报告器的基础契约。

2.4 更好的相等性测试:源自 UnderscoreisEqual的重写

2.0 删除了旧的相等性代码,以 Underscore.js 的isEqual为起点重写并补充了大量测试。这一渊源在当前源码中仍有注释可查——src/core/matchers/matchersUtil.js 明确写道:

// Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org)

新引擎MatchersUtil#equals(标注@since 2.0.0)支持的语义包括:

  • 原始值、字符串、数字(含NaN、+0/-0区分)、布尔值的按值比较;
  • 日期按毫秒时间戳比较,正则按source与 flags 比较;
  • 数组、Map、Set、嵌套对象的深度递归比较,并处理循环引用;
  • DOM 节点走isEqualNode;
  • 支持不对称相等性测试器(asymmetric equality testers),如jasmine.any()、jasmine.objectContaining(),与equals/contains无缝协作;
  • 通过customTesters_支持用户自定义相等性测试器(Env#addCustomEqualityTester,见 src/core/Env.js)。

同一文件中MatchersUtil#contains同样标注@since 2.0.0,用于toContain等匹配器。这也是jasmine.Any支持Boolean(合并的 PR #392)这类修复能够落地的底层基础。

2.5 自定义 Matcher 接口替换:官方匹配器同机制自举

发布说明直言:自定义 Matcher 的 API"此前几乎无文档、难以测试",2.0 将其整体替换,并且Jasmine 自身的所有匹配器也改用同一套自定义机制注册——即"Dogfooding"(吃自己的狗粮,自举式开发)。

这一设计在 src/core/requireCore.js 中清晰可见:

private$.matchers = jRequire.requireMatchers(jRequire, j$, private$); private$.asyncMatchers = jRequire.requireAsyncMatchers(jRequire, j$, private$);

requireMatchers.js/requireAsyncMatchers.js把官方匹配器统一装载后注入private$,再由private$.Expectation.addCoreMatchers(...)挂到期望对象上(src/core/Env.js)。用户侧注册接口收敛为两个:

env.addMatchers({ ... }); // 同步匹配器 env.addAsyncMatchers({ ... }); // 异步匹配器

(见 src/core/Env.js)。发布说明还提到自定义 Matcher 可传入negativeCompare,用于.not场景的定制实现;当前 src/core/Expectation.js 中依然保留.not对负向比较的调度逻辑。

2.6toThrow拆分:错误类型校验交给toThrowError

2.0 改变了toThrow的职责边界,将"抛出特定类型 Error / 特定消息"的校验能力迁移到新匹配器toThrowError,从而覆盖更多使用场景:

  • src/core/matchers/toThrow.js:只负责"是否抛出了东西"以及"抛出的值与期望值是否相等"(matchersUtil.equals(thrown, expected));
  • src/core/matchers/toThrowError.js(@since 2.0.0):支持四种组合用法——
expect(fn).toThrowError(); // 只要抛出 Error expect(fn).toThrowError(MyCustomError); // 必须是某 Error 子类实例 expect(fn).toThrowError('message'); // 消息精确匹配 expect(fn).toThrowError(MyCustomError, /bar/); // 类型 + 正则消息

实现中isAnErrorType通过构造Surrogate原型链判断类型是否为 Error 子类,exactMatcher则用error instanceof errorType与expected.test(error.message)双重判定(toThrowError.js)。另有一条相关修复:"toThrowmatchers handle falsy exceptions"(PR #317),保证抛出0、''、null等 falsy 异常时也能正确报告。

2.7 Clock 保持安装:消除计时函数归属歧义

1.x 中 Clock 会在规格结束时自动卸载,导致"当前到底用的是 mock 还是全局计时函数"存在歧义。2.0 起,安装(install)之后 Clock一直保持生效,直到显式调用uninstall。

当前 src/core/Clock.js 中的实现印证了这一契约:

  • install()(@since 2.0.0):校验全局计时函数未被篡改后,用setTimeout/clearTimeout/setInterval/clearInterval的 fake 版本替换全局(Clock.js);
  • uninstall()(@since 2.0.0):恢复真实计时函数,同时卸载MockDate(Clock.js);
  • tick(millis)驱动DelayedFunctionScheduler前进,并同步推进 mock 日期。

配套修复还包括"Clock ticking 默认改为 0"(PR #340)、"Mock clock 更少侵入——仅在安装时替换全局计时函数",以及"Clock 支持将eval的字符串作为函数调度"等。典型用法:

jasmine.clock().install(); jasmine.clock().mockDate(new Date(2020, 0, 1)); // ... 触发 setTimeout 逻辑 jasmine.clock().tick(1000); jasmine.clock().uninstall();

2.8 内部变量闭包化:更干净的全局接口

2.0 将一批内部函数(如"获取下一个 spec id")移入闭包,使jasmine命名空间只暴露真正的公共 API。这一治理思路在当前源码中体现为 src/core/requireCore.js 中private$与j$的双层隔离:所有实现细节存于private$闭包对象,对外只开放jasmine(j$)接口,并利用Object.defineProperty将公共成员设为不可写、不可配置,防止误改(requireCore.js)。

三、其他重要变更

3.1 大规模重构与更好的测试

发布说明将"大规模重构"列为最大的一组变更:几乎每个文件、每个对象都被触及,对象被合并或拆分,代码风格统一,几乎所有测试被重写。其结果是"Jasmine 由更小、更松耦合、通过显式依赖注入进行单元测试的对象组成"。这一架构特征在 src/core/requireCore.js 的装配清单中体现得淋漓尽致——Env、QueueRunner、Suite、Spec、Spy、CallTracker、ReportDispatcher、TreeProcessor等均通过工厂函数注入依赖组合而成,也直接服务于 2.0 以来的可扩展性(社区更容易提交合理的一轮通过的 Pull Request)。

3.2 环境初始化移至boot.js

Jasmine 环境的实例化与装配——包括构建 Reporter、暴露全局函数、执行测试——被集中到独立的boot.js文件,目的有二:一是让自定义 Reporter 的接入、对象配置、外部使用方式的定制变得更简单;二是支持"自加载两次"的开发模式(devboot.js分别从打包的jasmine.js与源码目录各加载一次,用于自测)。

当前仓库的浏览器端入口 src/boot/boot.js 仍保持这一职责划分:在window.onload中读取 URL 查询参数配置环境(env.configure(urls.configFromCurrentUrl())),创建HtmlReporterV2并通过env.addReporter()注册,最后调用env.execute()。也就是说,从 2.0 起,"谁搭环境、谁报结果、谁执行"被明确分离,用户可以在execute()之前自由插入自己的 Reporter。

3.3 开发与构建迁移到 Grunt(Node.js 工具链)

2.0 将命令行开发任务从 Ruby 迁移到 Node.js + Grunt.js。官方给出的理由很直白:JavaScript 工具擅长构建 JavaScript 项目,团队维护成本更低,社区也能在提交 PR 前先在浏览器和 Node 中验证贡献。构建相关脚本在当前仓库中仍有传承(见 scripts/buildDistribution.js、scripts/buildStandaloneDist.js 与 scripts/lib/buildDistribution.js),只是具体任务编排已随时代演进而变化。

3.4 自定义 require 方案:浏览器与 Node 统一加载

为了不引入新的运行时依赖又保持加载的干净,2.0 编写了一套同时工作在 Node.js 与浏览器中的自定义 "require" 方案。其当前形态集中在 src/core/requireCore.js 与 src/core/requireSuffix.js:

  • getJasmineRequireObj根据环境选择exports(Node)或普通对象(浏览器);
  • core()完成全部内部模块装配,返回{ jasmine: j$, private: private$ };
  • bootJasmine()在 Node 下module.exports = bootJasmine(...),在浏览器下直接bootJasmine().installGlobals()把describe、it、expect等挂到全局。

发布说明特别提醒:这套方案"只影响新增文件的 Pull Request",新增源文件时必须挂到 require 链路上,否则无法被加载。

3.5 用 Jasmine 测试 Jasmine:jasmine与j$的双轨约定

自定义 require 系统强化了"自举测试":打包后的jasmine.js用来测试源码目录中的 Jasmine 自身。在 Jasmine 的测试代码里会出现两个标识符:

  • jasmine:永远指源码版本的 Jasmine;
  • j$:指被加载到引用中的 Jasmine 实现。

官方要求所有贡献者遵守该约定。当前仓库 spec/helpers/defineJasmineUnderTest.js 及其 Node 版本 spec/helpers/nodeDefineJasmineUnderTest.js 正是这一自测机制的载体,用于把"待测的 Jasmine(源码版)"暴露为jasmineUnderTest供规格引用。

3.6 Node.js 成为一等公民

2.0 将 Node.js 正式纳入官方 CI 构建矩阵(Travis)。当时的node_suite.js本质上是"面向 Node 的 boot.js",官方 npm 包随后发布。当前仓库中,Node 侧运行规格的脚本已演进为 scripts/runSpecsInNode.js,并支持并行执行(scripts/runSpecsInParallel.js);而 src/core/requireSuffix.js 的 Node 分支(module.exports)正是"Node 上一等公民"加载方式的直接证据。

3.7 CI 矩阵、支持矩阵与工程治理

  • Travis CI 矩阵:2.0 的 CI 在浏览器矩阵中运行核心规格,覆盖 Firefox、Chrome、Safari 5/6、PhantomJS、Node.js 以及 IE 8/9/10,OS 矩阵尚不完整,并接受社区补充 OS/浏览器组合的 PR。当前仓库的 Sauce 支持脚本(scripts/run-sauce-browsers、scripts/start-sauce-connect)可视为这一浏览器矩阵测试策略的延续。
  • 支持矩阵更新:2.0 起放弃 IE 8 以下版本(IE < 8),需要兼容老浏览器的项目可继续使用 Jasmine 1.x。
  • 移除 JsDoc 页面:官方理由是"代码中的注释是等待发生的谎言"——过时的 JsDoc 注释与更过时的生成页面毫无帮助,因此全部移除,转而维护面向真实使用接口的introduction.js页面。
  • 引入 Code Climate:用于快速定位 JavaScript 代码热点(code hotspots),属于当时的工程质量度量工具。

四、2.0 合并的 Pull Requests 与 Bug 修复亮点

发布说明末尾列出了 2.0 周期内合并的 PR 与修复,按主题归纳如下(与上文各节对应):

功能与接口类

  • jasmine.Any支持Boolean(#392);
  • Reporters 可获得执行时间(#30);
  • toThrow匹配器正确处理 falsy 异常(#317);
  • Clock 默认 tick 改为 0(#340);
  • Env.execute允许接收可选的 spec/suite id 列表(与当前 src/core/Env.js 中execute(runablesToRun)的参数形态一致);
  • Spy 策略属性更名为returnValue/throwError(见 2.2 节)。

质量与行为修复

  • 白空格失败信息更易读(#332)、UTF-8 编码修复(#333);
  • getGlobal()在严格模式下可用;
  • 异步规格抛出异常时也能清除超时定时器;
  • 延迟函数内调度的超时被正确调度与执行;
  • 移除jasmine.Matchers.pp弃用 API(#363);
  • 消除j$的意外全局污染、修复timer全局泄漏;
  • beforeEach/it/afterEach之间保持一致的this;
  • 没有 expectation 的规格被视为通过;
  • 异步回调未在默认时间间隔内调用时,错误信息更友好;
  • 所有异步函数均受超时约束;
  • 自定义 matcher 可传negativeCompare以支持.not的定制实现;
  • HTML 输出性能优化(CSS 改动,#428)、HTML 标题可复制、规格全名不加多余句点、favicon 换用更高分辨率。

HTML/加载类小改进

  • HTML Reporter 为简洁与性能重构;HTML runner 页默认 UTF-8 编码;对spec参数中的正则特殊字符做转义;favicon 回归;失败时总有堆栈;移除未使用的jasmine.VERBOSE与jasmine.XmlHttpRequest引用。

五、从 1.x 迁移到 2.0 的要点清单

综合发布说明,从 1.x 升级到 2.0 时最需要关注的迁移点如下:

  1. 异步规格:弃用waitsFor/runs风格,改用beforeEach/it/afterEach的done回调(或返回 Promise 的写法);
  2. Spy:将spyOn(...).andReturn(x)之类的旧链式写法改为spyOn(...).and.returnValue(x);注意and.returnValue与and.throwError的命名变更;
  3. Reporter:按jasmineStarted / jasmineDone / suiteStarted / suiteDone / specStarted / specDone六事件重写自定义 Reporter,并可通过timer对象获取执行时间;
  4. 自定义 Matcher:改用env.addMatchers()/env.addAsyncMatchers()注册,匹配器通过返回{ pass, message }或使用negativeCompare实现.not语义;
  5. 异常断言:区分toThrow(抛出了什么值)与toThrowError(抛出何种 Error 类型 / 消息);
  6. Clock:显式调用jasmine.clock().uninstall()恢复全局计时函数,否则 mock 时钟会跨规格保持生效;
  7. 环境初始化:环境装配已移至boot.js,自定义 Reporter 应在env.execute()前通过env.addReporter()接入;
  8. 加载方式:新增文件需挂接到自定义 require 链路上;测试 Jasmine 自身时遵循jasmine(源码版)与j$(被测引用)的双轨约定。

六、结语

Jasmine Core 2.0 的价值不在于堆砌新功能,而在于把 1.x 时代积累的接口不一致性一次性清算:异步模型收敛为done回调串行队列,Spy 语法统一到and策略链,Reporter 变成六个稳定事件,相等性测试换上新引擎,自定义 Matcher 与官方匹配器共用同一套机制。这些决策大多在当前仓库源码中依然可循——QueueRunner.js、Spy.js、reporterEvents.js、matchersUtil.js、boot.js 等文件共同构成了 2.0 留给后世的架构遗产,也奠定了 Jasmine 之后十余年版本迭代的接口基石。对于需要阅读历史版本代码、维护老旧测试库或理解当前 Jasmine 接口演化的开发者,这份 2.0 发布说明都是不可跳过的关键文献。

  • 测试
  • 质量保障

【免费下载链接】jasmine

Simple JavaScript testing framework for browsers and node.js

项目地址:https://gitcode.com/gh_mirrors/ja/jasmine
点击查看免费下载

相关推荐

上一篇:MetaboAnalystR 实战:从原始峰表到通路富集的 5 步工作流
下一篇:3 个步骤跑通 sndcpy:让手机声音直接传到电脑播放

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 17:28:39

Linux系统编程-信号(黑马笔记)

当一个进程跟另外一个进程发送信号后&#xff0c;接收信号的进程有以下几种行为&#xff1a;1.执行默认处理 2.忽略 3.捕捉后特定处理1.kill函数发送信号的函数#include <sys/types.h> #include <signal.h>int kill(pid_t pid, int sig);参数说明pid&#xff1a;指…

作者头像 李华
网站建设 2026/10/3 17:25:00

犯罪预测综述:数据、模型与评估的完整链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 17:23:59

DRV8818与PIC18F4685组合驱动双极步进电机的工业实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 17:22:57

MySQL学习地图:64学时从建库到备份的实战路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 17:21:20

STM32 HardFault排查实战:从栈回溯到精准定位代码行

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华