- 测试
- 质量保障
【免费下载链接】jasmine
Simple JavaScript testing framework for browsers and node.js
本文基于 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)共八项:
- 异步规格(async specs)的新语法
- Spy 的新语法
- Reporter 的新接口
- 更好的相等性(Equality)测试
- 自定义 Matcher 接口替换
toThrowmatcher 行为变更- Clock 在规格结束后保持安装
- 更多内部变量/函数被移入闭包
下面逐项展开,并对照当前仓库源码给出实现印证。
二、破坏性变更逐项详解(附源码印证)
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 时最需要关注的迁移点如下:
- 异步规格:弃用
waitsFor/runs风格,改用beforeEach/it/afterEach的done回调(或返回 Promise 的写法); - Spy:将
spyOn(...).andReturn(x)之类的旧链式写法改为spyOn(...).and.returnValue(x);注意and.returnValue与and.throwError的命名变更; - Reporter:按
jasmineStarted / jasmineDone / suiteStarted / suiteDone / specStarted / specDone六事件重写自定义 Reporter,并可通过timer对象获取执行时间; - 自定义 Matcher:改用
env.addMatchers()/env.addAsyncMatchers()注册,匹配器通过返回{ pass, message }或使用negativeCompare实现.not语义; - 异常断言:区分
toThrow(抛出了什么值)与toThrowError(抛出何种 Error 类型 / 消息); - Clock:显式调用
jasmine.clock().uninstall()恢复全局计时函数,否则 mock 时钟会跨规格保持生效; - 环境初始化:环境装配已移至
boot.js,自定义 Reporter 应在env.execute()前通过env.addReporter()接入; - 加载方式:新增文件需挂接到自定义 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
相关推荐
type-graphql 版本演进全景解析:从 CHANGELOG 看 2.0 重构、破坏性变更与架构升级
type graphql 版本演进全景解析:从 CHANGELOG 看 2.0 重构、破坏性变更与架构升级 type graphql 是一个基于 TypeScr
后端GraphQLAPI设计Litho 版本演进深度解析:从 v0.23 到 v0.51 的关键变更、破坏性迁移与架构重构全指南
Litho 版本演进深度解析:从 v0.23 到 v0.51 的关键变更、破坏性迁移与架构重构全指南 导读:本文以仓库根目录 CHANGELOG.md http
移动开发UI组件Fabric.js 版本演进全解析:从 1.0 到 7.4 的核心变更、破坏性更新与架构重构指南
Fabric.js 版本演进全解析:从 1.0 到 7.4 的核心变更、破坏性更新与架构重构指南 本篇技术指南以本仓库根目录 CHANGELOG.md http
前端图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考