测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载Sinon 官方推荐在现代测试代码中以 Fakes 取代传统的 stubsfake 是一种不可变immutable的测试替身行为在创建时一次性定义同时完整保留 spy 的断言 API。本篇指南将系统讲解为何迁移、哪些场景必须继续使用 stubs、九种常见迁移模式、三种无法迁移的场景以及一份可直接执行的项目迁移清单与决策树帮助你在既有测试套件中平滑完成 stubs → fakes 的升级。为什么要迁移到 Fakes根据 Fakes 概念文档sinon.fake创建的是一个记录所有调用的Function每次调用的参数、返回值、this值以及抛出的异常都会被记录且 fake 是不可变对象——一旦创建行为不会改变。相比 stubs迁移到 fakes 有四个核心收益不可变Immutablefake 创建后无法修改行为从源头杜绝了测试中途行为被意外改写这类 bug更简单Simpler无需像 stub 那样链式调用多个行为方法行为在创建时一次性声明相同的 spy API所有 spy 断言calledOnce、calledWith、firstArg等都能直接用于 fakespy-call API 的firstArg、lastArg、callback属性同样可用意图更清晰Clearer intent行为在创建时就确定阅读代码即可明白测试替身要做什么。从源码层面看fake 的实现确实比 stub 简单得多fake.js 中的wrapFunc只是把用户传入的函数包进一个 proxy 并挂载调用记录而 stub.js 的createStub需要维护behaviors数组、fakes列表和matchingFakes匹配逻辑——这正是不变的 fake 更不易出错的底层原因。什么时候继续使用 StubsFakes 并非万能替代品。stub 拥有以下几类 fake 不具备的独特能力遇到这些场景应当保留 stub按调用次数定制行为onCall()、onFirstCall()、onSecondCall()、onThirdCall()按参数定制行为withArgs()可按不同参数返回不同结果属性打桩Property stubbing.get()、.set()、.value()用于访问器/属性替换可变行为Mutable behavior测试中途需要改变行为通常是代码坏味道但偶尔确实需要。其中withArgs的底层实现可以在 stub.js 中看到调用时会先通过proxy.matchingFakes(args)找出所有参数匹配的子 fake再按matchingArguments.length排序取最精确的一个来执行行为。这类机制在不可变的 fake 中并不存在因此这些场景请继续使用 stubs其余场景一律优先 fake。九种常见迁移模式以下迁移模式均采用BeforeStub/ AfterFake/ Change改动要点三段式呈现可直接对照改造。1. 简单返回值// Before (Stub) const stub sinon.stub().returns(42); const result stub(); assert.equal(result, 42); // After (Fake) const fake sinon.fake.returns(42); const result fake(); assert.equal(result, 42);改动要点将sinon.stub().returns()替换为sinon.fake.returns()。fake.returns的源码实现见 fake.js它内部定义了一个恒定返回value的函数再交给wrapFunc包装。2. 返回参数// Before (Stub) const stub sinon.stub().returnsArg(0); stub(hello); // returns hello stub(world); // returns world // After (Fake) // Fakes dont have returnsArg, use callsFake const fake sinon.fake((arg) arg); fake(hello); // returns hello fake(world); // returns world改动要点fake 没有returnsArg工厂改用sinon.fake(func)直接传入一个返回该参数的函数。这也是 fake 包装任意函数的典型用法——Fakes API 文档 将其归类为Wrap an existing function包装既有函数可用于观察被测系统或构造复杂行为。3. 抛出错误// Before (Stub) const stub sinon.stub().throws(new Error(Failed)); assert.throws(() stub()); // After (Fake) const fake sinon.fake.throws(new Error(Failed)); assert.throws(() fake());改动要点sinon.stub().throws()→sinon.fake.throws()。注意源码细节fake.throws内部调用getError(value)fake.js若传入的不是Error实例会自动包装为new Error(value)因此字符串参数同样安全。4. Promise 成功// Before (Stub) const stub sinon.stub().resolves(success); const result await stub(); assert.equal(result, success); // After (Fake) const fake sinon.fake.resolves(success); const result await fake(); assert.equal(result, success);改动要点sinon.stub().resolves()→sinon.fake.resolves()。其实现fake.js等价于内部函数return Promise.resolve(value)。5. Promise 失败// Before (Stub) const stub sinon.stub().rejects(new Error(Failed)); try { await stub(); } catch (e) { assert.equal(e.message, Failed); } // After (Fake) const fake sinon.fake.rejects(new Error(Failed)); try { await fake(); } catch (e) { assert.equal(e.message, Failed); }改动要点sinon.stub().rejects()→sinon.fake.rejects()。与throws一致fake.rejectsfake.js同样会经过getError将非 Error 值转为Error实例再Promise.reject。6. 自定义函数逻辑// Before (Stub) const stub sinon.stub().callsFake(function (x) { return x * 2; }); // After (Fake) const fake sinon.fake(function (x) { return x * 2; });改动要点把函数直接传给sinon.fake()替代链式的.callsFake()。fake主函数fake.js在传入了函数参数时会把该函数作为实际实现并在调用前记录firstArg、lastArg、callback等调用信息fake.js。7. 同步回调调用// Before (Stub) const stub sinon.stub().yields(error, data); stub((err, data) { assert.equal(err, error); assert.equal(data, data); }); // After (Fake) const fake sinon.fake.yields(error, data); fake((err, data) { assert.equal(err, error); assert.equal(data, data); });改动要点sinon.stub().yields()→sinon.fake.yields()。fake.yields的实现fake.js取最后一个参数作为回调并同步调用callback.apply(null, values)若最后一个参数不是函数会抛出TypeError(Expected last argument to be a function)。8. 异步回调调用// Before (Stub) const stub sinon.stub().yieldsAsync(error, data); // callback invoked asynchronously // After (Fake) const fake sinon.fake.yieldsAsync(error, data); // callback invoked asynchronously改动要点sinon.stub().yieldsAsync()→sinon.fake.yieldsAsync()。异步版本fake.js与同步版唯一的区别是把回调包进nextTick调度回调会在当前任务之后异步执行适合测试异步回调驱动的代码路径。9. 替换对象方法// Before (Stub) const obj { method() { return original; } }; sinon.stub(obj, method).returns(stubbed); obj.method(); // stubbed // After (Fake) const obj { method() { return original; } }; const fake sinon.fake.returns(stubbed); sinon.replace(obj, method, fake); obj.method(); // stubbed改动要点先独立创建 fake再用sinon.replace()把它插入对象。这是 fake 与 stub 最根本的架构差异Fakes API 文档 明确指出sinon.fake只负责创建fake不负责插入被测系统——插入工作统一交给sinon.replace*系列方法。sandbox.replace 文档 补充说明replace只能替换非访问器属性访问器需使用replaceGetter()/replaceSetter()且替换已替换的值会抛出异常。仓库中的测试 docs/tests/docs/fakes/_index-2.test.js 完整演示了这一流程sinon.replace(foo, bar, sinon.fake.returns(fake value))之后foo.bar()返回打桩值、fake.callCount被正确记录为 1最后用sinon.restore()还原。三种必须保留 Stubs 的场景场景一按调用次数定制行为fake 不支持按调用次序改变行为必须继续使用 stub// This pattern requires stubs const stub sinon .stub() .onFirstCall() .returns(1) .onSecondCall() .returns(2) .returns(3); stub(); // 1 stub(); // 2 stub(); // 3 stub(); // 3没有 fake 等价物——此模式请继续使用 stub。替代思路如果你能控制调用点可以拆成多个独立的 fake在各自的调用处使用const fake1 sinon.fake.returns(1); const fake2 sinon.fake.returns(2); const fake3 sinon.fake.returns(3); // Use appropriate fake at each call site场景二按参数定制行为根据参数返回不同值需要withArgsfake 无法直接等价// This pattern requires stubs const stub sinon.stub(); stub.withArgs(apple).returns(fruit); stub.withArgs(carrot).returns(vegetable); stub.returns(unknown); stub(apple); // fruit stub(carrot); // vegetable stub(pizza); // unknown没有 fake 等价物——请继续使用 stub。替代思路用带条件逻辑的 fake 函数实现等价行为const fake sinon.fake((food) { if (food apple) return fruit; if (food carrot) return vegetable; return unknown; });场景三属性打桩打桩 getter/setter 必须使用 stub// This pattern requires stubs const obj { get name() { return Alice; } }; sinon.stub(obj, name).get(() Bob); obj.name; // Bob没有 fake 等价物——属性打桩请继续使用 stub。关键差异对比可变性Stubs 可变 vs Fakes 不可变// Stubs (Mutable) const stub sinon.stub(); stub.returns(1); // Changes behavior stub(); // 1 stub.returns(2); // Changes behavior again stub(); // 2 // Fakes (Immutable) const fake1 sinon.fake.returns(1); const fake2 sinon.fake.returns(2); fake1(); // 1 fake2(); // 2 // fake1 still returns 1, fake2 returns 2stub 的行为方法可以被重复调用覆盖fake 的行为在创建时锁定需要不同行为就创建新的 fake。行为链式调用Stubs 可链式 vs Fakes 无链式// Stubs (Chained) const stub sinon.stub().onFirstCall().returns(1).onSecondCall().returns(2); // Fakes (No Chaining) // Behavior defined at creation const fake sinon.fake.returns(1); // No way to change behavior创建与插入Stubs 合并 vs Fakes 分离// Stubs (Combined) // Stub creation and plugging in are combined sinon.stub(obj, method).returns(42); // Fakes (Separated) // Creation and plugging in are separate const fake sinon.fake.returns(42); sinon.replace(obj, method, fake);创建与插入的分离遵循单一职责原则让每个步骤的意图更加清晰、更易测试。与之配套的间谍迁移指南见 Migrating from Spies to Fakes其中同样强调使用sinon.replace()完成插入。迁移检查清单改造测试套件时按以下清单逐项推进找出所有sinon.stub()用法检查是否存在按调用次数定制行为onCall、onFirstCall等——保留 stub检查是否存在按参数定制行为withArgs——保留 stub检查是否存在属性打桩.get()、.set()、.value()——保留 stub将简单 stub 替换为对应的 fake 工厂stub().returns()→fake.returns()stub().throws()→fake.throws()stub().resolves()→fake.resolves()stub().rejects()→fake.rejects()stub().yields()→fake.yields()stub().yieldsAsync()→fake.yieldsAsync()stub().callsFake()→fake(func)将对象方法打桩改为sinon.replace()方案确认所有测试仍然通过决策树在决定每个 stub 的去留时可按下述决策路径逐层判断Do you need call-specific behavior (onCall, onFirstCall)? ├─ Yes → Keep using stub └─ No → Do you need argument-based behavior (withArgs)? ├─ Yes → Keep using stub └─ No → Do you need property stubbing (.get, .set, .value)? ├─ Yes → Keep using stub └─ No → Migrate to fake!三个问题依次回答是就保留 stub全部回答否即可放心迁移为 fake。需要进一步了解 fake 的完整工厂能力fake.returns、fake.throws、fake.resolves、fake.rejects、fake.yields、fake.yieldsAsync各自的参数细节可查阅 Fakes API 文档stub 的既有 API 详见 Stub API 文档。延伸阅读Fakes 概念文档fake 的定义、基本用法与优先 fake 而非 spy/stub的官方立场Stub API 文档stub 独有的行为方法全集Why Fakes Over Spies and Stubs官方推荐理由Migrating from Spies to Fakesspy → fake 的并行迁移指南Fakes 基础用法测试可直接运行的 fake 最小示例赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐Sinon 迁移指南从 Spy 平滑迁移到 Fake 的完整实战手册Sinon 迁移指南从 Spy 平滑迁移到 Fake 的完整实战手册 本指南以 Sinon 官方文档 docs/concepts/spies/migratin测试开发工具Sinon Fakes 完整指南用不可变的轻量测试替身替代 spies 与 stubsSinon Fakes 完整指南用不可变的轻量测试替身替代 spies 与 stubs 导读 sinon.fake 是 Sinon 提供的一类更现代、更简单的测试开发工具Pillow 移植指南从 PIL 迁移到 Pillow 的完整实战手册Pillow 移植指南从 PIL 迁移到 Pillow 的完整实战手册 本指南基于仓库文档 docs/porting.rst https://link.git图像处理计算机视觉上一篇TanStack Solid Table AppHeaderProps 接口深度解析构建可复用表头与表尾组件的类型契约下一篇用 amalgamate.py 打造单头文件JSON for Modern C 的源码合并工具与 single_include/nlohmann/json.hpp 生成全解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
